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