sgw: track the up status of an egress interface
[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  * Find an egress interface in the configuration by if_index
263  *
264  * @param if_index the index of the egress interface
265  * @return the pointer to the egress interface, NULL when not found
266  */
267 struct sgw_egress_if * findEgressInterfaceByIndex(int if_index) {
268   if (if_index > 0) {
269     struct sgw_egress_if * egress_if = olsr_cnf->smart_gw_egress_interfaces;
270     while (egress_if) {
271       if (egress_if->if_index == if_index) {
272         return egress_if;
273       }
274       egress_if = egress_if->next;
275     }
276   }
277
278   return NULL ;
279 }
280
281 /**
282  * Calculate the costs from the bandwidth parameters
283  *
284  * @param bw the bandwidth parameters
285  * @param up true when the interface is up
286  * @return true when the costs changed
287  */
288 bool egressBwCalculateCosts(struct egress_if_bw * bw, bool up) {
289   int64_t costsPrevious = bw->costs;
290   bw->costs = gw_costs_weigh(up, gw_costs_weights, bw->path_cost, bw->egressUk, bw->egressDk);
291   return (costsPrevious != bw->costs);
292 }
293
294 /**
295  * Clear the bandwidth parameters
296  * @param bw the bandwidth parameters
297  * @param up true when the interface is up
298  */
299 void egressBwClear(struct egress_if_bw * bw, bool up) {
300   bw->egressUk = 0;
301   bw->egressDk = 0;
302   bw->path_cost = UINT32_MAX;
303   memset(&bw->network, 0, sizeof(bw->network));
304   memset(&bw->gateway, 0, sizeof(bw->gateway));
305
306   bw->networkSet = false;
307   bw->gatewaySet = false;
308
309   egressBwCalculateCosts(bw, up);
310 }
311
312 /*
313  * Timer
314  */
315
316 /** the timer for polling the egress file for changes */
317 static struct timer_entry *egress_file_timer;
318
319 /**
320  * Timer callback to read the egress file
321  *
322  * @param unused unused
323  */
324 static void egress_file_timer_callback(void *unused __attribute__ ((unused))) {
325   if (readEgressFile(olsr_cnf->smart_gw_egress_file)) {
326     // FIXME process changes
327   }
328 }
329
330 /*
331  * Life Cycle
332  */
333
334 /**
335  * Initialises the egress file reader
336  *
337  * @return
338  * - true upon success
339  * - false otherwise
340  */
341 bool startEgressFile(void) {
342   int r;
343
344   if (started) {
345     return true;
346   }
347
348   line = malloc(line_length);
349   if (!line) {
350     egressFileError(false, __LINE__, "Could not allocate a line buffer");
351     return false;
352   }
353   *line = '\0';
354
355   r = regcomp(&compiledRegexComment, regexComment, REG_EXTENDED);
356   if (r) {
357     regerror(r, &compiledRegexComment, line, line_length);
358     egressFileError(false, __LINE__, "Could not compile regex \"%s\" (%d = %s)", regexComment, r, line);
359
360     free(line);
361     line = NULL;
362     return false;
363   }
364
365   r = regcomp(&compiledRegexEgress, regexEgress, REG_EXTENDED);
366   if (r) {
367     regerror(r, &compiledRegexEgress, line, line_length);
368     egressFileError(false, __LINE__, "Could not compile regex \"%s\" (%d = %s)", regexEgress, r, line);
369
370     regfree(&compiledRegexComment);
371     free(line);
372     line = NULL;
373     return false;
374   }
375
376   gw_costs_weights.WexitU = olsr_cnf->smart_gw_weight_exitlink_up;
377   gw_costs_weights.WexitD = olsr_cnf->smart_gw_weight_exitlink_down;
378   gw_costs_weights.Wetx = olsr_cnf->smart_gw_weight_etx;
379   gw_costs_weights.Detx = olsr_cnf->smart_gw_divider_etx;
380
381   cachedStat.tv_sec = -1;
382   cachedStat.tv_nsec = -1;
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 /**
416  * Read the egress file
417  *
418  * @param fileName the filename
419  * @return true to indicate changes (any egress_if->bwChanged is true)
420  */
421 static bool readEgressFile(char * fileName) {
422   bool changed = false;
423
424   int fd;
425   FILE * fp = NULL;
426   struct stat statBuf;
427   unsigned int lineNumber = 0;
428   ssize_t length = -1;
429   bool reportedErrorsLocal = false;
430
431   fd = open(!fileName ? DEF_GW_EGRESS_FILE : fileName, O_RDONLY);
432   if (fd < 0) {
433     /* could not access the file */
434     goto out;
435   }
436
437   if (fstat(fd, &statBuf)) {
438     /* could not stat the file */
439     goto out;
440   }
441
442   if (!memcmp(&cachedStat, &statBuf.st_mtim, sizeof(cachedStat))) {
443     /* file did not change since last read */
444     goto out;
445   }
446
447   fp = fdopen(fd, "r");
448   if (!fp) {
449     /* could not open the file */
450     goto out;
451   }
452
453   /* copy 'current' egress interfaces into 'previous' field */
454   {
455     struct sgw_egress_if * egress_if = olsr_cnf->smart_gw_egress_interfaces;
456     while (egress_if) {
457       egress_if->bwPrevious = egress_if->bwCurrent;
458       egress_if->bwCostsChanged = false;
459       egress_if->bwNetworkChanged = false;
460       egress_if->bwGatewayChanged = false;
461       egress_if->bwChanged = false;
462
463       egress_if->inEgressFile = false;
464
465       egress_if = egress_if->next;
466     }
467   }
468
469   while ((length = getline(&line, &line_length, fp)) != -1) {
470     struct sgw_egress_if * egress_if = NULL;
471     unsigned long long uplink = DEF_EGRESS_UPLINK_KBPS;
472     unsigned long long downlink = DEF_EGRESS_DOWNLINK_KBPS;
473     unsigned long long pathCosts = DEF_EGRESS_PATH_COSTS;
474     struct olsr_ip_prefix network;
475     union olsr_ip_addr gateway;
476     bool networkSet = false;
477     bool gatewaySet = false;
478     int networkIpVersion = AF_INET;
479     int gatewayIpVersion = AF_INET;
480
481     lineNumber++;
482
483     if (!regexec(&compiledRegexComment, line, 0, NULL, 0)) {
484       /* the line is a comment */
485       continue;
486     }
487
488     memset(&network, 0, sizeof(network));
489     memset(&gateway, 0, sizeof(gateway));
490
491     stripEols(line, length);
492
493     memset(pmatch, 0, sizeof(pmatch));
494     if (regexec(&compiledRegexEgress, line, REGEX_EGRESS_LINE_MATCH_COUNT, pmatch, 0)) {
495       egressFileError(false, __LINE__, "Egress speed file line %d uses invalid syntax: line is ignored (%s)", lineNumber, line);
496       reportedErrorsLocal = true;
497       continue;
498     }
499
500     /* iface: mandatory presence, guaranteed through regex match */
501     {
502       regoff_t len = pmatch[1].rm_eo - pmatch[1].rm_so;
503       char * ifaceString = &line[pmatch[1].rm_so];
504       line[pmatch[1].rm_eo] = '\0';
505
506       if (len > IFNAMSIZ) {
507         /* interface name is too long */
508         egressFileError(false, __LINE__, "Egress speed file line %d: interface \"%s\" is too long: line is ignored", lineNumber, ifaceString);
509         reportedErrorsLocal = true;
510         continue;
511       }
512
513       egress_if = findEgressInterface(ifaceString);
514       if (!egress_if) {
515         /* not a known egress interface */
516         egressFileError(false, __LINE__, "Egress speed file line %d: interface \"%s\" is not a configured egress interface: line is ignored", lineNumber,
517             ifaceString);
518         reportedErrorsLocal = true;
519         continue;
520       }
521     }
522     assert(egress_if);
523
524     /* uplink: mandatory presence, guaranteed through regex match */
525     {
526       regoff_t len = pmatch[2].rm_eo - pmatch[2].rm_so;
527       char * uplinkString = &line[pmatch[2].rm_so];
528       line[pmatch[2].rm_eo] = '\0';
529
530       if ((len > 0) && !readULL(uplinkString, &uplink)) {
531         egressFileError(false, __LINE__, "Egress speed file line %d: uplink bandwidth \"%s\" is not a valid number: line is ignored", lineNumber, uplinkString);
532         reportedErrorsLocal = true;
533         continue;
534       }
535     }
536     uplink = MIN(uplink, MAX_SMARTGW_SPEED);
537
538     /* downlink: mandatory presence, guaranteed through regex match */
539     {
540       regoff_t len = pmatch[3].rm_eo - pmatch[3].rm_so;
541       char * downlinkString = &line[pmatch[3].rm_so];
542       line[pmatch[3].rm_eo] = '\0';
543
544       if ((len > 0) && !readULL(downlinkString, &downlink)) {
545         egressFileError(false, __LINE__, "Egress speed file line %d: downlink bandwidth \"%s\" is not a valid number: line is ignored", lineNumber,
546             downlinkString);
547         reportedErrorsLocal = true;
548         continue;
549       }
550     }
551     downlink = MIN(downlink, MAX_SMARTGW_SPEED);
552
553     /* path costs: optional presence */
554     if (pmatch[5].rm_so != -1) {
555       regoff_t len = pmatch[5].rm_eo - pmatch[5].rm_so;
556       char * pathCostsString = &line[pmatch[5].rm_so];
557       line[pmatch[5].rm_eo] = '\0';
558
559       if ((len > 0) && !readULL(pathCostsString, &pathCosts)) {
560         egressFileError(false, __LINE__, "Egress speed file line %d: path costs \"%s\" is not a valid number: line is ignored", lineNumber, pathCostsString);
561         reportedErrorsLocal = true;
562         continue;
563       }
564     }
565     pathCosts = MIN(pathCosts, UINT32_MAX);
566
567     /* network: optional presence */
568     if ((pmatch[7].rm_so != -1) && ((pmatch[7].rm_eo - pmatch[7].rm_so) > 0)) {
569       /* network is present: guarantees IP and prefix presence */
570       unsigned long long prefix_len;
571       char * networkString = &line[pmatch[8].rm_so];
572       char * prefixlenString = &line[pmatch[9].rm_so];
573       line[pmatch[8].rm_eo] = '\0';
574       line[pmatch[9].rm_eo] = '\0';
575
576       if (!readIPAddress(networkString, &network.prefix, &networkSet, &networkIpVersion)) {
577         egressFileError(false, __LINE__, "Egress speed file line %d: network IP address \"%s\" is not a valid IP address: line is ignored", lineNumber,
578             networkString);
579         reportedErrorsLocal = true;
580         continue;
581       }
582
583       if (!readULL(prefixlenString, &prefix_len)) {
584         egressFileError(false, __LINE__, "Egress speed file line %d: network prefix \"%s\" is not a valid number: line is ignored", lineNumber,
585             prefixlenString);
586         reportedErrorsLocal = true;
587         continue;
588       }
589
590       if (prefix_len > ((networkIpVersion == AF_INET) ? 32 : 128)) {
591         egressFileError(false, __LINE__, "Egress speed file line %d: network prefix \"%s\" is not in the range [0, %d]: line is ignored", lineNumber,
592             prefixlenString, ((networkIpVersion == AF_INET) ? 32 : 128));
593         reportedErrorsLocal = true;
594         continue;
595       }
596
597       network.prefix_len = prefix_len;
598     }
599
600     /* gateway: optional presence */
601     if (pmatch[11].rm_so != -1) {
602       regoff_t len = pmatch[11].rm_eo - pmatch[11].rm_so;
603       char * gatewayString = &line[pmatch[11].rm_so];
604       line[pmatch[11].rm_eo] = '\0';
605
606       if ((len > 0) && !readIPAddress(gatewayString, &gateway, &gatewaySet, &gatewayIpVersion)) {
607         egressFileError(false, __LINE__, "Egress speed file line %d: gateway IP address \"%s\" is not a valid IP address: line is ignored", lineNumber,
608             gatewayString);
609         reportedErrorsLocal = true;
610         continue;
611       }
612     }
613
614     /* check all IP versions are the same */
615     if ((networkSet && gatewaySet) && (networkIpVersion != gatewayIpVersion)) {
616       egressFileError(false, __LINE__, "Egress speed file line %d: network and gateway IP addresses must be of the same IP version: line is ignored",
617           lineNumber);
618       reportedErrorsLocal = true;
619       continue;
620     }
621
622     /* check no IPv6 */
623     if ((networkSet && networkIpVersion == AF_INET6) || //
624         (gatewaySet && gatewayIpVersion == AF_INET6)) {
625       egressFileError(false, __LINE__, "Egress speed file line %d: network and gateway IP addresses must not be IPv6 addresses: line is ignored", lineNumber);
626       reportedErrorsLocal = true;
627       continue;
628     }
629
630     /* ensure network is masked by netmask */
631     if (networkSet) {
632       /* assumes IPv4 */
633       in_addr_t mask = (network.prefix_len == 0) ? 0 : (~0U << (32 - network.prefix_len));
634       network.prefix.v4.s_addr = htonl(ntohl(network.prefix.v4.s_addr) & mask);
635     }
636
637     if (!uplink || !downlink) {
638       egressBwClear(&egress_if->bwCurrent, egress_if->upCurrent);
639     } else {
640       egress_if->bwCurrent.egressUk = uplink;
641       egress_if->bwCurrent.egressDk = downlink;
642       egress_if->bwCurrent.path_cost = pathCosts;
643       egress_if->bwCurrent.network = network;
644       egress_if->bwCurrent.gateway = gateway;
645
646       egress_if->bwCurrent.networkSet = networkSet;
647       egress_if->bwCurrent.gatewaySet = gatewaySet;
648
649       egressBwCalculateCosts(&egress_if->bwCurrent, egress_if->upCurrent);
650     }
651
652     egress_if->inEgressFile = true;
653   }
654
655   fclose(fp);
656   fp = NULL;
657
658   cachedStat = statBuf.st_mtim;
659
660   reportedErrors = reportedErrorsLocal;
661
662   /* clear absent egress interfaces and setup 'changed' status */
663   {
664     struct sgw_egress_if * egress_if = olsr_cnf->smart_gw_egress_interfaces;
665     while (egress_if) {
666       if (!egress_if->inEgressFile) {
667         egressBwClear(&egress_if->bwCurrent, egress_if->upCurrent);
668       }
669
670       egress_if->bwCostsChanged = egressBwCostsChanged(egress_if);
671       egress_if->bwNetworkChanged = egressBwNetworkChanged(egress_if);
672       egress_if->bwGatewayChanged = egressBwGatewayChanged(egress_if);
673       egress_if->bwChanged = egressBwChanged(egress_if);
674       if (egress_if->bwChanged) {
675         changed = true;
676       }
677
678       egress_if = egress_if->next;
679     }
680   }
681
682   out: if (fd >= 0) {
683     close(fd);
684     fd = -1;
685   }
686
687   return changed;
688 }
689
690 #endif /* __linux__ */