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