b75e509f3d
This commit adds support for configuring static PTMP overlays with VXLAN by allowing to specify multiple IPs for »vxlan-peer-ips«. If more than one IP is given ifupdown-ng will set up additional FDB entries for all peer IPs and the Linux Kernel will do ingres / head-end replication for BUM traffic. For a cleaner naming schema and simliar names to commercial vendor CLIs the options to specify unicast or multicast peers have been renamed and aliases added for compatibility to previous versions of ifupdown-ng: * »vxlan-remote-ip« now is named »vxlan-peer-ips« * »vxlan-remote-group« now is called »vxlan-peer-group« Signed-off-by: Maximilian Wilhelm <max@sdn.clinic>
533 lines
17 KiB
C
533 lines
17 KiB
C
/*
|
|
* libifupdown/interface-file.h
|
|
* Purpose: /etc/network/interfaces parser
|
|
*
|
|
* Copyright (c) 2020 Ariadne Conill <ariadne@dereferenced.org>
|
|
* Copyright (c) 2020 Maximilian Wilhelm <max@sdn.clinic>
|
|
*
|
|
* 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 <ctype.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#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 */
|
|
{"client", "dhcp-client-id"}, /* legacy ifupdown */
|
|
{"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 */
|
|
{"hostname", "dhcp-hostname"}, /* legacy ifupdown */
|
|
{"key", "tunnel-key"}, /* legacy ifupdown */
|
|
{"leasetime", "dhcp-leasetime"}, /* legacy ifupdown */
|
|
{"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 */
|
|
{"pointopoint", "point-to-point"}, /* legacy ifupdown, ifupdown2 */
|
|
{"provider", "ppp-provider"}, /* legacy ifupdown, ifupdown2 */
|
|
{"script", "dhcp-script"}, /* legacy ifupdown */
|
|
{"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 */
|
|
{"vendor", "dhcp-vendor"}, /* legacy ifupdown */
|
|
{"vrf", "vrf-member"}, /* ifupdown2 */
|
|
{"vxlan-local-tunnelip", "vxlan-local-ip"}, /* ifupdown2 */
|
|
{"vxlan-remote-group", "vxlan-peer-group"}, /* ifupdown-ng */
|
|
{"vxlan-remoteip", "vxlan-peer-ips"}, /* ifupdown2 */
|
|
{"vxlan-remote-ip", "vxlan-peer-ips"}, /* ifupdown-ng */
|
|
{"vxlan-svcnodeip", "vxlan-peer-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;
|
|
}
|
|
|
|
static void
|
|
report_error(struct lif_interface_file_parse_state *state, 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", state->cur_filename, state->cur_lineno, errbuf);
|
|
}
|
|
|
|
static bool
|
|
handle_address(struct lif_interface_file_parse_state *state, char *token, char *bufp)
|
|
{
|
|
(void) token;
|
|
|
|
char *addr = lif_next_token(&bufp);
|
|
|
|
if (state->cur_iface == NULL)
|
|
{
|
|
report_error(state, "%s '%s' without interface", token, addr);
|
|
/* Ignore this address, but don't fail hard */
|
|
return true;
|
|
}
|
|
|
|
lif_interface_address_add(state->cur_iface, addr);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
handle_auto(struct lif_interface_file_parse_state *state, char *token, char *bufp)
|
|
{
|
|
(void) token;
|
|
|
|
char *ifname = lif_next_token(&bufp);
|
|
if (!*ifname && state->cur_iface == NULL)
|
|
{
|
|
report_error(state, "auto without interface");
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
state->cur_iface = lif_interface_collection_find(state->collection, ifname);
|
|
if (state->cur_iface == NULL)
|
|
return false;
|
|
}
|
|
|
|
if (!state->cur_iface->is_template)
|
|
state->cur_iface->is_auto = true;
|
|
|
|
if (state->cur_iface->is_auto)
|
|
state->cur_iface->is_explicit = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
handle_gateway(struct lif_interface_file_parse_state *state, char *token, char *bufp)
|
|
{
|
|
(void) token;
|
|
|
|
char *addr = lif_next_token(&bufp);
|
|
|
|
if (state->cur_iface == NULL)
|
|
{
|
|
report_error(state, "%s '%s' without interface", token, addr);
|
|
/* Ignore this gateway, but don't fail hard */
|
|
return true;
|
|
}
|
|
|
|
lif_interface_use_executor(state->cur_iface, "static");
|
|
lif_dict_add(&state->cur_iface->vars, token, strdup(addr));
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
handle_generic(struct lif_interface_file_parse_state *state, char *token, char *bufp)
|
|
{
|
|
if (state->cur_iface == NULL)
|
|
return true;
|
|
|
|
token = maybe_remap_token(token);
|
|
|
|
/* This smells like a bridge */
|
|
if (strcmp(token, "bridge-ports") == 0)
|
|
state->cur_iface->is_bridge = true;
|
|
|
|
/* Skip any leading whitespaces in value for <token> */
|
|
while (isspace (*bufp))
|
|
bufp++;
|
|
|
|
lif_dict_add(&state->cur_iface->vars, token, strdup(bufp));
|
|
|
|
if (!lif_config.auto_executor_selection)
|
|
return true;
|
|
|
|
/* Check if token looks like <word1>-<word*> and assume <word1> 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(state->cur_iface, addon);
|
|
free(addon);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
handle_hostname(struct lif_interface_file_parse_state *state, char *token, char *bufp)
|
|
{
|
|
char *hostname = lif_next_token(&bufp);
|
|
|
|
if (state->cur_iface == NULL)
|
|
{
|
|
report_error(state, "%s '%s' without interface", token, hostname);
|
|
/* Ignore this hostname, but don't fail hard */
|
|
return true;
|
|
}
|
|
|
|
lif_dict_delete(&state->cur_iface->vars, "dhcp-hostname");
|
|
lif_dict_add(&state->cur_iface->vars, "dhcp-hostname", strdup(hostname));
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool handle_inherit(struct lif_interface_file_parse_state *state, char *token, char *bufp);
|
|
|
|
static bool
|
|
handle_iface(struct lif_interface_file_parse_state *state, char *token, char *bufp)
|
|
{
|
|
char *ifname = lif_next_token(&bufp);
|
|
if (!*ifname)
|
|
{
|
|
report_error(state, "%s without any other tokens", token);
|
|
/* This is broken but not fatal */
|
|
return true;
|
|
}
|
|
|
|
/* if we have a current interface, call lif_interface_finalize to finalize any
|
|
* address properties by converting them to CIDR and flushing the netmask property.
|
|
*/
|
|
if (state->cur_iface != NULL)
|
|
lif_interface_finalize(state->cur_iface);
|
|
|
|
state->cur_iface = lif_interface_collection_find(state->collection, ifname);
|
|
if (state->cur_iface == NULL)
|
|
{
|
|
report_error(state, "could not upsert interface %s", ifname);
|
|
return false;
|
|
}
|
|
|
|
/* mark the state->cur_iface as a template iface if `template` keyword
|
|
* is used.
|
|
*/
|
|
if (!strcmp(token, "template"))
|
|
{
|
|
state->cur_iface->is_auto = false;
|
|
state->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(state->cur_iface, "dhcp");
|
|
else if (!strcmp(token, "ppp"))
|
|
lif_interface_use_executor(state->cur_iface, "ppp");
|
|
else if (!strcmp(token, "inherits"))
|
|
{
|
|
if (!handle_inherit(state, token, bufp))
|
|
return false;
|
|
}
|
|
|
|
token = lif_next_token(&bufp);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
handle_inherit(struct lif_interface_file_parse_state *state, char *token, char *bufp)
|
|
{
|
|
char *target = lif_next_token(&bufp);
|
|
|
|
if (state->cur_iface == NULL)
|
|
{
|
|
report_error(state, "%s '%s' without interface", token, target);
|
|
/* This is broken but not fatal */
|
|
return true;
|
|
}
|
|
|
|
if (!*target)
|
|
{
|
|
report_error(state, "iface %s: unspecified inherit target", state->cur_iface->ifname);
|
|
/* Mark this interface as errornous but carry on */
|
|
state->cur_iface->has_config_error = true;
|
|
return true;
|
|
}
|
|
|
|
struct lif_interface *parent = lif_interface_collection_find(state->collection, target);
|
|
if (parent == NULL)
|
|
{
|
|
report_error(state, "iface %s: could not inherit from %s: not found",
|
|
state->cur_iface->ifname, target);
|
|
/* Mark this interface as errornous but carry on */
|
|
state->cur_iface->has_config_error = true;
|
|
return true;
|
|
|
|
}
|
|
|
|
if (!lif_config.allow_any_iface_as_template && !parent->is_template)
|
|
{
|
|
report_error(state, "iface %s: could not inherit from %ss: inheritence from non-template interface not allowed",
|
|
state->cur_iface->ifname, target);
|
|
/* Mark this interface as errornous but carry on */
|
|
state->cur_iface->has_config_error = true;
|
|
return true;
|
|
}
|
|
|
|
if (!lif_interface_collection_inherit(state->cur_iface, parent))
|
|
{
|
|
report_error(state, "iface %s: could not inherit from %s", state->cur_iface->ifname, target);
|
|
/* Mark this interface as errornous but carry on */
|
|
state->cur_iface->has_config_error = true;
|
|
return true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
handle_source(struct lif_interface_file_parse_state *state, char *token, char *bufp)
|
|
{
|
|
(void) token;
|
|
|
|
char *source_filename = lif_next_token(&bufp);
|
|
if (!*source_filename)
|
|
{
|
|
report_error(state, "missing filename to source");
|
|
/* Broken but not fatal */
|
|
return true;
|
|
}
|
|
|
|
return lif_interface_file_parse(state, source_filename);
|
|
}
|
|
|
|
static bool
|
|
handle_source_directory(struct lif_interface_file_parse_state *state, char *token, char *bufp)
|
|
{
|
|
(void) token;
|
|
|
|
char *source_directory = lif_next_token(&bufp);
|
|
if (!*source_directory)
|
|
{
|
|
report_error(state, "missing directory to source");
|
|
/* Broken but not fatal */
|
|
return true;
|
|
}
|
|
|
|
DIR *source_dir = opendir(source_directory);
|
|
if (source_dir == NULL)
|
|
{
|
|
report_error(state, "while opening directory %s: %s", source_directory, strerror(errno));
|
|
/* Broken but not fatal */
|
|
return true;
|
|
}
|
|
|
|
struct dirent *dirent_p;
|
|
for (dirent_p = readdir(source_dir); dirent_p != NULL; dirent_p = readdir(source_dir))
|
|
{
|
|
if (dirent_p->d_type != DT_REG)
|
|
continue;
|
|
|
|
char pathbuf[4096];
|
|
snprintf(pathbuf, sizeof pathbuf, "%s/%s", source_directory, dirent_p->d_name);
|
|
|
|
if (!lif_interface_file_parse(state, pathbuf))
|
|
{
|
|
closedir(source_dir);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
closedir(source_dir);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
handle_use(struct lif_interface_file_parse_state *state, char *token, char *bufp)
|
|
{
|
|
char *executor = lif_next_token(&bufp);
|
|
|
|
if (state->cur_iface == NULL)
|
|
{
|
|
report_error(state, "%s '%s' without interface", token, executor);
|
|
/* Broken but not fatal */
|
|
return true;
|
|
}
|
|
|
|
lif_interface_use_executor(state->cur_iface, executor);
|
|
return true;
|
|
}
|
|
|
|
/* map keywords to parser functions */
|
|
struct parser_keyword {
|
|
const char *token;
|
|
bool (*handle)(struct lif_interface_file_parse_state *state, char *token, char *bufp);
|
|
};
|
|
|
|
static const struct parser_keyword keywords[] = {
|
|
{"address", handle_address},
|
|
{"auto", handle_auto},
|
|
{"dhcp-hostname", handle_hostname},
|
|
{"gateway", handle_gateway},
|
|
{"hostname", handle_hostname},
|
|
{"iface", handle_iface},
|
|
{"inherit", handle_inherit},
|
|
{"interface", handle_iface},
|
|
{"source", handle_source},
|
|
{"source-directory", handle_source_directory},
|
|
{"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_interface_file_parse_state *state, const char *filename)
|
|
{
|
|
struct lif_dict_entry *entry = lif_dict_find(&state->loaded, filename);
|
|
if (entry != NULL)
|
|
{
|
|
report_error(state, "skipping already included file %s", filename);
|
|
return true;
|
|
}
|
|
|
|
FILE *f = fopen(filename, "r");
|
|
if (f == NULL)
|
|
return false;
|
|
|
|
const char *old_filename = state->cur_filename;
|
|
state->cur_filename = filename;
|
|
|
|
size_t old_lineno = state->cur_lineno;
|
|
state->cur_lineno = 0;
|
|
|
|
lif_dict_add(&state->loaded, filename, NULL);
|
|
|
|
char linebuf[4096];
|
|
while (lif_fgetline(linebuf, sizeof linebuf, f) != NULL)
|
|
{
|
|
state->cur_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(state, token, bufp))
|
|
goto parse_error;
|
|
}
|
|
else if (!handle_generic(state, token, bufp))
|
|
goto parse_error;
|
|
}
|
|
|
|
fclose(f);
|
|
|
|
/* finalize any open interface */
|
|
if (state->cur_iface != NULL)
|
|
lif_interface_finalize(state->cur_iface);
|
|
|
|
state->cur_filename = old_filename;
|
|
state->cur_lineno = old_lineno;
|
|
return true;
|
|
|
|
parse_error:
|
|
fclose(f);
|
|
state->cur_filename = old_filename;
|
|
state->cur_lineno = old_lineno;
|
|
return false;
|
|
}
|