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