/* * libifupdown/interface-file.h * Purpose: /etc/network/interfaces parser * * Copyright (c) 2020 Ariadne Conill * Copyright (c) 2020 Maximilian Wilhelm * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * This software is provided 'as is' and without any warranty, express or * implied. In no event shall the authors be liable for any damages arising * from the use of this software. */ #include #include #include #include #include "libifupdown/libifupdown.h" /* internally rewrite problematic ifupdown2 tokens to ifupdown-ng equivalents */ struct remap_token { const char *token; const char *alternative; }; /* this list must be in alphabetical order for bsearch */ static const struct remap_token tokens[] = { {"bond-ad-sys-priority", "bond-ad-actor-sys-prio"}, /* ifupdown2 */ {"bond-slaves", "bond-members"}, /* legacy ifupdown, ifupdown2 */ {"driver-message-level", "ethtool-msglvl"}, /* Debian ethtool integration */ {"endpoint", "tunnel-remote"}, /* legacy ifupdown */ {"ethernet-autoneg", "ethtool-ethernet-autoneg"}, /* Debian ethtool integration */ {"ethernet-pause-autoneg", "ethtool-pause-autoneg"}, /* Debian ethtool integration */ {"ethernet-pause-rx", "ethtool-pause-rx"}, /* Debian ethtool integration */ {"ethernet-pause-tx", "ethtool-pause-tx"}, /* Debian ethtool integration */ {"ethernet-port", "ethtool-ethernet-port"}, /* Debian ethtool integration */ {"ethernet-wol", "ethtool-ethernet-wol"}, /* Debian ethtool integration */ {"gro-offload", "ethtool-offload-gro"}, /* ifupdown2 */ {"gso-offload", "ethtool-offload-gso"}, /* ifupdown2 */ {"hardware-dma-ring-rx", "ethtool-dma-ring-rx"}, /* Debian ethtool integration */ {"hardware-dma-ring-rx-jumbo", "ethtool-dma-ring-rx-jumbo"}, /* Debian ethtool integration */ {"hardware-dma-ring-rx-mini", "ethtool-dma-ring-rx-mini"}, /* Debian ethtool integration */ {"hardware-dma-ring-tx", "ethtool-dma-ring-tx"}, /* Debian ethtool integration */ {"hardware-irq-coalesce-adaptive-rx", "ethtool-coalesce-adaptive-rx"}, /* Debian ethtool integration */ {"hardware-irq-coalesce-adaptive-tx", "ethtool-coalesce-adaptive-tx"}, /* Debian ethtool integration */ {"hardware-irq-coalesce-pkt-rate-high", "ethtool-coalesce-pkt-rate-high"}, /* Debian ethtool integration */ {"hardware-irq-coalesce-pkt-rate-low", "ethtool-coalesce-pkt-rate-low"}, /* Debian ethtool integration */ {"hardware-irq-coalesce-rx-frames", "ethtool-coalesce-rx-frames"}, /* Debian ethtool integration */ {"hardware-irq-coalesce-rx-frames-high", "ethtool-coalesce-rx-frames-high"}, /* Debian ethtool integration */ {"hardware-irq-coalesce-rx-frames-irq", "ethtool-coalesce-rx-frames-irq"}, /* Debian ethtool integration */ {"hardware-irq-coalesce-rx-frames-low", "ethtool-coalesce-rx-frames-low"}, /* Debian ethtool integration */ {"hardware-irq-coalesce-rx-usecs", "ethtool-coalesce-rx-usecs"}, /* Debian ethtool integration */ {"hardware-irq-coalesce-rx-usecs-high", "ethtool-coalesce-rx-usecs-high"}, /* Debian ethtool integration */ {"hardware-irq-coalesce-rx-usecs-irq", "ethtool-coalesce-rx-usecs-irq"}, /* Debian ethtool integration */ {"hardware-irq-coalesce-rx-usecs-low", "ethtool-coalesce-rx-usecs-low"}, /* Debian ethtool integration */ {"hardware-irq-coalesce-sample-interval", "ethtool-coalesce-sample-interval"}, /* Debian ethtool integration */ {"hardware-irq-coalesce-stats-block-usecs", "ethtool-coalesce-stats-block-usecs"}, /* Debian ethtool integration */ {"hardware-irq-coalesce-tx-frames", "ethtool-coalesce-tx-frames"}, /* Debian ethtool integration */ {"hardware-irq-coalesce-tx-frames-high", "ethtool-coalesce-tx-frames-high"}, /* Debian ethtool integration */ {"hardware-irq-coalesce-tx-frames-irq", "ethtool-coalesce-tx-frames-irq"}, /* Debian ethtool integration */ {"hardware-irq-coalesce-tx-frames-low", "ethtool-coalesce-tx-frames-low"}, /* Debian ethtool integration */ {"hardware-irq-coalesce-tx-usecs", "ethtool-coalesce-tx-usecs"}, /* Debian ethtool integration */ {"hardware-irq-coalesce-tx-usecs-high", "ethtool-coalesce-tx-usecs-high"}, /* Debian ethtool integration */ {"hardware-irq-coalesce-tx-usecs-irq", "ethtool-coalesce-tx-usecs-irq"}, /* Debian ethtool integration */ {"hardware-irq-coalesce-tx-usecs-low", "ethtool-coalesce-tx-usecs-low"}, /* Debian ethtool integration */ {"link-autoneg", "ethtool-ethernet-autoneg"}, /* ifupdown2 */ {"link-duplex", "ethtool-link-duplex"}, /* Debian ethtool integration */ {"link-fec", "ethtool-link-fec"}, /* ifupdown2 */ {"link-speed", "ethtool-link-speed"}, /* Debian ethtool integration */ {"local", "tunnel-local"}, /* legacy ifupdown */ {"lro-offload", "ethtool-offload-lro"}, /* ifupdown2 */ {"mode", "tunnel-mode"}, /* legacy ifupdown */ {"offload-gro", "ethtool-offload-gro"}, /* Debian ethtool integration */ {"offload-gso", "ethtool-offload-gso"}, /* Debian ethtool integration */ {"offload-lro", "ethtool-offload-lro"}, /* Debian ethtool integration */ {"offload-rx", "ethtool-offload-rx"}, /* Debian ethtool integration */ {"offload-sg", "ethtool-offload-sg"}, /* Debian ethtool integration */ {"offload-tso", "ethtool-offload-tso"}, /* Debian ethtool integration */ {"offload-tx", "ethtool-offload-tx"}, /* Debian ethtool integration */ {"offload-ufo", "ethtool-offload-ufo"}, /* Debian ethtool integration */ {"provider", "ppp-provider"}, /* legacy ifupdown, ifupdown2 */ {"rx-offload", "ethtool-offload-rx"}, /* ifupdown2 */ {"tso-offload", "ethtool-offload-tso"}, /* ifupdown2 */ {"ttl", "tunnel-ttl"}, /* legacy ifupdown */ {"tunnel-endpoint", "tunnel-remote"}, /* ifupdown2 */ {"tunnel-physdev", "tunnel-dev"}, /* ifupdown2 */ {"tx-offload", "ethtool-offload-tx"}, /* ifupdown2 */ {"ufo-offload", "ethtool-offload-ufo"}, /* ifupdown2 */ {"vrf", "vrf-member"}, /* ifupdown2 */ {"vxlan-local-tunnelip", "vxlan-local-ip"}, /* ifupdown2 */ {"vxlan-remoteip", "vxlan-remote-ip"}, /* ifupdown2 */ {"vxlan-svcnodeip", "vxlan-remote-group"}, /* ifupdown2 */ }; static int token_cmp(const void *a, const void *b) { const char *key = a; const struct remap_token *token = b; return strcmp(key, token->token); } static char * maybe_remap_token(const char *token) { const struct remap_token *tok = NULL; static char tokbuf[4096]; tok = bsearch(token, tokens, ARRAY_SIZE(tokens), sizeof(*tokens), token_cmp); strlcpy(tokbuf, tok != NULL ? tok->alternative : token, sizeof tokbuf); return tokbuf; } /* XXX: remove this global variable somehow */ static struct lif_interface *cur_iface = NULL; static void report_error(const char *filename, size_t lineno, const char *errfmt, ...) { char errbuf[4096]; va_list va; va_start(va, errfmt); vsnprintf(errbuf, sizeof errbuf, errfmt, va); va_end(va); fprintf(stderr, "%s:%zu: %s\n", filename, lineno, errbuf); } static bool handle_address(struct lif_dict *collection, const char *filename, size_t lineno, char *token, char *bufp) { (void) collection; (void) token; char *addr = lif_next_token(&bufp); if (cur_iface == NULL) { report_error(filename, lineno, "%s '%s' without interface", token, addr); /* Ignore this address, but don't fail hard */ return true; } lif_interface_address_add(cur_iface, addr); return true; } static bool handle_auto(struct lif_dict *collection, const char *filename, size_t lineno, char *token, char *bufp) { (void) filename; (void) lineno; (void) token; char *ifname = lif_next_token(&bufp); if (!*ifname && cur_iface == NULL) { report_error(filename, lineno, "auto without interface"); return true; } else { cur_iface = lif_interface_collection_find(collection, ifname); if (cur_iface == NULL) return false; } if (!cur_iface->is_template) cur_iface->is_auto = true; return true; } static bool handle_gateway(struct lif_dict *collection, const char *filename, size_t lineno, char *token, char *bufp) { (void) collection; (void) token; char *addr = lif_next_token(&bufp); if (cur_iface == NULL) { report_error(filename, lineno, "%s '%s' without interface", token, addr); /* Ignore this gateway, but don't fail hard */ return true; } lif_interface_use_executor(cur_iface, "static"); lif_dict_add(&cur_iface->vars, token, strdup(addr)); return true; } static bool handle_generic(struct lif_dict *collection, const char *filename, size_t lineno, char *token, char *bufp) { (void) collection; (void) filename; (void) lineno; if (cur_iface == NULL) return true; token = maybe_remap_token(token); /* Skip any leading whitespaces in value for */ while (isspace (*bufp)) bufp++; lif_dict_add(&cur_iface->vars, token, strdup(bufp)); /* Check if token looks like - and assume is an addon */ char *word_end = strchr(token, '-'); if (word_end != NULL) { /* Copy word1 to not mangle *token */ char *addon = strndup(token, word_end - token); lif_interface_use_executor(cur_iface, addon); free(addon); } return true; } static bool handle_inherit(struct lif_dict *collection, const char *filename, size_t lineno, char *token, char *bufp); static bool handle_iface(struct lif_dict *collection, const char *filename, size_t lineno, char *token, char *bufp) { char *ifname = lif_next_token(&bufp); if (!*ifname) { report_error(filename, lineno, "%s without any other tokens", token); /* This is broken but not fatal */ return true; } cur_iface = lif_interface_collection_find(collection, ifname); if (cur_iface == NULL) { report_error(filename, lineno, "could not upsert interface %s", ifname); return false; } /* mark the cur_iface as a template iface if `template` keyword * is used. */ if (!strcmp(token, "template")) { cur_iface->is_auto = false; cur_iface->is_template = true; } /* in original ifupdown config, we can have "inet loopback" * or "inet dhcp" or such to designate hints. lets pick up * those hints here. */ token = lif_next_token(&bufp); while (*token) { if (!strcmp(token, "dhcp")) lif_interface_use_executor(cur_iface, "dhcp"); else if (!strcmp(token, "ppp")) lif_interface_use_executor(cur_iface, "ppp"); else if (!strcmp(token, "inherits")) { if (!handle_inherit(collection, filename, lineno, token, bufp)) return false; } token = lif_next_token(&bufp); } return true; } static bool handle_inherit(struct lif_dict *collection, const char *filename, size_t lineno, char *token, char *bufp) { char *target = lif_next_token(&bufp); if (cur_iface == NULL) { report_error(filename, lineno, "%s '%s' without interface", token, target); /* This is broken but not fatal */ return true; } if (!*target) { report_error(filename, lineno, "iface %s: unspecified inherit target", cur_iface->ifname); /* Mark this interface as errornous but carry on */ cur_iface->has_config_error = true; return true; } struct lif_interface *parent = lif_interface_collection_find(collection, target); if (parent == NULL) { report_error(filename, lineno, "iface %s: could not inherit from %s: not found", cur_iface->ifname, target); /* Mark this interface as errornous but carry on */ cur_iface->has_config_error = true; return true; } if (!lif_config.allow_any_iface_as_template && !parent->is_template) { report_error(filename, lineno, "iface %s: could not inherit from %ss: inheritence from non-template interface not allowed", cur_iface->ifname, target); /* Mark this interface as errornous but carry on */ cur_iface->has_config_error = true; return true; } if (!lif_interface_collection_inherit(cur_iface, parent)) { report_error(filename, lineno, "iface %s: could not inherit from %s", cur_iface->ifname, target); /* Mark this interface as errornous but carry on */ cur_iface->has_config_error = true; return true; } return true; } static bool handle_source(struct lif_dict *collection, const char *filename, size_t lineno, char *token, char *bufp) { (void) token; char *source_filename = lif_next_token(&bufp); if (!*source_filename) { report_error(filename, lineno, "missing filename to source"); /* Broken but not fatal */ return true; } if (!strcmp(filename, source_filename)) { report_error(filename, lineno, "attempt to source %s would create infinite loop", source_filename); /* Broken but not fatal */ return true; } return lif_interface_file_parse(collection, source_filename); } static bool handle_use(struct lif_dict *collection, const char *filename, size_t lineno, char *token, char *bufp) { (void) collection; char *executor = lif_next_token(&bufp); if (cur_iface == NULL) { report_error(filename, lineno, "%s '%s' without interface", token, executor); /* Broken but not fatal */ return true; } lif_interface_use_executor(cur_iface, executor); return true; } /* map keywords to parser functions */ struct parser_keyword { const char *token; bool (*handle)(struct lif_dict *collection, const char *filename, size_t lineno, char *token, char *bufp); }; static const struct parser_keyword keywords[] = { {"address", handle_address}, {"auto", handle_auto}, {"gateway", handle_gateway}, {"iface", handle_iface}, {"inherit", handle_inherit}, {"interface", handle_iface}, {"source", handle_source}, {"template", handle_iface}, {"use", handle_use}, }; static int keyword_cmp(const void *a, const void *b) { const char *key = a; const struct parser_keyword *token = b; return strcmp(key, token->token); } bool lif_interface_file_parse(struct lif_dict *collection, const char *filename) { FILE *f = fopen(filename, "r"); if (f == NULL) return false; char linebuf[4096]; size_t lineno = 0; while (lif_fgetline(linebuf, sizeof linebuf, f) != NULL) { lineno++; char *bufp = linebuf; char *token = lif_next_token(&bufp); if (!*token || !isalpha(*token)) continue; const struct parser_keyword *parserkw = bsearch(token, keywords, ARRAY_SIZE(keywords), sizeof(*keywords), keyword_cmp); if (parserkw != NULL) { if (!parserkw->handle(collection, filename, lineno, token, bufp)) goto parse_error; } else if (!handle_generic(collection, filename, lineno, token, bufp)) goto parse_error; } fclose(f); return true; parse_error: fclose(f); return false; }