ifupdown-ng/libifupdown/interface-file.c
Maximilian Wilhelm 9ee3a874d4 Add support for bonding / LAGs.
* Add a bond executor
  * Add mappings from ifupdown1/2
  * Add a detailed man page
  * Remove legacy compatiblity glue for setups with 'requires' only

  The current implementation has to work around the fact that member interfaces
  will be already up then the bond is created. This is simply done by downing
  them, adding them to the bundle and upping them again. This can possible be
  done in a nicer way after revisiting the ordering of plugin execution (#12).

  Closes #91

Signed-off-by: Maximilian Wilhelm <max@sdn.clinic>
2020-10-04 01:35:41 +02:00

442 lines
14 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 "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 <token> */
while (isspace (*bufp))
bufp++;
lif_dict_add(&cur_iface->vars, token, strdup(bufp));
/* 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(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;
}