sgw: properly open and stat the egress file
[olsrd.git] / src / egressFile.c
1 #ifdef __linux__
2
3 #include "egressFile.h"
4
5 /* Plugin includes */
6
7 /* OLSRD includes */
8 #include "olsr_cfg.h"
9 #include "gateway_costs.h"
10 #include "gateway.h"
11 #include "scheduler.h"
12 #include "ipcalc.h"
13 #include "log.h"
14
15 /* System includes */
16 #include <unistd.h>
17 #include <fcntl.h>
18 #include <regex.h>
19 #include <sys/stat.h>
20 #include <assert.h>
21 #include <net/if.h>
22
23 /** the maximum length of a line that is read from the file */
24 #define LINE_LENGTH 256
25
26 /** the weights for the cost calculation */
27 static struct costs_weights gw_costs_weights_storage;
28 static struct costs_weights * gw_costs_weights = NULL;
29
30 /** regular expression describing a comment */
31 static const char * regexComment = "^([[:space:]]*|[[:space:]#]+.*)$";
32
33 /**
34  * regular expression describing an egress line.
35  *
36  * # interface=uplink (Kbps),downlink (Kbps),path cost,gateway
37  */
38 static const char * regexEgress = "^[[:space:]]*" //
39         "([^[:space:]=]+)"                     /* 01: interface, mandatory, can NOT be empty */
40         "[[:space:]]*=[[:space:]]*"            /* --: field separator */
41         "([[:digit:]]*)"                       /* 02: uplink, mandatory, can be empty */
42         "[[:space:]]*,[[:space:]]*"            /* --: field separator */
43         "([[:digit:]]*)"                       /* 03: downlink, mandatory, can be empty */
44         "("                                    /* 04: (the rest is optional) */
45         "[[:space:]]*,[[:space:]]*"            /* --: field separator */
46         "([[:digit:]]*)"                       /* 05: path cost, optional, can be empty */
47         "("                                    /* 06: (the rest is optional) */
48         "[[:space:]]*,[[:space:]]*"            /* --: field separator */
49         "(|([[:digit:]\\.:]+)/([[:digit:]]+))" /* 07: network, optional, can be empty, 07=ip/x 08=ip 09=x */
50         "("                                    /* 10: (the rest is optional) */
51         "[[:space:]]*,[[:space:]]*"            /* --: field separator */
52         "([[:digit:]\\.:]*)"                   /* 11: gateway, optional, can be empty */
53         ")?"                                   /* 10 */
54         ")?"                                   /* 06 */
55         ")?"                                   /* 04 */
56         "[[:space:]]*$";
57
58 /** the number of matches in regexEgress */
59 #define REGEX_EGRESS_LINE_MATCH_COUNT (1 /* 00 */ + 11)
60
61 /** the compiled regular expression describing a comment */
62 static regex_t compiledRegexComment;
63
64 /** the compiled regular expression describing an egress line */
65 static regex_t compiledRegexEgress;
66
67 /** true when the file reader has been started */
68 static bool started = false;
69
70 /** type to hold the cached stat result */
71 typedef struct _CachedStat {
72 #if defined(__linux__) && !defined(__ANDROID__)
73   struct timespec timeStamp; /* Time of last modification (full resolution) */
74 #else
75   time_t timeStamp; /* Time of last modification (second resolution) */
76 #endif
77 } CachedStat;
78
79 /** the cached stat result */
80 static CachedStat cachedStat;
81
82 /** the malloc-ed buffer in which to store a line read from the file */
83 static char * line = NULL;
84
85 /* forward declaration */
86 static bool readEgressFile(const char * fileName);
87
88 /*
89  * Error Reporting
90  */
91
92 /** the maximum length of an error report */
93 #define ERROR_LENGTH 1024
94
95 /** true when errors have been reported, used to reduce error reports */
96 static bool reportedErrors = false;
97
98 /**
99  * Report an error.
100  *
101  * @param useErrno
102  * when true then errno is used in the error message; the error reason is also
103  * reported.
104  * @param lineNo
105  * the line number of the caller
106  * @param format
107  * a pointer to the format string
108  * @param ...
109  * arguments to the format string
110  */
111 __attribute__ ((format(printf, 3, 4)))
112 static void egressFileError(bool useErrno, int lineNo, const char *format, ...) {
113   char str[ERROR_LENGTH];
114   char *strErr = NULL;
115
116   if (reportedErrors) {
117     return;
118   }
119
120   if (useErrno) {
121     strErr = strerror(errno);
122   }
123
124   if ((format == NULL ) || (*format == '\0')) {
125     olsr_syslog(OLSR_LOG_ERR, "%s@%d: %s\n", __FILE__, lineNo, useErrno ? strErr : "Unknown error");
126   } else {
127     va_list arglist;
128
129     va_start(arglist, format);
130     vsnprintf(str, sizeof(str), format, arglist);
131     va_end(arglist);
132
133     str[sizeof(str) - 1] = '\0'; /* Ensures null termination */
134
135     if (useErrno) {
136       olsr_syslog(OLSR_LOG_ERR, "%s@%d: %s: %s\n", __FILE__, lineNo, str, strErr);
137     } else {
138       olsr_syslog(OLSR_LOG_ERR, "%s@%d: %s\n", __FILE__, lineNo, str);
139     }
140   }
141 }
142
143 /*
144  * Helpers
145  */
146
147 /**
148  * Read an (olsr_ip_addr) IP address from a string:
149  * First tries to parse the value as an IPv4 address, and if not successful
150  * tries to parse it as an IPv6 address.
151  *
152  * @param str
153  * The string to convert to an (olsr_ip_addr) IP address
154  * @param dst
155  * A pointer to the location where to store the (olsr_ip_addr) IP address upon
156  * successful conversion. Not touched when errors are reported.
157  * @param dstSet
158  * A pointer to the location where to store the flag that signals whether the
159  * IP address is set. Not touched when errors are reported.
160  * @param dstIpVersion
161  * A pointer to the location where to store the IP version of the IP address.
162  * Not touched when errors are reported.
163  *
164  * @return
165  * - true on success
166  * - false otherwise
167  */
168 static bool readIPAddress(const char * str, union olsr_ip_addr * dst, bool * dstSet, int * dstIpVersion) {
169   int conversion;
170   union olsr_ip_addr ip;
171   int ip_version;
172
173   assert(str);
174   assert(dst);
175   assert(dstSet);
176   assert(dstIpVersion);
177
178   /* try IPv4 first */
179   ip_version = AF_INET;
180   memset(&ip, 0, sizeof(ip));
181   conversion = inet_pton(ip_version, str, &ip.v4);
182
183   if (conversion != 1) {
184     /* now try IPv6: IPv4 conversion was not successful */
185     ip_version = AF_INET6;
186     memset(&ip, 0, sizeof(ip));
187     conversion = inet_pton(ip_version, str, &ip.v6);
188   }
189
190   if (conversion != 1) {
191     return false;
192   }
193
194   *dst = ip;
195   *dstSet = true;
196   *dstIpVersion = ip_version;
197   return true;
198 }
199
200 /**
201  * Read an unsigned long long number from a value string.
202  * An empty string results in a value of zero.
203  *
204  * @param value
205  * The string to convert to a number
206  * @param valueNumber
207  * A pointer to the location where to store the number upon successful conversion.
208  * Not touched when errors are reported.
209  *
210  * @return
211  * - true on success
212  * - false otherwise
213  */
214 static bool readULL(const char * value, unsigned long long * valueNumber) {
215   char * endPtr = NULL;
216   unsigned long valueNew;
217
218   assert(value);
219   assert(valueNumber);
220
221   if (!value || !strlen(value)) {
222     *valueNumber = 0;
223     return true;
224   }
225
226   errno = 0;
227   valueNew = strtoull(value, &endPtr, 10);
228
229   if (!((endPtr != value) && (*value != '\0') && (*endPtr == '\0')) || (errno == ERANGE)) {
230     /* invalid conversion */
231     return false;
232   }
233
234   *valueNumber = valueNew;
235   return true;
236 }
237
238 /**
239  * Strip EOL characters from the end of a string
240  *
241  * @param str the string to strip
242  * @param length the length of the string
243  */
244 static void stripEols(char * str, ssize_t length) {
245   ssize_t len = length;
246   while ((len > 0) && ((str[len - 1] == '\n') || (str[len - 1] == '\r'))) {
247     len--;
248   }
249   str[len] = '\0';
250 }
251
252 /**
253  * Find an egress interface in the configuration
254  *
255  * @param name the name of the egress interface
256  * @return the pointer to the egress interface, NULL when not found
257  */
258 struct sgw_egress_if * findEgressInterface(char * name) {
259   if (name && (name[0] != '\0')) {
260     struct sgw_egress_if * egress_if = olsr_cnf->smart_gw_egress_interfaces;
261     while (egress_if) {
262       if (!strcmp(egress_if->name, name)) {
263         return egress_if;
264       }
265       egress_if = egress_if->next;
266     }
267   }
268
269   return NULL ;
270 }
271
272 /**
273  * Find an egress interface in the configuration by if_index
274  *
275  * @param if_index the index of the egress interface
276  * @return the pointer to the egress interface, NULL when not found
277  */
278 struct sgw_egress_if * findEgressInterfaceByIndex(int if_index) {
279   if (if_index > 0) {
280     struct sgw_egress_if * egress_if = olsr_cnf->smart_gw_egress_interfaces;
281     while (egress_if) {
282       if (egress_if->if_index == if_index) {
283         return egress_if;
284       }
285       egress_if = egress_if->next;
286     }
287   }
288
289   return NULL ;
290 }
291
292 /**
293  * Calculate the costs from the bandwidth parameters
294  *
295  * @param bw the bandwidth parameters
296  * @param up true when the interface is up
297  * @return true when the costs changed
298  */
299 bool egressBwCalculateCosts(struct egress_if_bw * bw, bool up) {
300   if (!gw_costs_weights) {
301     gw_costs_weights_storage.WexitU = olsr_cnf->smart_gw_weight_exitlink_up;
302     gw_costs_weights_storage.WexitD = olsr_cnf->smart_gw_weight_exitlink_down;
303     gw_costs_weights_storage.Wetx = olsr_cnf->smart_gw_weight_etx;
304     gw_costs_weights_storage.Detx = olsr_cnf->smart_gw_divider_etx;
305     gw_costs_weights = &gw_costs_weights_storage;
306   }
307
308   {
309     int64_t costsPrevious = bw->costs;
310     bw->costs = gw_costs_weigh(up, gw_costs_weights_storage, bw->path_cost, olsr_cnf->smart_gw_path_max_cost_etx_max, bw->egressUk, bw->egressDk);
311     return (costsPrevious != bw->costs);
312   }
313 }
314
315 /**
316  * Clear the bandwidth parameters
317  * @param bw the bandwidth parameters
318  * @param up true when the interface is up
319  */
320 void egressBwClear(struct egress_if_bw * bw, bool up) {
321   bw->egressUk = 0;
322   bw->egressDk = 0;
323   bw->path_cost = 0;
324   memset(&bw->network, 0, sizeof(bw->network));
325   memset(&bw->gateway, 0, sizeof(bw->gateway));
326
327   bw->networkSet = false;
328   bw->gatewaySet = false;
329
330   egressBwCalculateCosts(bw, up);
331 }
332
333 /*
334  * Timer
335  */
336
337 /** the timer for polling the egress file for changes */
338 static struct timer_entry *egress_file_timer;
339
340 /**
341  * Timer callback to read the egress file
342  *
343  * @param unused unused
344  */
345 static void egress_file_timer_callback(void *unused __attribute__ ((unused))) {
346   if (readEgressFile(olsr_cnf->smart_gw_egress_file)) {
347     doRoutesMultiGw(true, false, GW_MULTI_CHANGE_PHASE_RUNTIME);
348   }
349 }
350
351 /*
352  * Life Cycle
353  */
354
355 /**
356  * Initialises the egress file reader
357  *
358  * @return
359  * - true upon success
360  * - false otherwise
361  */
362 bool startEgressFile(void) {
363   int r;
364
365   if (started) {
366     return true;
367   }
368
369   line = malloc(LINE_LENGTH);
370   if (!line) {
371     egressFileError(false, __LINE__, "Could not allocate a line buffer");
372     return false;
373   }
374   *line = '\0';
375
376   r = regcomp(&compiledRegexComment, regexComment, REG_EXTENDED);
377   if (r) {
378     regerror(r, &compiledRegexComment, line, LINE_LENGTH);
379     egressFileError(false, __LINE__, "Could not compile regex \"%s\" (%d = %s)", regexComment, r, line);
380
381     free(line);
382     line = NULL;
383     return false;
384   }
385
386   r = regcomp(&compiledRegexEgress, regexEgress, REG_EXTENDED);
387   if (r) {
388     regerror(r, &compiledRegexEgress, line, LINE_LENGTH);
389     egressFileError(false, __LINE__, "Could not compile regex \"%s\" (%d = %s)", regexEgress, r, line);
390
391     regfree(&compiledRegexComment);
392     free(line);
393     line = NULL;
394     return false;
395   }
396
397   memset(&cachedStat.timeStamp, 0, sizeof(cachedStat.timeStamp));
398
399   readEgressFile(olsr_cnf->smart_gw_egress_file);
400
401   olsr_set_timer(&egress_file_timer, olsr_cnf->smart_gw_egress_file_period, 0, true, &egress_file_timer_callback, NULL, NULL);
402
403   started = true;
404   return true;
405 }
406
407 /**
408  * Cleans up the egress file reader.
409  */
410 void stopEgressFile(void) {
411   if (started) {
412     olsr_stop_timer(egress_file_timer);
413     egress_file_timer = NULL;
414
415     regfree(&compiledRegexEgress);
416     regfree(&compiledRegexComment);
417     free(line);
418
419     started = false;
420   }
421 }
422
423 /*
424  * File Reader
425  */
426
427 /** the buffer with regex matches */
428 static regmatch_t pmatch[REGEX_EGRESS_LINE_MATCH_COUNT];
429
430 static void readEgressFileClear(void) {
431   struct sgw_egress_if * egress_if = olsr_cnf->smart_gw_egress_interfaces;
432   while (egress_if) {
433     egress_if->bwPrevious = egress_if->bwCurrent;
434     egress_if->bwCostsChanged = false;
435     egress_if->bwNetworkChanged = false;
436     egress_if->bwGatewayChanged = false;
437     egress_if->bwChanged = false;
438
439     egress_if->inEgressFile = false;
440
441     egress_if = egress_if->next;
442   }
443 }
444
445 /**
446  * Read the egress file
447  *
448  * @param fileName the filename
449  * @return true to indicate changes (any egress_if->bwChanged is true)
450  */
451 static bool readEgressFile(const char * fileName) {
452   int fd;
453   struct stat statBuf;
454   FILE * fp = NULL;
455   void * mtim;
456   unsigned int lineNumber = 0;
457
458   bool changed = false;
459   ssize_t length = -1;
460   bool reportedErrorsLocal = false;
461   const char * filepath = !fileName ? DEF_GW_EGRESS_FILE : fileName;
462
463   fd = open(filepath, O_RDONLY);
464   if (fd < 0) {
465     /* could not open the file */
466     memset(&cachedStat.timeStamp, 0, sizeof(cachedStat.timeStamp));
467     readEgressFileClear();
468     goto outerror;
469   }
470
471   if (fstat(fd, &statBuf)) {
472     /* could not stat the file */
473     memset(&cachedStat.timeStamp, 0, sizeof(cachedStat.timeStamp));
474     readEgressFileClear();
475     goto outerror;
476   }
477
478 #if defined(__linux__) && !defined(__ANDROID__)
479   mtim = &statBuf.st_mtim;
480 #else
481   mtim = &statBuf.st_mtime;
482 #endif
483
484   if (!memcmp(&cachedStat.timeStamp, mtim, sizeof(cachedStat.timeStamp))) {
485     /* file did not change since last read */
486     goto out;
487   }
488
489   fp = fdopen(fd, "r");
490   if (!fp) {
491     /* could not open the file */
492     goto out;
493   }
494
495   memcpy(&cachedStat.timeStamp, mtim, sizeof(cachedStat.timeStamp));
496
497   /* copy 'current' egress interfaces into 'previous' field */
498   readEgressFileClear();
499
500   while (fgets(line, LINE_LENGTH, fp)) {
501     struct sgw_egress_if * egress_if = NULL;
502     unsigned long long uplink = DEF_EGRESS_UPLINK_KBPS;
503     unsigned long long downlink = DEF_EGRESS_DOWNLINK_KBPS;
504     unsigned long long pathCosts = DEF_EGRESS_PATH_COSTS;
505     struct olsr_ip_prefix network;
506     union olsr_ip_addr gateway;
507     bool networkSet = false;
508     bool gatewaySet = false;
509     int networkIpVersion = AF_INET;
510     int gatewayIpVersion = AF_INET;
511
512     lineNumber++;
513
514     if (!regexec(&compiledRegexComment, line, 0, NULL, 0)) {
515       /* the line is a comment */
516       continue;
517     }
518
519     memset(&network, 0, sizeof(network));
520     memset(&gateway, 0, sizeof(gateway));
521
522     stripEols(line, length);
523
524     memset(pmatch, 0, sizeof(pmatch));
525     if (regexec(&compiledRegexEgress, line, REGEX_EGRESS_LINE_MATCH_COUNT, pmatch, 0)) {
526       egressFileError(false, __LINE__, "Egress speed file line %d uses invalid syntax: line is ignored (%s)", lineNumber, line);
527       reportedErrorsLocal = true;
528       continue;
529     }
530
531     /* iface: mandatory presence, guaranteed through regex match */
532     {
533       regoff_t len = pmatch[1].rm_eo - pmatch[1].rm_so;
534       char * ifaceString = &line[pmatch[1].rm_so];
535       line[pmatch[1].rm_eo] = '\0';
536
537       if (len > IFNAMSIZ) {
538         /* interface name is too long */
539         egressFileError(false, __LINE__, "Egress speed file line %d: interface \"%s\" is too long: line is ignored", lineNumber, ifaceString);
540         reportedErrorsLocal = true;
541         continue;
542       }
543
544       egress_if = findEgressInterface(ifaceString);
545       if (!egress_if) {
546         /* not a known egress interface */
547         egressFileError(false, __LINE__, "Egress speed file line %d: interface \"%s\" is not a configured egress interface: line is ignored", lineNumber,
548             ifaceString);
549         reportedErrorsLocal = true;
550         continue;
551       }
552     }
553     assert(egress_if);
554
555     /* uplink: mandatory presence, guaranteed through regex match */
556     {
557       regoff_t len = pmatch[2].rm_eo - pmatch[2].rm_so;
558       char * uplinkString = &line[pmatch[2].rm_so];
559       line[pmatch[2].rm_eo] = '\0';
560
561       if ((len > 0) && !readULL(uplinkString, &uplink)) {
562         egressFileError(false, __LINE__, "Egress speed file line %d: uplink bandwidth \"%s\" is not a valid number: line is ignored", lineNumber, uplinkString);
563         reportedErrorsLocal = true;
564         continue;
565       }
566     }
567     uplink = MIN(uplink, MAX_SMARTGW_SPEED);
568
569     /* downlink: mandatory presence, guaranteed through regex match */
570     {
571       regoff_t len = pmatch[3].rm_eo - pmatch[3].rm_so;
572       char * downlinkString = &line[pmatch[3].rm_so];
573       line[pmatch[3].rm_eo] = '\0';
574
575       if ((len > 0) && !readULL(downlinkString, &downlink)) {
576         egressFileError(false, __LINE__, "Egress speed file line %d: downlink bandwidth \"%s\" is not a valid number: line is ignored", lineNumber,
577             downlinkString);
578         reportedErrorsLocal = true;
579         continue;
580       }
581     }
582     downlink = MIN(downlink, MAX_SMARTGW_SPEED);
583
584     /* path costs: optional presence */
585     if (pmatch[5].rm_so != -1) {
586       regoff_t len = pmatch[5].rm_eo - pmatch[5].rm_so;
587       char * pathCostsString = &line[pmatch[5].rm_so];
588       line[pmatch[5].rm_eo] = '\0';
589
590       if ((len > 0) && !readULL(pathCostsString, &pathCosts)) {
591         egressFileError(false, __LINE__, "Egress speed file line %d: path costs \"%s\" is not a valid number: line is ignored", lineNumber, pathCostsString);
592         reportedErrorsLocal = true;
593         continue;
594       }
595     }
596     pathCosts = MIN(pathCosts, UINT32_MAX);
597
598     /* network: optional presence */
599     if ((pmatch[7].rm_so != -1) && ((pmatch[7].rm_eo - pmatch[7].rm_so) > 0)) {
600       /* network is present: guarantees IP and prefix presence */
601       unsigned long long prefix_len;
602       char * networkString = &line[pmatch[8].rm_so];
603       char * prefixlenString = &line[pmatch[9].rm_so];
604       line[pmatch[8].rm_eo] = '\0';
605       line[pmatch[9].rm_eo] = '\0';
606
607       if (!readIPAddress(networkString, &network.prefix, &networkSet, &networkIpVersion)) {
608         egressFileError(false, __LINE__, "Egress speed file line %d: network IP address \"%s\" is not a valid IP address: line is ignored", lineNumber,
609             networkString);
610         reportedErrorsLocal = true;
611         continue;
612       }
613
614       if (!readULL(prefixlenString, &prefix_len)) {
615         egressFileError(false, __LINE__, "Egress speed file line %d: network prefix \"%s\" is not a valid number: line is ignored", lineNumber,
616             prefixlenString);
617         reportedErrorsLocal = true;
618         continue;
619       }
620
621       if (prefix_len > ((networkIpVersion == AF_INET) ? 32 : 128)) {
622         egressFileError(false, __LINE__, "Egress speed file line %d: network prefix \"%s\" is not in the range [0, %d]: line is ignored", lineNumber,
623             prefixlenString, ((networkIpVersion == AF_INET) ? 32 : 128));
624         reportedErrorsLocal = true;
625         continue;
626       }
627
628       network.prefix_len = prefix_len;
629     }
630
631     /* gateway: optional presence */
632     if (pmatch[11].rm_so != -1) {
633       regoff_t len = pmatch[11].rm_eo - pmatch[11].rm_so;
634       char * gatewayString = &line[pmatch[11].rm_so];
635       line[pmatch[11].rm_eo] = '\0';
636
637       if ((len > 0) && !readIPAddress(gatewayString, &gateway, &gatewaySet, &gatewayIpVersion)) {
638         egressFileError(false, __LINE__, "Egress speed file line %d: gateway IP address \"%s\" is not a valid IP address: line is ignored", lineNumber,
639             gatewayString);
640         reportedErrorsLocal = true;
641         continue;
642       }
643     }
644
645     /* check all IP versions are the same */
646     if ((networkSet && gatewaySet) && (networkIpVersion != gatewayIpVersion)) {
647       egressFileError(false, __LINE__, "Egress speed file line %d: network and gateway IP addresses must be of the same IP version: line is ignored",
648           lineNumber);
649       reportedErrorsLocal = true;
650       continue;
651     }
652
653     /* check no IPv6 */
654     if ((networkSet && networkIpVersion == AF_INET6) || //
655         (gatewaySet && gatewayIpVersion == AF_INET6)) {
656       egressFileError(false, __LINE__, "Egress speed file line %d: network and gateway IP addresses must not be IPv6 addresses: line is ignored", lineNumber);
657       reportedErrorsLocal = true;
658       continue;
659     }
660
661     /* ensure network is masked by netmask */
662     if (networkSet) {
663       /* assumes IPv4 */
664       in_addr_t mask = (network.prefix_len == 0) ? 0 : (~0U << (32 - network.prefix_len));
665       uint32_t masked = ntohl(network.prefix.v4.s_addr) & mask;
666       network.prefix.v4.s_addr = htonl(masked);
667     }
668
669     if (!uplink || !downlink) {
670       egressBwClear(&egress_if->bwCurrent, egress_if->upCurrent);
671     } else {
672       egress_if->bwCurrent.egressUk = uplink;
673       egress_if->bwCurrent.egressDk = downlink;
674       egress_if->bwCurrent.path_cost = pathCosts;
675       egress_if->bwCurrent.network = network;
676       egress_if->bwCurrent.gateway = gateway;
677
678       egress_if->bwCurrent.networkSet = networkSet;
679       egress_if->bwCurrent.gatewaySet = gatewaySet;
680
681       egressBwCalculateCosts(&egress_if->bwCurrent, egress_if->upCurrent);
682     }
683
684     egress_if->inEgressFile = true;
685   }
686
687   fclose(fp);
688   fp = NULL;
689
690   reportedErrors = reportedErrorsLocal;
691
692   outerror:
693
694   /* clear absent egress interfaces and setup 'changed' status */
695   {
696     struct sgw_egress_if * egress_if = olsr_cnf->smart_gw_egress_interfaces;
697     while (egress_if) {
698       if (!egress_if->inEgressFile) {
699         egressBwClear(&egress_if->bwCurrent, egress_if->upCurrent);
700       }
701
702       egress_if->bwCostsChanged = egressBwCostsChanged(egress_if);
703       egress_if->bwNetworkChanged = egressBwNetworkChanged(egress_if);
704       egress_if->bwGatewayChanged = egressBwGatewayChanged(egress_if);
705       egress_if->bwChanged = egressBwChanged(egress_if);
706       if (egress_if->bwChanged) {
707         changed = true;
708       }
709
710       egress_if = egress_if->next;
711     }
712   }
713
714   out: if (fp) {
715     fclose(fp);
716   }
717   if (fd >= 0) {
718     close(fd);
719   }
720   return changed;
721 }
722
723 #endif /* __linux__ */