ifupdown-ng/libifupdown/interface-file.c

389 lines
12 KiB
C

/*
* libifupdown/interface-file.h
* Purpose: /etc/network/interfaces parser
*
* Copyright (c) 2020 Ariadne Conill <ariadne@dereferenced.org>
*
* 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[] = {
{"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 */
};
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);
return false;
}
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)
return false;
else
{
cur_iface = lif_interface_collection_find(collection, ifname);
if (cur_iface == NULL)
return false;
}
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);
return false;
}
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);
return false;
}
cur_iface = lif_interface_collection_find(collection, ifname);
if (cur_iface == NULL)
{
report_error(filename, lineno, "could not upsert interface %s", ifname);
return false;
}
/* 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);
return false;
}
if (!*target)
{
report_error(filename, lineno, "%s: unspecified interface");
return false;
}
if (!lif_interface_collection_inherit(cur_iface, collection, target))
{
report_error(filename, lineno, "could not inherit %s", target);
return false;
}
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");
return false;
}
if (!strcmp(filename, source_filename))
{
report_error(filename, lineno, "attempt to source %s would create infinite loop",
source_filename);
return false;
}
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);
return false;
}
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},
{"source", handle_source},
{"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;
}