linkd

Control plane daemon for unos
git clone git://git.finwo.net/app/linkd
Log | Files | Refs | README

linkd.c (11785B)


      1 #define _GNU_SOURCE
      2 
      3 #include <signal.h>
      4 #include <errno.h>
      5 #include <net/if.h>
      6 #include <stdio.h>
      7 #include <stdlib.h>
      8 #include <string.h>
      9 #include <strings.h>
     10 #include <unistd.h>
     11 
     12 #ifdef __cplusplus
     13 extern "C" {
     14 #endif
     15 
     16 #include "cofyc/argparse.h"
     17 #include "rxi/log.h"
     18 
     19 #include "config/daemon.h"
     20 #include "config/ports.h"
     21 #include "config/ifaces.h"
     22 #include "dataplane.h"
     23 #include "dataplane/plugin.h"
     24 #include "dhcp.h"
     25 #include "ipc.h"
     26 #include "netlink/netlink.h"
     27 #include "netlink/rtnl.h"
     28 #include "util/config.h"
     29 #include "cli/registry.h"
     30 
     31 static const char *const usage[] = {
     32   __NAME " [options]",
     33   __NAME " --help",
     34   NULL,
     35 };
     36 
     37 static FILE *log_file;
     38 static char *log_path;
     39 static volatile sig_atomic_t sighup_received;
     40 
     41 static void logfile_callback(log_Event *ev) {
     42   if (sighup_received) {
     43     sighup_received = 0;
     44     if (log_path && log_file) {
     45       fclose(log_file);
     46       log_file = fopen(log_path, "a");
     47     }
     48   }
     49   if (log_file) {
     50     char buf[64];
     51     buf[strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", ev->time)] = '\0';
     52     fprintf(log_file, "%s %-5s %s:%d: ", buf, log_level_string(ev->level), ev->file, ev->line);
     53     vfprintf(log_file, ev->fmt, ev->ap);
     54     fprintf(log_file, "\n");
     55     fflush(log_file);
     56   }
     57 }
     58 
     59 static void sighup_handler(int sig) {
     60   (void)sig;
     61   sighup_received = 1;
     62 }
     63 
     64 static void stop_handler(int sig) {
     65   (void)sig;
     66   nl_request_stop();
     67 }
     68 
     69 static int apply_ports(void) {
     70   struct linkd_port *cur;
     71   struct dp_port     port;
     72 
     73   for ( cur = ports_list() ; cur ; cur = cur->next ) {
     74     memset(&port, 0, sizeof(port));
     75     port.name    = cur->name;
     76     port.speed   = cur->speed;
     77     port.fec     = cur->fec;
     78     port.autoneg = cur->autoneg;
     79 
     80     if (dp_port_apply(&port) != DP_RET_OK) {
     81       log_warn("%s: failed to apply port configuration", cur->name);
     82     }
     83   }
     84 
     85   return 0;
     86 }
     87 
     88 // Applies the already-parsed in-memory config (ifaces_list()); nothing here
     89 // reads the filesystem. Two ordered phases, because interfaces depend on each
     90 // other: a member cannot join a VRF that does not exist yet, and members are
     91 // routinely declared first -- the production Cumulus config we captured has
     92 // `iface eth0` carrying `vrf mgmt` above the `iface mgmt` stanza that defines
     93 // it. Bridges need no such phase: membership is declared on the bridge
     94 // (`bridge-ports`), not on the member.
     95 static int apply_interfaces(void) {
     96   struct linkd_iface *cur;
     97 
     98   // Phase 1: bring every VRF device into existence.
     99   for (cur = ifaces_list(); cur; cur = cur->next) {
    100     if (!cur->auto_flag) continue;
    101     if (!iface_is_vrf(cur)) continue;
    102     log_info("apply_interfaces: vrf %s table %d", cur->name, cur->vrf_table);
    103     rtnl_vrf_create(cur->name, (uint32_t)cur->vrf_table);
    104     rtnl_link_up(cur->name);
    105   }
    106 
    107   // Phase 2: apply every interface, VRF devices included (create is
    108   // idempotent -- rtnl_vrf_create returns early when the device exists).
    109   for (cur = ifaces_list(); cur; cur = cur->next) {
    110     if (!cur->auto_flag) continue;
    111     if (cur->pre_up) {
    112       log_info("apply_interfaces: pre-up %s: %s", cur->name, cur->pre_up);
    113       if (system(cur->pre_up) != 0) {
    114         log_warn("pre-up for %s failed", cur->name);
    115         continue;
    116       }
    117     }
    118     if (cur->vlan_id >= 0) {
    119       const char *raw = cur->vlan_raw_device;
    120       char tmp[IF_NAMESIZE];
    121       if (!raw) {
    122         char *dot = strrchr(cur->name, '.');
    123         if (dot) {
    124           size_t len = dot - cur->name;
    125           if (len < sizeof(tmp)) {
    126             memcpy(tmp, cur->name, len);
    127             tmp[len] = '\0';
    128             raw = tmp;
    129           }
    130         }
    131       }
    132       if (raw) {
    133         log_info("apply_interfaces: vlan %s id %d on %s", cur->name, cur->vlan_id, raw);
    134         rtnl_vlan_create(cur->name, raw, cur->vlan_id);
    135       }
    136     }
    137     if (iface_is_bridge(cur)) {
    138       log_info("apply_interfaces: bridge %s ports %s", cur->name, cur->bridge_ports ? cur->bridge_ports : "(none)");
    139       rtnl_bridge_create(cur->name);
    140       if (cur->bridge_stp) {
    141         bool on = !strcasecmp(cur->bridge_stp, "on") || !strcasecmp(cur->bridge_stp, "yes");
    142         rtnl_bridge_set_stp(cur->name, on);
    143       }
    144       if (cur->bridge_vlan_aware >= 0) rtnl_bridge_set_vlan_aware(cur->name, cur->bridge_vlan_aware);
    145       if (cur->bridge_ports && strcasecmp(cur->bridge_ports, "none") != 0) {
    146         char *ports = strdup(cur->bridge_ports);
    147         char *tok = strtok(ports, " \t");
    148         while (tok) {
    149           if (strcasecmp(tok, "none") == 0) { tok = strtok(NULL, " \t"); continue; }
    150           rtnl_bridge_add_port(cur->name, tok);
    151           rtnl_link_up(tok);
    152           tok = strtok(NULL, " \t");
    153         }
    154         free(ports);
    155       }
    156     }
    157     // Enslave to a VRF before bringing the link up and assigning addresses:
    158     // moving an interface into a VRF flushes its addresses, so doing it after
    159     // would silently discard everything we just configured.
    160     if (cur->vrf_master) {
    161       rtnl_vrf_add_port(cur->vrf_master, cur->name);
    162     }
    163     log_info("apply_interfaces: link up %s", cur->name);
    164     rtnl_link_up(cur->name);
    165     if (iface_method_is(cur, "dhcp")) {
    166       if (dhcp_start(cur->name) != 0) log_error("apply_interfaces: dhcp failed for %s", cur->name);
    167     }
    168     // `manual` means the link only; addresses stay the operator's business.
    169     if (!iface_method_is(cur, "manual")) {
    170       for (struct iface_addr *a = cur->addrs; a; a = a->next) {
    171         log_info("apply_interfaces: addr %s dev %s", a->address, cur->name);
    172         rtnl_addr_add(cur->name, a->address, a->netmask);
    173       }
    174     }
    175     if (cur->gateway) {
    176       log_info("apply_interfaces: gateway %s via %s", cur->gateway, cur->name);
    177       rtnl_route_add_default(cur->gateway, cur->name);
    178     }
    179     if (cur->mtu) {
    180       log_info("apply_interfaces: mtu %d dev %s", cur->mtu, cur->name);
    181       rtnl_link_set_mtu(cur->name, cur->mtu);
    182     }
    183     if (cur->hwaddress) {
    184       log_info("apply_interfaces: hwaddress %s dev %s", cur->hwaddress, cur->name);
    185       rtnl_link_set_hwaddr(cur->name, cur->hwaddress);
    186     }
    187     if (cur->post_up) {
    188       log_info("apply_interfaces: post-up %s: %s", cur->name, cur->post_up);
    189       system(cur->post_up);
    190     }
    191   }
    192   return 0;
    193 }
    194 
    195 // Atomic publish: write tmp + rename so frr never observes a half file.
    196 static int ready_publish(const char *path) {
    197   char tmp[1024];
    198   FILE *fd;
    199 
    200   if (!path || !*path) return 0;
    201   snprintf(tmp, sizeof(tmp), "%s.tmp.%d", path, (int)getpid());
    202   fd = fopen(tmp, "w");
    203   if (!fd) {
    204     log_error("ready-file %s: %s", tmp, strerror(errno));
    205     return -1;
    206   }
    207   fprintf(fd, "%d\n", (int)getpid());
    208   fclose(fd);
    209   if (rename(tmp, path) != 0) {
    210     log_error("ready-file rename: %s", strerror(errno));
    211     unlink(tmp);
    212     return -1;
    213   }
    214   return 0;
    215 }
    216 
    217 static void ready_remove(const char *path) {
    218   if (!path || !*path) return;
    219   unlink(path);
    220 }
    221 
    222 int main_linkd(int argc, char **argv) {
    223   // Cast for argparse which wants const char**
    224   const char **c_argv = (const char **)argv;
    225   char *config_path = NULL;
    226   char *loglevel = "info";
    227   char *logfile_path = NULL;
    228   char *ready_file = "";
    229   int   resync_interval = 60;
    230   int   nl_fd = -1;
    231   int   rc = 0;
    232 
    233   struct argparse_option options[] = {
    234     OPT_HELP(),
    235     OPT_STRING('c', "config", &config_path, "Configuration file (default: " LINKD_CONFIG_PATH ")", NULL, 0, 0),
    236     OPT_STRING('v', "verbosity", &loglevel, "log verbosity: fatal,error,warn,info,debug,trace (default: info)", NULL, 0, 0),
    237     OPT_STRING(0, "log", &logfile_path, "also write log to file (SIGHUP reopens for logrotate)", NULL, 0, 0),
    238     OPT_INTEGER(0, "resync-interval", &resync_interval, "seconds between full resyncs (0 disables timer)", NULL, 0, 0),
    239     OPT_STRING(0, "ready-file", &ready_file, "readiness file to publish after initial resync (empty disables)", NULL, 0, 0),
    240     OPT_END(),
    241   };
    242 
    243   // Initialize components
    244   daemon_cfg_register();
    245   ports_register();
    246   ifaces_register();
    247 
    248   struct argparse argparse;
    249   argparse_init(&argparse, options, usage, 0);
    250   argparse_describe(&argparse, NULL,
    251       "\n"
    252       __NAME " programs the forwarding plane from the kernel's routing state.\n"
    253       "Built for target " __TARGET ".\n"
    254   );
    255   argc = argparse_parse(&argparse, argc, c_argv);
    256 
    257   int level = LOG_INFO;
    258   if (0) {
    259     (void)0;
    260   } else if (!strcasecmp(loglevel, "trace")) {
    261     level = LOG_TRACE;
    262   } else if (!strcasecmp(loglevel, "debug")) {
    263     level = LOG_DEBUG;
    264   } else if (!strcasecmp(loglevel, "info")) {
    265     level = LOG_INFO;
    266   } else if (!strcasecmp(loglevel, "warn")) {
    267     level = LOG_WARN;
    268   } else if (!strcasecmp(loglevel, "error")) {
    269     level = LOG_ERROR;
    270   } else if (!strcasecmp(loglevel, "fatal")) {
    271     level = LOG_FATAL;
    272   } else {
    273     fprintf(stderr, "Unknown log level: %s\n", loglevel);
    274     return 1;
    275   }
    276   log_set_level(level);
    277   setvbuf(stderr, NULL, _IOLBF, 0);
    278 
    279   log_file = NULL;
    280   log_path = NULL;
    281   sighup_received = 0;
    282 
    283   if (logfile_path && logfile_path[0]) {
    284     log_path = strdup(logfile_path);
    285     log_file = fopen(log_path, "a");
    286     if (log_file) {
    287       log_add_callback(logfile_callback, log_path, level);
    288     } else {
    289       fprintf(stderr, "Could not open log file: %s\n", logfile_path);
    290       free(log_path);
    291       log_path = NULL;
    292     }
    293   }
    294 
    295   if (resync_interval < 0) {
    296     log_error("--resync-interval must be >= 0");
    297     return 1;
    298   }
    299 
    300   signal(SIGHUP, sighup_handler);
    301   signal(SIGINT, stop_handler);
    302   signal(SIGTERM, stop_handler);
    303   signal(SIGPIPE, SIG_IGN);
    304 
    305   if (daemon_cfg_load(config_path) < 0) {
    306     return 1;
    307   }
    308   const struct linkd_daemon_cfg *dcfg = daemon_cfg();
    309 
    310   if (load_namespace("ports", dcfg->ports, dcfg->ports_n) < 0) {
    311     return 1;
    312   }
    313   if (load_namespace("interfaces", dcfg->iface, dcfg->iface_n) < 0) {
    314     return 1;
    315   }
    316   {
    317     int n = 0;
    318     for (struct linkd_iface *c = ifaces_list(); c; c = c->next) n++;
    319     log_info("main: loaded %d interfaces", n);
    320     for (struct linkd_iface *c = ifaces_list(); c; c = c->next) {
    321       log_info("main: iface %s method %s auto %d addrs %s", c->name, c->method ? c->method : "(null)", c->auto_flag, c->addrs ? c->addrs->address : "(none)");
    322     }
    323   }
    324 
    325   // Startup order is load-bearing: ports -> plugins -> port_apply -> nl_open
    326   // -> initial resync -> ready -> event loop. frr must not start before the
    327   // ready file exists or zebra misses the swpN netdevs.
    328   //
    329   // A plugin that fails to start is not fatal: the backoff retries it, and
    330   // with none configured every dataplane call is a no-op.
    331   dp_start();
    332 
    333   if (ipc_init() != 0) {
    334     log_warn("ipc: init failed, ifup/ifquery will not be available");
    335   }
    336 
    337   apply_ports();
    338   // Interfaces are applied via netlink/system ip after ports, before resync
    339   // so that the subsequent nl_resync sees them.
    340 
    341   nl_fd = nl_open();
    342   if (nl_fd < 0) {
    343     dp_stop();
    344     ports_free();
    345     ifaces_free();
    346     ipc_fini();
    347     return 1;
    348   }
    349 
    350   // After nl_open so the resync below observes what this creates.
    351   apply_interfaces();
    352 
    353   // Fail loud: never publish readiness on partial state; runit retries.
    354   if (nl_resync(nl_fd) != 0) {
    355     log_fatal("netlink: initial resync failed");
    356     close(nl_fd);
    357     ipc_fini();
    358     dp_stop();
    359     ports_free();
    360     ifaces_free();
    361     return 1;
    362   }
    363 
    364   if (ready_file && *ready_file) {
    365     if (ready_publish(ready_file) != 0) {
    366       close(nl_fd);
    367       ipc_fini();
    368       dp_stop();
    369       ports_free();
    370       ifaces_free();
    371       return 1;
    372     }
    373     log_info("ready: published %s", ready_file);
    374   }
    375 
    376   rc = nl_run(nl_fd, resync_interval);
    377   close(nl_fd);
    378   ipc_fini();
    379 
    380   ready_remove(ready_file);
    381   dhcp_stop_all();
    382   dp_stop();
    383   ports_free();
    384   ifaces_free();
    385 
    386   if (log_file) fclose(log_file);
    387   free(log_path);
    388 
    389   return rc < 0 ? 1 : 0;
    390 }
    391 
    392 __attribute__((constructor))
    393 static void register_linkd(void) {
    394   cli_register("linkd", main_linkd);
    395 }
    396 
    397 #ifdef __cplusplus
    398 } // extern "C"
    399 #endif