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