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