linkd

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

linkd.c (11516B)


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