ifaces.c (17140B)
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <strings.h> 5 6 #include "finwo/cnfparse.h" 7 8 #include "util/config.h" 9 10 #include "ifaces.h" 11 12 static struct linkd_iface *ifaces = NULL; 13 static struct linkd_iface *ifaces_tail = NULL; 14 15 // Allocate an iface with every "unset" field at its sentinel. 16 // 17 // calloc() zeroes, which is the correct "unset" for pointers and for counters 18 // like mtu, but WRONG for any field whose valid range includes 0. Those use -1 19 // as the sentinel and must be set explicitly. 20 // 21 // This exists because bridge_vlan_aware was documented as "-1 unset" but never 22 // initialised, so it sat at 0 and the `bridge_vlan_aware >= 0` test classified 23 // EVERY interface as a bridge. It was masked only because creating a bridge on 24 // an existing device fails harmlessly. Centralising the sentinels here means a 25 // new tri-state field cannot repeat that by being forgotten at one of the two 26 // allocation sites. 27 static struct linkd_iface * iface_new(const char *name) { 28 struct linkd_iface *ifc = calloc(1, sizeof(*ifc)); 29 if (!ifc) return NULL; 30 ifc->name = name ? strdup(name) : NULL; 31 ifc->vlan_id = -1; 32 ifc->bridge_vlan_aware = -1; 33 ifc->vrf_table = -1; 34 return ifc; 35 } 36 37 // Is this interface a bridge? 38 // 39 // Single source of truth: this was duplicated in cli/linkd.c and ipc.c, which 40 // is how the two could drift. Explicit bridge-* directives win; the name 41 // prefix is only a fallback, and never applies to something already declared 42 // a VLAN or enslaved to a VRF. 43 bool iface_is_bridge(const struct linkd_iface *ifc) { 44 if (!ifc) return false; 45 if (ifc->bridge_ports) return true; 46 if (ifc->bridge_stp) return true; 47 if (ifc->bridge_vlan_aware >= 0) return true; 48 if (ifc->vlan_id >= 0) return false; 49 if (ifc->vrf_table >= 0) return false; 50 return !strncmp(ifc->name, "br", 2); 51 } 52 53 // Is this interface a VRF? `vrf-table` makes it one; `vrf <name>` makes it a 54 // MEMBER of one, which is a different thing entirely. 55 bool iface_is_vrf(const struct linkd_iface *ifc) { 56 return ifc && ifc->vrf_table >= 0; 57 } 58 59 // `vrf-table auto`: lowest free id at or above VRF_TABLE_AUTO_BASE. 60 // 61 // 1001 is not arbitrary -- it is where Cumulus starts, and the S5248F-ON we 62 // captured has its `mgmt` VRF on exactly table 1001 (PROGRESS.md 2.1). Matching 63 // that means a config moved off a Cumulus box lands on the same tables. 64 // Deliberately skips 253/254/255 by construction, and skips any id already 65 // claimed explicitly, so `auto` and a hardcoded id cannot collide. 66 #define VRF_TABLE_AUTO_BASE 1001 67 static int vrf_table_auto(const char *name) { 68 struct linkd_iface *cur; 69 int candidate; 70 for (candidate = VRF_TABLE_AUTO_BASE; candidate < 2147483647; candidate++) { 71 int taken = 0; 72 for (cur = ifaces; cur; cur = cur->next) { 73 // An interface re-parsing its own stanza must keep its table. 74 if (name && cur->name && !strcmp(cur->name, name)) continue; 75 if (cur->vrf_table == candidate) { taken = 1; break; } 76 } 77 if (!taken) return candidate; 78 } 79 return -1; 80 } 81 82 struct linkd_iface * ifaces_list(void) { 83 return ifaces; 84 } 85 86 struct linkd_iface * ifaces_find(const char *name) { 87 struct linkd_iface *cur; 88 for (cur = ifaces; cur; cur = cur->next) { 89 if (!strcmp(cur->name, name)) return cur; 90 } 91 return NULL; 92 } 93 94 void ifaces_set_auto(const char *name, int flag) { 95 struct linkd_iface *cur = ifaces_find(name); 96 if (cur) { 97 cur->auto_flag = flag; 98 return; 99 } 100 // Create placeholder for auto without iface block yet (common in Debian: auto comes before iface) 101 cur = iface_new(name); 102 cur->auto_flag = flag; 103 if (ifaces_tail) { 104 ifaces_tail->next = cur; 105 } else { 106 ifaces = cur; 107 } 108 ifaces_tail = cur; 109 } 110 111 void ifaces_free(void) { 112 struct linkd_iface *cur = ifaces; 113 struct linkd_iface *next; 114 struct iface_addr *a, *anext; 115 while (cur) { 116 next = cur->next; 117 free(cur->name); 118 free(cur->method); 119 free(cur->family); 120 free(cur->gateway); 121 free(cur->broadcast); 122 free(cur->hwaddress); 123 free(cur->vlan_raw_device); 124 free(cur->pre_up); 125 free(cur->post_up); 126 free(cur->pre_down); 127 free(cur->post_down); 128 free(cur->bridge_ports); 129 free(cur->bridge_stp); 130 free(cur->vrf_master); 131 for (a = cur->addrs; a; a = anext) { 132 anext = a->next; 133 free(a->address); 134 free(a->netmask); 135 free(a); 136 } 137 free(cur); 138 cur = next; 139 } 140 ifaces = NULL; 141 ifaces_tail = NULL; 142 } 143 144 static void iface_add_addr(struct linkd_iface *iface, const char *addr, const char *netmask) { 145 struct iface_addr *a = calloc(1, sizeof(*a)); 146 a->address = strdup(addr); 147 if (netmask) a->netmask = strdup(netmask); 148 a->next = NULL; 149 // append 150 if (!iface->addrs) { 151 iface->addrs = a; 152 } else { 153 struct iface_addr *cur = iface->addrs; 154 while (cur->next) cur = cur->next; 155 cur->next = a; 156 } 157 } 158 159 // auto <if> [<if> ...] 160 struct cnf_directive * cfg_parse_auto(FILE *fd, struct cnf_directive *dir, void *user) { 161 (void)fd; (void)user; 162 int i; 163 for (i = 0; i < (int)dir->argc; i++) { 164 ifaces_set_auto(dir->argv[i], 1); 165 } 166 cnf_directive_free(dir); 167 return NULL; 168 } 169 170 // iface <name> [inet|inet6 <method>] -- cumulus style `iface <name>` preferred (no inet) 171 struct cnf_directive * cfg_parse_iface(FILE *fd, struct cnf_directive *dir, void *user) { 172 struct linkd_iface *iface; 173 (void)user; 174 175 if (dir->argc < 1 || dir->argc > 3) { 176 cfg_error("`iface` directive: expected `iface <name>` or `iface <name> inet <method>`"); 177 goto fail; 178 } 179 180 const char *name = dir->argv[0]; 181 const char *family = NULL; 182 const char *method = NULL; 183 184 if (dir->argc == 1) { 185 // Cumulus style: iface <name> -- defaults to static, no family 186 method = "static"; 187 } else if (dir->argc == 3) { 188 // Traditional: iface <name> inet static 189 family = dir->argv[1]; 190 method = dir->argv[2]; 191 if (strcasecmp(family, "inet") && strcasecmp(family, "inet6")) { 192 cfg_error("`iface %s` unknown family: %s (expected inet/inet6)", name, family); 193 goto fail; 194 } 195 } else { 196 cfg_error("`iface` directive: expected `iface <name>` or `iface <name> inet <method>`"); 197 goto fail; 198 } 199 200 // Find existing placeholder from auto, or create new 201 iface = ifaces_find(name); 202 int was_placeholder = 0; 203 if (iface && !iface->method && !iface->family && !iface->addrs) { 204 // placeholder from auto -- reuse 205 was_placeholder = 1; 206 } else if (iface) { 207 // Duplicate iface block -- for now we merge? Cumulus allows multiple iface stanzas for same if 208 // but we treat as error to keep simple, unless it's same name with different family 209 // Allow merging: if family/method differ, we keep as separate? For now, error on exact duplicate 210 // Check if same iface already has addrs/method -- if so, create new entry with same name but as separate node 211 // To keep diff-apply simple, we allow multiple entries with same name as separate list nodes 212 // So we will create a new node even if name exists, unless it was placeholder 213 was_placeholder = 0; 214 } 215 216 if (iface && was_placeholder) { 217 // reuse placeholder 218 free(iface->method); 219 free(iface->family); 220 iface->method = method ? strdup(method) : NULL; 221 iface->family = family ? strdup(family) : NULL; 222 // keep auto_flag 223 } else { 224 // create new 225 iface = iface_new(name); 226 iface->method = method ? strdup(method) : NULL; 227 iface->family = family ? strdup(family) : NULL; 228 // check if auto was previously set for this name via placeholder? Already handled, but if not placeholder, check if any placeholder had auto 229 // Actually ifaces_set_auto may have created placeholder with auto, we already handled. 230 // If no placeholder, auto_flag stays 0 until auto directive is parsed (order may be auto before or after iface) 231 // So we need to check if there was a prior auto for this name that wasn't yet linked -- but our placeholder logic already covers. 232 if (ifaces_tail) { 233 ifaces_tail->next = iface; 234 } else { 235 ifaces = iface; 236 } 237 ifaces_tail = iface; 238 } 239 240 // Handle vlan dot notation: swp1.100 -> raw_device swp1, vlan_id 100 241 char *dot = strrchr(iface->name, '.'); 242 if (dot && !iface->vlan_raw_device) { 243 char *end = NULL; 244 long vid = strtol(dot+1, &end, 10); 245 if (end != dot+1 && *end == '\0' && vid >= 1 && vid <= 4094) { 246 iface->vlan_id = (int)vid; 247 // Only set raw_device if not already set via explicit vlan-raw-device 248 // Keep as is, will be resolved later if needed 249 } 250 } 251 252 for(;;) { 253 if (dir) cnf_directive_free(dir); 254 dir = cnf_directive_read(fd); 255 if (!dir) break; 256 257 if(0) {} 258 else if (!strcasecmp("address", dir->name)) { 259 if (dir->argc < 1 || dir->argc > 2) { 260 cfg_error("`address` expects 1 or 2 args (address + optional netmask)"); 261 goto fail; 262 } 263 const char *addr = dir->argv[0]; 264 const char *nm = dir->argc > 1 ? dir->argv[1] : NULL; 265 iface_add_addr(iface, addr, nm); 266 } 267 else if (!strcasecmp("netmask", dir->name)) { 268 if (dir->argc != 1) { 269 cfg_error("`netmask` expects 1 arg"); 270 goto fail; 271 } 272 // Attach netmask to last address without netmask 273 struct iface_addr *a = iface->addrs; 274 if (!a) { 275 cfg_error("`netmask` without preceding `address`"); 276 goto fail; 277 } 278 while (a->next) a = a->next; 279 free(a->netmask); 280 a->netmask = strdup(dir->argv[0]); 281 } 282 else if (!strcasecmp("broadcast", dir->name)) { 283 if (dir->argc != 1) { cfg_error("`broadcast` expects 1 arg"); goto fail; } 284 free(iface->broadcast); 285 iface->broadcast = strdup(dir->argv[0]); 286 } 287 else if (!strcasecmp("gateway", dir->name)) { 288 if (dir->argc != 1) { cfg_error("`gateway` expects 1 arg"); goto fail; } 289 free(iface->gateway); 290 iface->gateway = strdup(dir->argv[0]); 291 } 292 else if (!strcasecmp("mtu", dir->name)) { 293 if (dir->argc != 1) { cfg_error("`mtu` expects 1 arg"); goto fail; } 294 iface->mtu = atoi(dir->argv[0]); 295 } 296 else if (!strcasecmp("hwaddress", dir->name) || !strcasecmp("hw-address", dir->name)) { 297 if (dir->argc != 1) { cfg_error("`hwaddress` expects 1 arg"); goto fail; } 298 free(iface->hwaddress); 299 iface->hwaddress = strdup(dir->argv[0]); 300 } 301 else if (!strcasecmp("vlan-raw-device", dir->name) || !strcasecmp("vlan_raw_device", dir->name)) { 302 if (dir->argc != 1) { cfg_error("`vlan-raw-device` expects 1 arg"); goto fail; } 303 free(iface->vlan_raw_device); 304 iface->vlan_raw_device = strdup(dir->argv[0]); 305 } 306 else if (!strcasecmp("vlan-id", dir->name) || !strcasecmp("vlan_id", dir->name)) { 307 if (dir->argc != 1) { cfg_error("`vlan-id` expects 1 arg"); goto fail; } 308 iface->vlan_id = atoi(dir->argv[0]); 309 } 310 else if (!strcasecmp("pre-up", dir->name) || !strcasecmp("pre_up", dir->name)) { 311 if (dir->argc < 1) { cfg_error("`pre-up` expects at least 1 arg"); goto fail; } 312 // Join argv with spaces 313 size_t len = 0; 314 for (int i=0;i<(int)dir->argc;i++) len += strlen(dir->argv[i])+1; 315 char *cmd = calloc(1, len+1); 316 for (int i=0;i<(int)dir->argc;i++) { 317 strcat(cmd, dir->argv[i]); 318 if (i+1 < (int)dir->argc) strcat(cmd, " "); 319 } 320 free(iface->pre_up); 321 iface->pre_up = cmd; 322 } 323 else if (!strcasecmp("post-up", dir->name) || !strcasecmp("post_up", dir->name)) { 324 if (dir->argc < 1) { cfg_error("`post-up` expects at least 1 arg"); goto fail; } 325 size_t len = 0; 326 for (int i=0;i<(int)dir->argc;i++) len += strlen(dir->argv[i])+1; 327 char *cmd = calloc(1, len+1); 328 for (int i=0;i<(int)dir->argc;i++) { 329 strcat(cmd, dir->argv[i]); 330 if (i+1 < (int)dir->argc) strcat(cmd, " "); 331 } 332 free(iface->post_up); 333 iface->post_up = cmd; 334 } 335 else if (!strcasecmp("pre-down", dir->name) || !strcasecmp("pre_down", dir->name)) { 336 if (dir->argc < 1) { cfg_error("`pre-down` expects at least 1 arg"); goto fail; } 337 size_t len = 0; 338 for (int i=0;i<(int)dir->argc;i++) len += strlen(dir->argv[i])+1; 339 char *cmd = calloc(1, len+1); 340 for (int i=0;i<(int)dir->argc;i++) { 341 strcat(cmd, dir->argv[i]); 342 if (i+1 < (int)dir->argc) strcat(cmd, " "); 343 } 344 free(iface->pre_down); 345 iface->pre_down = cmd; 346 } 347 else if (!strcasecmp("post-down", dir->name) || !strcasecmp("post_down", dir->name)) { 348 if (dir->argc < 1) { cfg_error("`post-down` expects at least 1 arg"); goto fail; } 349 size_t len = 0; 350 for (int i=0;i<(int)dir->argc;i++) len += strlen(dir->argv[i])+1; 351 char *cmd = calloc(1, len+1); 352 for (int i=0;i<(int)dir->argc;i++) { 353 strcat(cmd, dir->argv[i]); 354 if (i+1 < (int)dir->argc) strcat(cmd, " "); 355 } 356 free(iface->post_down); 357 iface->post_down = cmd; 358 } 359 else if (!strcasecmp("up", dir->name)) { 360 // alias for post-up 361 if (dir->argc < 1) { cfg_error("`up` expects at least 1 arg"); goto fail; } 362 size_t len = 0; 363 for (int i=0;i<(int)dir->argc;i++) len += strlen(dir->argv[i])+1; 364 char *cmd = calloc(1, len+1); 365 for (int i=0;i<(int)dir->argc;i++) { 366 strcat(cmd, dir->argv[i]); 367 if (i+1 < (int)dir->argc) strcat(cmd, " "); 368 } 369 free(iface->post_up); 370 iface->post_up = cmd; 371 } 372 else if (!strcasecmp("down", dir->name)) { 373 if (dir->argc < 1) { cfg_error("`down` expects at least 1 arg"); goto fail; } 374 size_t len = 0; 375 for (int i=0;i<(int)dir->argc;i++) len += strlen(dir->argv[i])+1; 376 char *cmd = calloc(1, len+1); 377 for (int i=0;i<(int)dir->argc;i++) { 378 strcat(cmd, dir->argv[i]); 379 if (i+1 < (int)dir->argc) strcat(cmd, " "); 380 } 381 free(iface->pre_down); 382 iface->pre_down = cmd; 383 } 384 else if (!strcasecmp("bridge-ports", dir->name) || !strcasecmp("bridge_ports", dir->name)) { 385 if (dir->argc < 1) { cfg_error("`bridge-ports` expects at least 1 arg"); goto fail; } 386 size_t len = 0; 387 for (int i=0;i<(int)dir->argc;i++) len += strlen(dir->argv[i])+1; 388 char *cmd = calloc(1, len+1); 389 for (int i=0;i<(int)dir->argc;i++) { 390 strcat(cmd, dir->argv[i]); 391 if (i+1 < (int)dir->argc) strcat(cmd, " "); 392 } 393 free(iface->bridge_ports); 394 iface->bridge_ports = cmd; 395 } 396 else if (!strcasecmp("bridge-stp", dir->name) || !strcasecmp("bridge_stp", dir->name)) { 397 if (dir->argc != 1) { cfg_error("`bridge-stp` expects 1 arg (on/off)"); goto fail; } 398 free(iface->bridge_stp); 399 iface->bridge_stp = strdup(dir->argv[0]); 400 } 401 else if (!strcasecmp("bridge-vlan-aware", dir->name) || !strcasecmp("bridge_vlan_aware", dir->name)) { 402 if (dir->argc != 1) { cfg_error("`bridge-vlan-aware` expects 1 arg (yes/no)"); goto fail; } 403 if (!strcasecmp(dir->argv[0], "yes") || !strcasecmp(dir->argv[0], "on") || !strcasecmp(dir->argv[0], "1")) iface->bridge_vlan_aware = 1; 404 else if (!strcasecmp(dir->argv[0], "no") || !strcasecmp(dir->argv[0], "off") || !strcasecmp(dir->argv[0], "0")) iface->bridge_vlan_aware = 0; 405 else { cfg_error("`bridge-vlan-aware` expects yes/no"); goto fail; } 406 } 407 // `vrf <name>`: enslave this interface to an existing VRF device. 408 else if (!strcasecmp("vrf", dir->name)) { 409 if (dir->argc != 1) { cfg_error("`vrf` expects 1 arg (vrf device name)"); goto fail; } 410 free(iface->vrf_master); 411 iface->vrf_master = strdup(dir->argv[0]); 412 } 413 // `vrf-table <id|auto>`: THIS interface is a VRF device with that table. 414 // Cumulus spells the automatic case `auto`; we allocate from a private 415 // range rather than colliding with main(254)/local(255)/default(253). 416 else if (!strcasecmp("vrf-table", dir->name) || !strcasecmp("vrf_table", dir->name)) { 417 if (dir->argc != 1) { cfg_error("`vrf-table` expects 1 arg (table id or `auto`)"); goto fail; } 418 if (!strcasecmp(dir->argv[0], "auto")) { 419 iface->vrf_table = vrf_table_auto(iface->name); 420 if (iface->vrf_table < 0) { 421 cfg_error("`vrf-table auto`: no free table id"); 422 goto fail; 423 } 424 } else { 425 char *end = NULL; 426 long v = strtol(dir->argv[0], &end, 10); 427 if (!end || *end || v <= 0 || v > 2147483647L) { 428 cfg_error("`vrf-table` expects a positive table id or `auto`, got `%s`", dir->argv[0]); 429 goto fail; 430 } 431 if (v == 253 || v == 254 || v == 255) { 432 cfg_error("`vrf-table` %ld is a reserved table (default/main/local)", v); 433 goto fail; 434 } 435 iface->vrf_table = (int)v; 436 } 437 } 438 else { 439 break; 440 } 441 } 442 443 return dir; 444 445 // The iface is already linked into the module list by this point, so 446 // ifaces_free() owns it; only the directive needs releasing here. 447 fail: 448 cnf_directive_free(dir); 449 return NULL; 450 } 451 452 void ifaces_register(void) { 453 struct cfg_ns *ns = cfg_ns_get("interfaces"); 454 cfg_register_directive(ns, "auto", cfg_parse_auto); 455 cfg_register_directive(ns, "allow-auto", cfg_parse_auto); 456 cfg_register_directive(ns, "allow-hotplug", cfg_parse_auto); 457 cfg_register_directive(ns, "iface", cfg_parse_iface); 458 }