diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 13:00:47 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 13:00:47 +0000 |
commit | 2cb7e0aaedad73b076ea18c6900b0e86c5760d79 (patch) | |
tree | da68ca54bb79f4080079bf0828acda937593a4e1 /src/network | |
parent | Initial commit. (diff) | |
download | systemd-upstream.tar.xz systemd-upstream.zip |
Adding upstream version 247.3.upstream/247.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
188 files changed, 49904 insertions, 0 deletions
diff --git a/src/network/fuzz-netdev-parser.c b/src/network/fuzz-netdev-parser.c new file mode 100644 index 0000000..ddabe1c --- /dev/null +++ b/src/network/fuzz-netdev-parser.c @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "fd-util.h" +#include "fs-util.h" +#include "fuzz.h" +#include "networkd-manager.h" +#include "tmpfile-util.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + _cleanup_(manager_freep) Manager *manager = NULL; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_(unlink_tempfilep) char netdev_config[] = "/tmp/fuzz-networkd.XXXXXX"; + + if (!getenv("SYSTEMD_LOG_LEVEL")) + log_set_max_level(LOG_CRIT); + + assert_se(fmkostemp_safe(netdev_config, "r+", &f) == 0); + if (size != 0) + assert_se(fwrite(data, size, 1, f) == 1); + + fflush(f); + assert_se(manager_new(&manager) >= 0); + (void) netdev_load_one(manager, netdev_config); + return 0; +} diff --git a/src/network/fuzz-network-parser.c b/src/network/fuzz-network-parser.c new file mode 100644 index 0000000..1292eba --- /dev/null +++ b/src/network/fuzz-network-parser.c @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "fd-util.h" +#include "fs-util.h" +#include "fuzz.h" +#include "networkd-manager.h" +#include "tmpfile-util.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + _cleanup_(manager_freep) Manager *manager = NULL; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_(unlink_tempfilep) char network_config[] = "/tmp/fuzz-networkd.XXXXXX"; + + if (size > 65535) + return 0; + + if (!getenv("SYSTEMD_LOG_LEVEL")) + log_set_max_level(LOG_CRIT); + + assert_se(fmkostemp_safe(network_config, "r+", &f) == 0); + if (size != 0) + assert_se(fwrite(data, size, 1, f) == 1); + + fflush(f); + assert_se(manager_new(&manager) >= 0); + (void) network_load_one(manager, &manager->networks, network_config); + return 0; +} diff --git a/src/network/fuzz-network-parser.options b/src/network/fuzz-network-parser.options new file mode 100644 index 0000000..0824b19 --- /dev/null +++ b/src/network/fuzz-network-parser.options @@ -0,0 +1,2 @@ +[libfuzzer] +max_len = 65535 diff --git a/src/network/generator/main.c b/src/network/generator/main.c new file mode 100644 index 0000000..f9cace7 --- /dev/null +++ b/src/network/generator/main.c @@ -0,0 +1,206 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <getopt.h> + +#include "fd-util.h" +#include "generator.h" +#include "macro.h" +#include "main-func.h" +#include "mkdir.h" +#include "network-generator.h" +#include "path-util.h" +#include "proc-cmdline.h" + +#define NETWORKD_UNIT_DIRECTORY "/run/systemd/network" + +static const char *arg_root = NULL; + +static int network_save(Network *network, const char *dest_dir) { + _cleanup_free_ char *filename = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(network); + + r = asprintf(&filename, "%s-%s.network", + isempty(network->ifname) ? "91" : "90", + isempty(network->ifname) ? "default" : network->ifname); + if (r < 0) + return log_oom(); + + r = generator_open_unit_file(dest_dir, "kernel command line", filename, &f); + if (r < 0) + return r; + + network_dump(network, f); + + return 0; +} + +static int netdev_save(NetDev *netdev, const char *dest_dir) { + _cleanup_free_ char *filename = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(netdev); + + r = asprintf(&filename, "90-%s.netdev", + netdev->ifname); + if (r < 0) + return log_oom(); + + r = generator_open_unit_file(dest_dir, "kernel command line", filename, &f); + if (r < 0) + return r; + + netdev_dump(netdev, f); + + return 0; +} + +static int link_save(Link *link, const char *dest_dir) { + _cleanup_free_ char *filename = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(link); + + r = asprintf(&filename, "90-%s.link", + link->ifname); + if (r < 0) + return log_oom(); + + r = generator_open_unit_file(dest_dir, "kernel command line", filename, &f); + if (r < 0) + return r; + + link_dump(link, f); + + return 0; +} + +static int context_save(Context *context) { + Network *network; + NetDev *netdev; + Link *link; + int k, r; + const char *p; + + p = prefix_roota(arg_root, NETWORKD_UNIT_DIRECTORY); + + r = mkdir_p(p, 0755); + if (r < 0) + return log_error_errno(r, "Failed to create directory " NETWORKD_UNIT_DIRECTORY ": %m"); + + HASHMAP_FOREACH(network, context->networks_by_name) { + k = network_save(network, p); + if (k < 0 && r >= 0) + r = k; + } + + HASHMAP_FOREACH(netdev, context->netdevs_by_name) { + k = netdev_save(netdev, p); + if (k < 0 && r >= 0) + r = k; + } + + HASHMAP_FOREACH(link, context->links_by_name) { + k = link_save(link, p); + if (k < 0 && r >= 0) + r = k; + } + + return r; +} + +static int help(void) { + printf("%s [OPTIONS...] [-- KERNEL_CMDLINE]\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --root=PATH Operate on an alternate filesystem root\n" + , program_invocation_short_name + ); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_ROOT, + }; + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "root", required_argument, NULL, ARG_ROOT }, + {}, + }; + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + + case 'h': + return help(); + + case ARG_VERSION: + return version(); + + case ARG_ROOT: + arg_root = optarg; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +static int run(int argc, char *argv[]) { + _cleanup_(context_clear) Context context = {}; + int i, r; + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + if (optind >= argc) { + r = proc_cmdline_parse(parse_cmdline_item, &context, 0); + if (r < 0) + return log_warning_errno(r, "Failed to parse kernel command line: %m"); + } else { + for (i = optind; i < argc; i++) { + _cleanup_free_ char *word = NULL; + char *value; + + word = strdup(argv[i]); + if (!word) + return log_oom(); + + value = strchr(word, '='); + if (value) + *(value++) = 0; + + r = parse_cmdline_item(word, value, &context); + if (r < 0) + return log_warning_errno(r, "Failed to parse command line \"%s%s%s\": %m", + word, value ? "=" : "", strempty(value)); + } + } + + r = context_merge_networks(&context); + if (r < 0) + return log_warning_errno(r, "Failed to merge multiple command line options: %m"); + + return context_save(&context); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/network/generator/network-generator.c b/src/network/generator/network-generator.c new file mode 100644 index 0000000..2fa21a0 --- /dev/null +++ b/src/network/generator/network-generator.c @@ -0,0 +1,1233 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "ether-addr-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "hostname-util.h" +#include "log.h" +#include "macro.h" +#include "network-generator.h" +#include "parse-util.h" +#include "proc-cmdline.h" +#include "socket-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" + +/* + # .network + ip={dhcp|on|any|dhcp6|auto6|either6} + ip=<interface>:{dhcp|on|any|dhcp6|auto6}[:[<mtu>][:<macaddr>]] + ip=<client-IP>:[<peer>]:<gateway-IP>:<netmask>:<client_hostname>:<interface>:{none|off|dhcp|on|any|dhcp6|auto6|ibft}[:[<mtu>][:<macaddr>]] + ip=<client-IP>:[<peer>]:<gateway-IP>:<netmask>:<client_hostname>:<interface>:{none|off|dhcp|on|any|dhcp6|auto6|ibft}[:[<dns1>][:<dns2>]] + rd.route=<net>/<netmask>:<gateway>[:<interface>] + nameserver=<IP> [nameserver=<IP> ...] + rd.peerdns=0 + + # .link + ifname=<interface>:<MAC> + + # .netdev + vlan=<vlanname>:<phydevice> + bond=<bondname>[:<bondslaves>:[:<options>[:<mtu>]]] + team=<teammaster>:<teamslaves> # not supported + bridge=<bridgename>:<ethnames> + + # ignored + bootdev=<interface> + BOOTIF=<MAC> + rd.bootif=0 + biosdevname=0 + rd.neednet=1 +*/ + +static const char * const dracut_dhcp_type_table[_DHCP_TYPE_MAX] = { + [DHCP_TYPE_NONE] = "none", + [DHCP_TYPE_OFF] = "off", + [DHCP_TYPE_ON] = "on", + [DHCP_TYPE_ANY] = "any", + [DHCP_TYPE_DHCP] = "dhcp", + [DHCP_TYPE_DHCP6] = "dhcp6", + [DHCP_TYPE_AUTO6] = "auto6", + [DHCP_TYPE_EITHER6] = "either6", + [DHCP_TYPE_IBFT] = "ibft", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(dracut_dhcp_type, DHCPType); + +static const char * const networkd_dhcp_type_table[_DHCP_TYPE_MAX] = { + [DHCP_TYPE_NONE] = "no", + [DHCP_TYPE_OFF] = "no", + [DHCP_TYPE_ON] = "yes", + [DHCP_TYPE_ANY] = "yes", + [DHCP_TYPE_DHCP] = "ipv4", + [DHCP_TYPE_DHCP6] = "ipv6", + [DHCP_TYPE_AUTO6] = "no", /* TODO: enable other setting? */ + [DHCP_TYPE_EITHER6] = "ipv6", /* TODO: enable other setting? */ + [DHCP_TYPE_IBFT] = "no", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(networkd_dhcp_type, DHCPType); + +static Address *address_free(Address *address) { + if (!address) + return NULL; + + if (address->network) + LIST_REMOVE(addresses, address->network->addresses, address); + + return mfree(address); +} + +static int address_new(Network *network, int family, unsigned char prefixlen, + union in_addr_union *addr, union in_addr_union *peer, Address **ret) { + Address *address; + + assert(network); + + address = new(Address, 1); + if (!address) + return -ENOMEM; + + *address = (Address) { + .family = family, + .prefixlen = prefixlen, + .address = *addr, + .peer = *peer, + }; + + LIST_PREPEND(addresses, network->addresses, address); + + address->network = network; + + if (ret) + *ret = address; + return 0; +} + +static Route *route_free(Route *route) { + if (!route) + return NULL; + + if (route->network) + LIST_REMOVE(routes, route->network->routes, route); + + return mfree(route); +} + +static int route_new(Network *network, int family, unsigned char prefixlen, + union in_addr_union *dest, union in_addr_union *gateway, Route **ret) { + Route *route; + + assert(network); + + route = new(Route, 1); + if (!route) + return -ENOMEM; + + *route = (Route) { + .family = family, + .prefixlen = prefixlen, + .dest = dest ? *dest : IN_ADDR_NULL, + .gateway = *gateway, + }; + + LIST_PREPEND(routes, network->routes, route); + + route->network = network; + + if (ret) + *ret = route; + return 0; +} + +static Network *network_free(Network *network) { + Address *address; + Route *route; + + if (!network) + return NULL; + + free(network->ifname); + free(network->hostname); + strv_free(network->dns); + free(network->vlan); + free(network->bridge); + free(network->bond); + + while ((address = network->addresses)) + address_free(address); + + while ((route = network->routes)) + route_free(route); + + return mfree(network); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(Network*, network_free); + +static int network_new(Context *context, const char *name, Network **ret) { + _cleanup_(network_freep) Network *network = NULL; + _cleanup_free_ char *ifname = NULL; + int r; + + assert(context); + + if (!isempty(name) && !ifname_valid(name)) + return -EINVAL; + + ifname = strdup(name); + if (!ifname) + return -ENOMEM; + + network = new(Network, 1); + if (!network) + return -ENOMEM; + + *network = (Network) { + .ifname = TAKE_PTR(ifname), + .dhcp_type = _DHCP_TYPE_INVALID, + .dhcp_use_dns = -1, + }; + + r = hashmap_ensure_allocated(&context->networks_by_name, &string_hash_ops); + if (r < 0) + return r; + + r = hashmap_put(context->networks_by_name, network->ifname, network); + if (r < 0) + return r; + + if (ret) + *ret = network; + + TAKE_PTR(network); + return 0; +} + +Network *network_get(Context *context, const char *ifname) { + return hashmap_get(context->networks_by_name, ifname); +} + +static NetDev *netdev_free(NetDev *netdev) { + if (!netdev) + return NULL; + + free(netdev->ifname); + free(netdev->kind); + return mfree(netdev); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(NetDev*, netdev_free); + +static int netdev_new(Context *context, const char *_kind, const char *_ifname, NetDev **ret) { + _cleanup_(netdev_freep) NetDev *netdev = NULL; + _cleanup_free_ char *kind = NULL, *ifname = NULL; + int r; + + assert(context); + + if (!ifname_valid(_ifname)) + return -EINVAL; + + kind = strdup(_kind); + if (!kind) + return -ENOMEM; + + ifname = strdup(_ifname); + if (!ifname) + return -ENOMEM; + + netdev = new(NetDev, 1); + if (!netdev) + return -ENOMEM; + + *netdev = (NetDev) { + .kind = TAKE_PTR(kind), + .ifname = TAKE_PTR(ifname), + }; + + r = hashmap_ensure_allocated(&context->netdevs_by_name, &string_hash_ops); + if (r < 0) + return r; + + r = hashmap_put(context->netdevs_by_name, netdev->ifname, netdev); + if (r < 0) + return r; + + if (ret) + *ret = netdev; + + TAKE_PTR(netdev); + return 0; +} + +NetDev *netdev_get(Context *context, const char *ifname) { + return hashmap_get(context->netdevs_by_name, ifname); +} + +static Link *link_free(Link *link) { + if (!link) + return NULL; + + free(link->ifname); + return mfree(link); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_free); + +static int link_new(Context *context, const char *name, struct ether_addr *mac, Link **ret) { + _cleanup_(link_freep) Link *link = NULL; + _cleanup_free_ char *ifname = NULL; + int r; + + assert(context); + + if (!ifname_valid(name)) + return -EINVAL; + + ifname = strdup(name); + if (!ifname) + return -ENOMEM; + + link = new(Link, 1); + if (!link) + return -ENOMEM; + + *link = (Link) { + .ifname = TAKE_PTR(ifname), + .mac = *mac, + }; + + r = hashmap_ensure_allocated(&context->links_by_name, &string_hash_ops); + if (r < 0) + return r; + + r = hashmap_put(context->links_by_name, link->ifname, link); + if (r < 0) + return r; + + if (ret) + *ret = link; + + TAKE_PTR(link); + return 0; +} + +Link *link_get(Context *context, const char *ifname) { + return hashmap_get(context->links_by_name, ifname); +} + +static int network_set_dhcp_type(Context *context, const char *ifname, const char *dhcp_type) { + Network *network; + DHCPType t; + int r; + + t = dracut_dhcp_type_from_string(dhcp_type); + if (t < 0) + return -EINVAL; + + network = network_get(context, ifname); + if (!network) { + r = network_new(context, ifname, &network); + if (r < 0) + return r; + } + + network->dhcp_type = t; + return 0; +} + +static int network_set_hostname(Context *context, const char *ifname, const char *hostname) { + Network *network; + + network = network_get(context, ifname); + if (!network) + return -ENODEV; + + return free_and_strdup(&network->hostname, hostname); +} + +static int network_set_mtu(Context *context, const char *ifname, int family, const char *mtu) { + Network *network; + + network = network_get(context, ifname); + if (!network) + return -ENODEV; + + return parse_mtu(family, mtu, &network->mtu); +} + +static int network_set_mac_address(Context *context, const char *ifname, const char *mac) { + Network *network; + + network = network_get(context, ifname); + if (!network) + return -ENODEV; + + return ether_addr_from_string(mac, &network->mac); +} + +static int network_set_address(Context *context, const char *ifname, int family, unsigned char prefixlen, + union in_addr_union *addr, union in_addr_union *peer) { + Network *network; + + if (in_addr_is_null(family, addr) != 0) + return 0; + + network = network_get(context, ifname); + if (!network) + return -ENODEV; + + return address_new(network, family, prefixlen, addr, peer, NULL); +} + +static int network_set_route(Context *context, const char *ifname, int family, unsigned char prefixlen, + union in_addr_union *dest, union in_addr_union *gateway) { + Network *network; + int r; + + if (in_addr_is_null(family, gateway) != 0) + return 0; + + network = network_get(context, ifname); + if (!network) { + r = network_new(context, ifname, &network); + if (r < 0) + return r; + } + + return route_new(network, family, prefixlen, dest, gateway, NULL); +} + +static int network_set_dns(Context *context, const char *ifname, const char *dns) { + union in_addr_union a; + Network *network; + int family, r; + + r = in_addr_from_string_auto(dns, &family, &a); + if (r < 0) + return r; + + network = network_get(context, ifname); + if (!network) { + r = network_new(context, ifname, &network); + if (r < 0) + return r; + } + + return strv_extend(&network->dns, dns); +} + +static int network_set_dhcp_use_dns(Context *context, const char *ifname, bool value) { + Network *network; + int r; + + network = network_get(context, ifname); + if (!network) { + r = network_new(context, ifname, &network); + if (r < 0) + return r; + } + + network->dhcp_use_dns = value; + + return 0; +} + +static int network_set_vlan(Context *context, const char *ifname, const char *value) { + Network *network; + int r; + + network = network_get(context, ifname); + if (!network) { + r = network_new(context, ifname, &network); + if (r < 0) + return r; + } + + return free_and_strdup(&network->vlan, value); +} + +static int network_set_bridge(Context *context, const char *ifname, const char *value) { + Network *network; + int r; + + network = network_get(context, ifname); + if (!network) { + r = network_new(context, ifname, &network); + if (r < 0) + return r; + } + + return free_and_strdup(&network->bridge, value); +} + +static int network_set_bond(Context *context, const char *ifname, const char *value) { + Network *network; + int r; + + network = network_get(context, ifname); + if (!network) { + r = network_new(context, ifname, &network); + if (r < 0) + return r; + } + + return free_and_strdup(&network->bond, value); +} + +static int parse_cmdline_ip_mtu_mac(Context *context, const char *ifname, int family, const char *value) { + const char *mtu, *p; + int r; + + /* [<mtu>][:<macaddr>] */ + + p = strchr(value, ':'); + if (!p) + mtu = value; + else + mtu = strndupa(value, p - value); + + r = network_set_mtu(context, ifname, family, mtu); + if (r < 0) + return r; + + if (!p) + return 0; + + r = network_set_mac_address(context, ifname, p + 1); + if (r < 0) + return r; + + return 0; +} + +static int parse_ip_address_one(int family, const char **value, union in_addr_union *ret) { + const char *p = *value, *q, *buf; + int r; + + if (p[0] == ':') { + *value = p + 1; + return 0; + } + + if (family == AF_INET6) { + if (p[0] != '[') + return -EINVAL; + + q = strchr(p + 1, ']'); + if (!q) + return -EINVAL; + + if (q[1] != ':') + return -EINVAL; + + buf = strndupa(p + 1, q - p - 1); + p = q + 2; + } else { + q = strchr(p, ':'); + if (!q) + return -EINVAL; + + buf = strndupa(p, q - p); + p = q + 1; + } + + r = in_addr_from_string(family, buf, ret); + if (r < 0) + return r; + + *value = p; + return 1; +} + +static int parse_netmask_or_prefixlen(int family, const char **value, unsigned char *ret) { + union in_addr_union netmask; + const char *p, *q; + int r; + + r = parse_ip_address_one(family, value, &netmask); + if (r > 0) { + if (family == AF_INET6) + /* TODO: Not supported yet. */ + return -EINVAL; + + *ret = in4_addr_netmask_to_prefixlen(&netmask.in); + } else if (r == 0) + *ret = family == AF_INET6 ? 128 : 32; + else { + p = strchr(*value, ':'); + if (!p) + return -EINVAL; + + q = strndupa(*value, p - *value); + r = safe_atou8(q, ret); + if (r < 0) + return r; + + *value = p + 1; + } + + return 0; +} + +static int parse_cmdline_ip_address(Context *context, int family, const char *value) { + union in_addr_union addr = {}, peer = {}, gateway = {}; + const char *hostname = NULL, *ifname, *dhcp_type, *dns, *p; + unsigned char prefixlen; + int r; + + /* ip=<client-IP>:[<peer>]:<gateway-IP>:<netmask>:<client_hostname>:<interface>:{none|off|dhcp|on|any|dhcp6|auto6|ibft}[:[<mtu>][:<macaddr>]] + * ip=<client-IP>:[<peer>]:<gateway-IP>:<netmask>:<client_hostname>:<interface>:{none|off|dhcp|on|any|dhcp6|auto6|ibft}[:[<dns1>][:<dns2>]] */ + + r = parse_ip_address_one(family, &value, &addr); + if (r < 0) + return r; + r = parse_ip_address_one(family, &value, &peer); + if (r < 0) + return r; + r = parse_ip_address_one(family, &value, &gateway); + if (r < 0) + return r; + r = parse_netmask_or_prefixlen(family, &value, &prefixlen); + if (r < 0) + return r; + + /* hostname */ + p = strchr(value, ':'); + if (!p) + return -EINVAL; + + if (p != value) { + hostname = strndupa(value, p - value); + if (!hostname_is_valid(hostname, false)) + return -EINVAL; + } + + value = p + 1; + + /* ifname */ + p = strchr(value, ':'); + if (!p) + return -EINVAL; + + ifname = strndupa(value, p - value); + + value = p + 1; + + /* dhcp_type */ + p = strchr(value, ':'); + if (!p) + dhcp_type = value; + else + dhcp_type = strndupa(value, p - value); + + r = network_set_dhcp_type(context, ifname, dhcp_type); + if (r < 0) + return r; + + /* set values */ + r = network_set_hostname(context, ifname, hostname); + if (r < 0) + return r; + + r = network_set_address(context, ifname, family, prefixlen, &addr, &peer); + if (r < 0) + return r; + + r = network_set_route(context, ifname, family, 0, NULL, &gateway); + if (r < 0) + return r; + + if (!p) + return 0; + + /* First, try [<mtu>][:<macaddr>] */ + r = parse_cmdline_ip_mtu_mac(context, ifname, AF_UNSPEC, p + 1); + if (r >= 0) + return 0; + + /* Next, try [<dns1>][:<dns2>] */ + value = p + 1; + p = strchr(value, ':'); + if (!p) { + r = network_set_dns(context, ifname, value); + if (r < 0) + return r; + } else { + dns = strndupa(value, p - value); + r = network_set_dns(context, ifname, dns); + if (r < 0) + return r; + r = network_set_dns(context, ifname, p + 1); + if (r < 0) + return r; + } + + return 0; +} + +static int parse_cmdline_ip_interface(Context *context, const char *value) { + const char *ifname, *dhcp_type, *p; + int r; + + /* ip=<interface>:{dhcp|on|any|dhcp6|auto6}[:[<mtu>][:<macaddr>]] */ + + p = strchr(value, ':'); + if (!p) + return -EINVAL; + + ifname = strndupa(value, p - value); + + value = p + 1; + p = strchr(value, ':'); + if (!p) + dhcp_type = value; + else + dhcp_type = strndupa(value, p - value); + + r = network_set_dhcp_type(context, ifname, dhcp_type); + if (r < 0) + return r; + + if (!p) + return 0; + + return parse_cmdline_ip_mtu_mac(context, ifname, AF_UNSPEC, p + 1); +} + +static int parse_cmdline_ip(Context *context, const char *key, const char *value) { + const char *p; + int r; + + if (proc_cmdline_value_missing(key, value)) + return -EINVAL; + + p = strchr(value, ':'); + if (!p) + /* ip={dhcp|on|any|dhcp6|auto6|either6} */ + return network_set_dhcp_type(context, "", value); + + if (value[0] == '[') + return parse_cmdline_ip_address(context, AF_INET6, value); + + r = parse_cmdline_ip_address(context, AF_INET, value); + if (r < 0) + return parse_cmdline_ip_interface(context, value); + + return 0; +} + +static int parse_cmdline_rd_route(Context *context, const char *key, const char *value) { + union in_addr_union addr = {}, gateway = {}; + unsigned char prefixlen; + const char *buf, *p; + int family, r; + + /* rd.route=<net>/<netmask>:<gateway>[:<interface>] */ + + if (proc_cmdline_value_missing(key, value)) + return -EINVAL; + + if (value[0] == '[') { + p = strchr(value, ']'); + if (!p) + return -EINVAL; + + if (p[1] != ':') + return -EINVAL; + + buf = strndupa(value + 1, p - value - 1); + value = p + 2; + family = AF_INET6; + } else { + p = strchr(value, ':'); + if (!p) + return -EINVAL; + + buf = strndupa(value, p - value); + value = p + 1; + family = AF_INET; + } + + r = in_addr_prefix_from_string(buf, family, &addr, &prefixlen); + if (r < 0) + return r; + + p = strchr(value, ':'); + if (!p) + value = strjoina(value, ":"); + + r = parse_ip_address_one(family, &value, &gateway); + if (r < 0) + return r; + + return network_set_route(context, value, family, prefixlen, &addr, &gateway); +} + +static int parse_cmdline_nameserver(Context *context, const char *key, const char *value) { + if (proc_cmdline_value_missing(key, value)) + return -EINVAL; + + return network_set_dns(context, "", value); +} + +static int parse_cmdline_rd_peerdns(Context *context, const char *key, const char *value) { + int r; + + if (proc_cmdline_value_missing(key, value)) + return network_set_dhcp_use_dns(context, "", true); + + r = parse_boolean(value); + if (r < 0) + return r; + + return network_set_dhcp_use_dns(context, "", r); +} + +static int parse_cmdline_vlan(Context *context, const char *key, const char *value) { + const char *name, *p; + NetDev *netdev; + int r; + + if (proc_cmdline_value_missing(key, value)) + return -EINVAL; + + p = strchr(value, ':'); + if (!p) + return -EINVAL; + + name = strndupa(value, p - value); + + netdev = netdev_get(context, name); + if (!netdev) { + r = netdev_new(context, "vlan", name, &netdev); + if (r < 0) + return r; + } + + return network_set_vlan(context, p + 1, name); +} + +static int parse_cmdline_bridge(Context *context, const char *key, const char *value) { + const char *name, *p; + NetDev *netdev; + int r; + + if (proc_cmdline_value_missing(key, value)) + return -EINVAL; + + p = strchr(value, ':'); + if (!p) + return -EINVAL; + + name = strndupa(value, p - value); + + netdev = netdev_get(context, name); + if (!netdev) { + r = netdev_new(context, "bridge", name, &netdev); + if (r < 0) + return r; + } + + p++; + if (isempty(p)) + return -EINVAL; + + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, ",", 0); + if (r <= 0) + return r; + + r = network_set_bridge(context, word, name); + if (r < 0) + return r; + } +} + +static int parse_cmdline_bond(Context *context, const char *key, const char *value) { + const char *name, *slaves, *p; + NetDev *netdev; + int r; + + if (proc_cmdline_value_missing(key, value)) + return -EINVAL; + + p = strchr(value, ':'); + if (!p) + return -EINVAL; + + name = strndupa(value, p - value); + + netdev = netdev_get(context, name); + if (!netdev) { + r = netdev_new(context, "bond", name, &netdev); + if (r < 0) + return r; + } + + value = p + 1; + p = strchr(value, ':'); + if (!p) + slaves = value; + else + slaves = strndupa(value, p - value); + + if (isempty(slaves)) + return -EINVAL; + + for (const char *q = slaves; ; ) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&q, &word, ",", 0); + if (r == 0) + break; + if (r < 0) + return r; + + r = network_set_bond(context, word, name); + if (r < 0) + return r; + } + + if (!p) + return 0; + + value = p + 1; + p = strchr(value, ':'); + if (!p) + /* TODO: set bonding options */ + return 0; + + return parse_mtu(AF_UNSPEC, p + 1, &netdev->mtu); +} + +static int parse_cmdline_ifname(Context *context, const char *key, const char *value) { + struct ether_addr mac; + const char *name, *p; + int r; + + /* ifname=<interface>:<MAC> */ + + if (proc_cmdline_value_missing(key, value)) + return -EINVAL; + + p = strchr(value, ':'); + if (!p) + return -EINVAL; + + name = strndupa(value, p - value); + + r = ether_addr_from_string(p + 1, &mac); + if (r < 0) + return r; + + return link_new(context, name, &mac, NULL); +} + +int parse_cmdline_item(const char *key, const char *value, void *data) { + Context *context = data; + + assert(key); + assert(data); + + if (streq(key, "ip")) + return parse_cmdline_ip(context, key, value); + if (streq(key, "rd.route")) + return parse_cmdline_rd_route(context, key, value); + if (streq(key, "nameserver")) + return parse_cmdline_nameserver(context, key, value); + if (streq(key, "rd.peerdns")) + return parse_cmdline_rd_peerdns(context, key, value); + if (streq(key, "vlan")) + return parse_cmdline_vlan(context, key, value); + if (streq(key, "bridge")) + return parse_cmdline_bridge(context, key, value); + if (streq(key, "bond")) + return parse_cmdline_bond(context, key, value); + if (streq(key, "ifname")) + return parse_cmdline_ifname(context, key, value); + + return 0; +} + +int context_merge_networks(Context *context) { + Network *all, *network; + Route *route; + int r; + + assert(context); + + /* Copy settings about the following options + rd.route=<net>/<netmask>:<gateway>[:<interface>] + nameserver=<IP> [nameserver=<IP> ...] + rd.peerdns=0 */ + + all = network_get(context, ""); + if (!all) + return 0; + + if (hashmap_size(context->networks_by_name) <= 1) + return 0; + + HASHMAP_FOREACH(network, context->networks_by_name) { + if (network == all) + continue; + + network->dhcp_use_dns = all->dhcp_use_dns; + + r = strv_extend_strv(&network->dns, all->dns, false); + if (r < 0) + return r; + + LIST_FOREACH(routes, route, all->routes) { + r = route_new(network, route->family, route->prefixlen, &route->dest, &route->gateway, NULL); + if (r < 0) + return r; + } + } + + assert_se(hashmap_remove(context->networks_by_name, "") == all); + network_free(all); + return 0; +} + +void context_clear(Context *context) { + if (!context) + return; + + hashmap_free_with_destructor(context->networks_by_name, network_free); + hashmap_free_with_destructor(context->netdevs_by_name, netdev_free); + hashmap_free_with_destructor(context->links_by_name, link_free); +} + +static int address_dump(Address *address, FILE *f) { + _cleanup_free_ char *addr = NULL, *peer = NULL; + int r; + + r = in_addr_prefix_to_string(address->family, &address->address, address->prefixlen, &addr); + if (r < 0) + return r; + + if (in_addr_is_null(address->family, &address->peer) == 0) { + r = in_addr_to_string(address->family, &address->peer, &peer); + if (r < 0) + return r; + } + + fprintf(f, + "\n[Address]\n" + "Address=%s\n", + addr); + + if (peer) + fprintf(f, "Peer=%s\n", peer); + + return 0; +} + +static int route_dump(Route *route, FILE *f) { + _cleanup_free_ char *dest = NULL, *gateway = NULL; + int r; + + if (in_addr_is_null(route->family, &route->dest) == 0) { + r = in_addr_prefix_to_string(route->family, &route->dest, route->prefixlen, &dest); + if (r < 0) + return r; + } + + r = in_addr_to_string(route->family, &route->gateway, &gateway); + if (r < 0) + return r; + + fputs("\n[Route]\n", f); + if (dest) + fprintf(f, "Destination=%s\n", dest); + fprintf(f, "Gateway=%s\n", gateway); + + return 0; +} + +void network_dump(Network *network, FILE *f) { + char mac[ETHER_ADDR_TO_STRING_MAX]; + Address *address; + Route *route; + const char *dhcp; + char **dns; + + assert(network); + assert(f); + + fprintf(f, + "[Match]\n" + "Name=%s\n", + isempty(network->ifname) ? "*" : network->ifname); + + fputs("\n[Link]\n", f); + + if (!ether_addr_is_null(&network->mac)) + fprintf(f, "MACAddress=%s\n", ether_addr_to_string(&network->mac, mac)); + if (network->mtu > 0) + fprintf(f, "MTUBytes=%" PRIu32 "\n", network->mtu); + + fputs("\n[Network]\n", f); + + dhcp = networkd_dhcp_type_to_string(network->dhcp_type); + if (dhcp) + fprintf(f, "DHCP=%s\n", dhcp); + + if (!strv_isempty(network->dns)) + STRV_FOREACH(dns, network->dns) + fprintf(f, "DNS=%s\n", *dns); + + if (network->vlan) + fprintf(f, "VLAN=%s\n", network->vlan); + + if (network->bridge) + fprintf(f, "Bridge=%s\n", network->bridge); + + if (network->bond) + fprintf(f, "Bond=%s\n", network->bond); + + fputs("\n[DHCP]\n", f); + + if (!isempty(network->hostname)) + fprintf(f, "Hostname=%s\n", network->hostname); + + if (network->dhcp_use_dns >= 0) + fprintf(f, "UseDNS=%s\n", yes_no(network->dhcp_use_dns)); + + LIST_FOREACH(addresses, address, network->addresses) + (void) address_dump(address, f); + + LIST_FOREACH(routes, route, network->routes) + (void) route_dump(route, f); +} + +void netdev_dump(NetDev *netdev, FILE *f) { + assert(netdev); + assert(f); + + fprintf(f, + "[NetDev]\n" + "Kind=%s\n" + "Name=%s\n", + netdev->kind, + netdev->ifname); + + if (netdev->mtu > 0) + fprintf(f, "MTUBytes=%" PRIu32 "\n", netdev->mtu); +} + +void link_dump(Link *link, FILE *f) { + char mac[ETHER_ADDR_TO_STRING_MAX]; + + assert(link); + assert(f); + + fputs("[Match]\n", f); + + if (!ether_addr_is_null(&link->mac)) + fprintf(f, "MACAddress=%s\n", ether_addr_to_string(&link->mac, mac)); + + fprintf(f, + "\n[Link]\n" + "Name=%s\n", + link->ifname); +} + +int network_format(Network *network, char **ret) { + _cleanup_free_ char *s = NULL; + size_t sz = 0; + int r; + + assert(network); + assert(ret); + + { + _cleanup_fclose_ FILE *f = NULL; + + f = open_memstream_unlocked(&s, &sz); + if (!f) + return -ENOMEM; + + network_dump(network, f); + + /* Add terminating 0, so that the output buffer is a valid string. */ + fputc('\0', f); + + r = fflush_and_check(f); + } + if (r < 0) + return r; + + assert(s); + *ret = TAKE_PTR(s); + assert(sz > 0); + return (int) sz - 1; +} + +int netdev_format(NetDev *netdev, char **ret) { + _cleanup_free_ char *s = NULL; + size_t sz = 0; + int r; + + assert(netdev); + assert(ret); + + { + _cleanup_fclose_ FILE *f = NULL; + + f = open_memstream_unlocked(&s, &sz); + if (!f) + return -ENOMEM; + + netdev_dump(netdev, f); + + /* Add terminating 0, so that the output buffer is a valid string. */ + fputc('\0', f); + + r = fflush_and_check(f); + } + if (r < 0) + return r; + + assert(s); + *ret = TAKE_PTR(s); + assert(sz > 0); + return (int) sz - 1; +} + +int link_format(Link *link, char **ret) { + _cleanup_free_ char *s = NULL; + size_t sz = 0; + int r; + + assert(link); + assert(ret); + + { + _cleanup_fclose_ FILE *f = NULL; + + f = open_memstream_unlocked(&s, &sz); + if (!f) + return -ENOMEM; + + link_dump(link, f); + + /* Add terminating 0, so that the output buffer is a valid string. */ + fputc('\0', f); + + r = fflush_and_check(f); + } + if (r < 0) + return r; + + assert(s); + *ret = TAKE_PTR(s); + assert(sz > 0); + return (int) sz - 1; +} diff --git a/src/network/generator/network-generator.h b/src/network/generator/network-generator.h new file mode 100644 index 0000000..86bcaec --- /dev/null +++ b/src/network/generator/network-generator.h @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <net/ethernet.h> +#include <stdio.h> + +#include "hashmap.h" +#include "in-addr-util.h" +#include "list.h" + +typedef enum DHCPType { + DHCP_TYPE_NONE, + DHCP_TYPE_OFF, + DHCP_TYPE_ON, + DHCP_TYPE_ANY, + DHCP_TYPE_DHCP, + DHCP_TYPE_DHCP6, + DHCP_TYPE_AUTO6, + DHCP_TYPE_EITHER6, + DHCP_TYPE_IBFT, + _DHCP_TYPE_MAX, + _DHCP_TYPE_INVALID = -1, +} DHCPType; + +typedef struct Address Address; +typedef struct Link Link; +typedef struct NetDev NetDev; +typedef struct Network Network; +typedef struct Route Route; +typedef struct Context Context; + +struct Address { + Network *network; + + union in_addr_union address, peer; + unsigned char prefixlen; + int family; + + LIST_FIELDS(Address, addresses); +}; + +struct Route { + Network *network; + + union in_addr_union dest, gateway; + unsigned char prefixlen; + int family; + + LIST_FIELDS(Route, routes); +}; + +struct Network { + /* [Match] */ + char *ifname; + + /* [Link] */ + struct ether_addr mac; + uint32_t mtu; + + /* [Network] */ + DHCPType dhcp_type; + char **dns; + char *vlan; + char *bridge; + char *bond; + + /* [DHCP] */ + char *hostname; + int dhcp_use_dns; + + LIST_HEAD(Address, addresses); + LIST_HEAD(Route, routes); +}; + +struct NetDev { + /* [NetDev] */ + char *ifname; + char *kind; + uint32_t mtu; +}; + +struct Link { + /* [Match] */ + char *ifname; + struct ether_addr mac; +}; + +typedef struct Context { + Hashmap *networks_by_name; + Hashmap *netdevs_by_name; + Hashmap *links_by_name; +} Context; + +int parse_cmdline_item(const char *key, const char *value, void *data); +int context_merge_networks(Context *context); +void context_clear(Context *context); + +Network *network_get(Context *context, const char *ifname); +void network_dump(Network *network, FILE *f); +int network_format(Network *network, char **ret); + +NetDev *netdev_get(Context *context, const char *ifname); +void netdev_dump(NetDev *netdev, FILE *f); +int netdev_format(NetDev *netdev, char **ret); + +Link *link_get(Context *context, const char *ifname); +void link_dump(Link *link, FILE *f); +int link_format(Link *link, char **ret); diff --git a/src/network/generator/test-network-generator.c b/src/network/generator/test-network-generator.c new file mode 100644 index 0000000..e658d89 --- /dev/null +++ b/src/network/generator/test-network-generator.c @@ -0,0 +1,438 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "macro.h" +#include "network-generator.h" +#include "string-util.h" + +static void test_network_one(const char *ifname, const char *key, const char *value, const char *expected) { + _cleanup_(context_clear) Context context = {}; + _cleanup_free_ char *output = NULL; + Network *network; + + printf("# %s=%s\n", key, value); + assert_se(parse_cmdline_item(key, value, &context) >= 0); + assert_se(network = network_get(&context, ifname)); + assert_se(network_format(network, &output) >= 0); + puts(output); + assert_se(streq(output, expected)); +} + +static void test_network_two(const char *ifname, + const char *key1, const char *value1, + const char *key2, const char *value2, + const char *expected) { + _cleanup_(context_clear) Context context = {}; + _cleanup_free_ char *output = NULL; + Network *network; + + printf("# %s=%s\n", key1, value1); + printf("# %s=%s\n", key2, value2); + assert_se(parse_cmdline_item(key1, value1, &context) >= 0); + assert_se(parse_cmdline_item(key2, value2, &context) >= 0); + assert_se(context_merge_networks(&context) >= 0); + assert_se(network = network_get(&context, ifname)); + assert_se(network_format(network, &output) >= 0); + puts(output); + assert_se(streq(output, expected)); +} + +static void test_netdev_one(const char *ifname, const char *key, const char *value, const char *expected) { + _cleanup_(context_clear) Context context = {}; + _cleanup_free_ char *output = NULL; + NetDev *netdev; + + printf("# %s=%s\n", key, value); + assert_se(parse_cmdline_item(key, value, &context) >= 0); + assert_se(netdev = netdev_get(&context, ifname)); + assert_se(netdev_format(netdev, &output) >= 0); + puts(output); + assert_se(streq(output, expected)); +} + +static void test_link_one(const char *ifname, const char *key, const char *value, const char *expected) { + _cleanup_(context_clear) Context context = {}; + _cleanup_free_ char *output = NULL; + Link *link; + + printf("# %s=%s\n", key, value); + assert_se(parse_cmdline_item(key, value, &context) >= 0); + assert_se(link = link_get(&context, ifname)); + assert_se(link_format(link, &output) >= 0); + puts(output); + assert_se(streq(output, expected)); +} + +int main(int argc, char *argv[]) { + test_network_one("", "ip", "dhcp6", + "[Match]\n" + "Name=*\n" + "\n[Link]\n" + "\n[Network]\n" + "DHCP=ipv6\n" + "\n[DHCP]\n" + ); + + test_network_one("eth0", "ip", "eth0:dhcp", + "[Match]\n" + "Name=eth0\n" + "\n[Link]\n" + "\n[Network]\n" + "DHCP=ipv4\n" + "\n[DHCP]\n" + ); + + test_network_one("eth0", "ip", "eth0:dhcp:1530", + "[Match]\n" + "Name=eth0\n" + "\n[Link]\n" + "MTUBytes=1530\n" + "\n[Network]\n" + "DHCP=ipv4\n" + "\n[DHCP]\n" + ); + + test_network_one("eth0", "ip", "eth0:dhcp:1530:00:11:22:33:44:55", + "[Match]\n" + "Name=eth0\n" + "\n[Link]\n" + "MACAddress=00:11:22:33:44:55\n" + "MTUBytes=1530\n" + "\n[Network]\n" + "DHCP=ipv4\n" + "\n[DHCP]\n" + ); + + test_network_one("eth0", "ip", "192.168.0.10::192.168.0.1:255.255.255.0:hogehoge:eth0:on", + "[Match]\n" + "Name=eth0\n" + "\n[Link]\n" + "\n[Network]\n" + "DHCP=yes\n" + "\n[DHCP]\n" + "Hostname=hogehoge\n" + "\n[Address]\n" + "Address=192.168.0.10/24\n" + "\n[Route]\n" + "Gateway=192.168.0.1\n" + ); + + test_network_one("eth0", "ip", "192.168.0.10:192.168.0.2:192.168.0.1:255.255.255.0:hogehoge:eth0:on", + "[Match]\n" + "Name=eth0\n" + "\n[Link]\n" + "\n[Network]\n" + "DHCP=yes\n" + "\n[DHCP]\n" + "Hostname=hogehoge\n" + "\n[Address]\n" + "Address=192.168.0.10/24\n" + "Peer=192.168.0.2\n" + "\n[Route]\n" + "Gateway=192.168.0.1\n" + ); + + test_network_one("eth0", "ip", "192.168.0.10:192.168.0.2:192.168.0.1:255.255.255.0:hogehoge:eth0:on:1530", + "[Match]\n" + "Name=eth0\n" + "\n[Link]\n" + "MTUBytes=1530\n" + "\n[Network]\n" + "DHCP=yes\n" + "\n[DHCP]\n" + "Hostname=hogehoge\n" + "\n[Address]\n" + "Address=192.168.0.10/24\n" + "Peer=192.168.0.2\n" + "\n[Route]\n" + "Gateway=192.168.0.1\n" + ); + + test_network_one("eth0", "ip", "192.168.0.10:192.168.0.2:192.168.0.1:255.255.255.0:hogehoge:eth0:on:1530:00:11:22:33:44:55", + "[Match]\n" + "Name=eth0\n" + "\n[Link]\n" + "MACAddress=00:11:22:33:44:55\n" + "MTUBytes=1530\n" + "\n[Network]\n" + "DHCP=yes\n" + "\n[DHCP]\n" + "Hostname=hogehoge\n" + "\n[Address]\n" + "Address=192.168.0.10/24\n" + "Peer=192.168.0.2\n" + "\n[Route]\n" + "Gateway=192.168.0.1\n" + ); + + test_network_one("eth0", "ip", "192.168.0.10:192.168.0.2:192.168.0.1:255.255.255.0:hogehoge:eth0:on:10.10.10.10", + "[Match]\n" + "Name=eth0\n" + "\n[Link]\n" + "\n[Network]\n" + "DHCP=yes\n" + "DNS=10.10.10.10\n" + "\n[DHCP]\n" + "Hostname=hogehoge\n" + "\n[Address]\n" + "Address=192.168.0.10/24\n" + "Peer=192.168.0.2\n" + "\n[Route]\n" + "Gateway=192.168.0.1\n" + ); + + test_network_one("eth0", "ip", "192.168.0.10:192.168.0.2:192.168.0.1:255.255.255.0:hogehoge:eth0:on:10.10.10.10:10.10.10.11", + "[Match]\n" + "Name=eth0\n" + "\n[Link]\n" + "\n[Network]\n" + "DHCP=yes\n" + "DNS=10.10.10.10\n" + "DNS=10.10.10.11\n" + "\n[DHCP]\n" + "Hostname=hogehoge\n" + "\n[Address]\n" + "Address=192.168.0.10/24\n" + "Peer=192.168.0.2\n" + "\n[Route]\n" + "Gateway=192.168.0.1\n" + ); + + test_network_one("eth0", "ip", "[2001:1234:56:8f63::10]::[2001:1234:56:8f63::1]:64:hogehoge:eth0:on", + "[Match]\n" + "Name=eth0\n" + "\n[Link]\n" + "\n[Network]\n" + "DHCP=yes\n" + "\n[DHCP]\n" + "Hostname=hogehoge\n" + "\n[Address]\n" + "Address=2001:1234:56:8f63::10/64\n" + "\n[Route]\n" + "Gateway=2001:1234:56:8f63::1\n" + ); + + test_network_one("eth0", "ip", "[2001:1234:56:8f63::10]:[2001:1234:56:8f63::2]:[2001:1234:56:8f63::1]:64:hogehoge:eth0:on", + "[Match]\n" + "Name=eth0\n" + "\n[Link]\n" + "\n[Network]\n" + "DHCP=yes\n" + "\n[DHCP]\n" + "Hostname=hogehoge\n" + "\n[Address]\n" + "Address=2001:1234:56:8f63::10/64\n" + "Peer=2001:1234:56:8f63::2\n" + "\n[Route]\n" + "Gateway=2001:1234:56:8f63::1\n" + ); + + test_network_one("", "rd.route", "10.1.2.3/16:10.0.2.3", + "[Match]\n" + "Name=*\n" + "\n[Link]\n" + "\n[Network]\n" + "\n[DHCP]\n" + "\n[Route]\n" + "Destination=10.1.2.3/16\n" + "Gateway=10.0.2.3\n" + ); + + test_network_one("eth0", "rd.route", "10.1.2.3/16:10.0.2.3:eth0", + "[Match]\n" + "Name=eth0\n" + "\n[Link]\n" + "\n[Network]\n" + "\n[DHCP]\n" + "\n[Route]\n" + "Destination=10.1.2.3/16\n" + "Gateway=10.0.2.3\n" + ); + + test_network_one("", "nameserver", "10.1.2.3", + "[Match]\n" + "Name=*\n" + "\n[Link]\n" + "\n[Network]\n" + "DNS=10.1.2.3\n" + "\n[DHCP]\n" + ); + + test_network_one("", "rd.peerdns", "0", + "[Match]\n" + "Name=*\n" + "\n[Link]\n" + "\n[Network]\n" + "\n[DHCP]\n" + "UseDNS=no\n" + ); + + test_network_one("", "rd.peerdns", "1", + "[Match]\n" + "Name=*\n" + "\n[Link]\n" + "\n[Network]\n" + "\n[DHCP]\n" + "UseDNS=yes\n" + ); + + test_network_one("eth0", "vlan", "vlan99:eth0", + "[Match]\n" + "Name=eth0\n" + "\n[Link]\n" + "\n[Network]\n" + "VLAN=vlan99\n" + "\n[DHCP]\n" + ); + + test_network_one("eth0", "bridge", "bridge99:eth0,eth1", + "[Match]\n" + "Name=eth0\n" + "\n[Link]\n" + "\n[Network]\n" + "Bridge=bridge99\n" + "\n[DHCP]\n" + ); + + test_network_one("eth1", "bridge", "bridge99:eth0,eth1", + "[Match]\n" + "Name=eth1\n" + "\n[Link]\n" + "\n[Network]\n" + "Bridge=bridge99\n" + "\n[DHCP]\n" + ); + + test_network_one("eth0", "bond", "bond99:eth0,eth1", + "[Match]\n" + "Name=eth0\n" + "\n[Link]\n" + "\n[Network]\n" + "Bond=bond99\n" + "\n[DHCP]\n" + ); + + test_network_one("eth1", "bond", "bond99:eth0,eth1::1530", + "[Match]\n" + "Name=eth1\n" + "\n[Link]\n" + "\n[Network]\n" + "Bond=bond99\n" + "\n[DHCP]\n" + ); + + test_netdev_one("bond99", "bond", "bond99:eth0,eth1::1530", + "[NetDev]\n" + "Kind=bond\n" + "Name=bond99\n" + "MTUBytes=1530\n" + ); + + test_link_one("hogehoge", "ifname", "hogehoge:00:11:22:33:44:55", + "[Match]\n" + "MACAddress=00:11:22:33:44:55\n" + "\n[Link]\n" + "Name=hogehoge\n" + ); + + test_network_two("eth0", + "ip", "192.168.0.10:192.168.0.2:192.168.0.1:255.255.255.0:hogehoge:eth0:on:10.10.10.10:10.10.10.11", + "rd.route", "10.1.2.3/16:10.0.2.3", + "[Match]\n" + "Name=eth0\n" + "\n[Link]\n" + "\n[Network]\n" + "DHCP=yes\n" + "DNS=10.10.10.10\n" + "DNS=10.10.10.11\n" + "\n[DHCP]\n" + "Hostname=hogehoge\n" + "\n[Address]\n" + "Address=192.168.0.10/24\n" + "Peer=192.168.0.2\n" + "\n[Route]\n" + "Destination=10.1.2.3/16\n" + "Gateway=10.0.2.3\n" + "\n[Route]\n" + "Gateway=192.168.0.1\n" + ); + + test_network_two("eth0", + "ip", "192.168.0.10:192.168.0.2:192.168.0.1:255.255.255.0:hogehoge:eth0:on", + "nameserver", "10.1.2.3", + "[Match]\n" + "Name=eth0\n" + "\n[Link]\n" + "\n[Network]\n" + "DHCP=yes\n" + "DNS=10.1.2.3\n" + "\n[DHCP]\n" + "Hostname=hogehoge\n" + "\n[Address]\n" + "Address=192.168.0.10/24\n" + "Peer=192.168.0.2\n" + "\n[Route]\n" + "Gateway=192.168.0.1\n" + ); + + test_network_two("eth0", + "ip", "192.168.0.10:192.168.0.2:192.168.0.1:255.255.255.0:hogehoge:eth0:on:10.10.10.10:10.10.10.11", + "nameserver", "10.1.2.3", + "[Match]\n" + "Name=eth0\n" + "\n[Link]\n" + "\n[Network]\n" + "DHCP=yes\n" + "DNS=10.10.10.10\n" + "DNS=10.10.10.11\n" + "DNS=10.1.2.3\n" + "\n[DHCP]\n" + "Hostname=hogehoge\n" + "\n[Address]\n" + "Address=192.168.0.10/24\n" + "Peer=192.168.0.2\n" + "\n[Route]\n" + "Gateway=192.168.0.1\n" + ); + + test_network_two("eth0", + "ip", "192.168.0.10:192.168.0.2:192.168.0.1:255.255.255.0:hogehoge:eth0:on:10.10.10.10:10.10.10.11", + "rd.peerdns", "1", + "[Match]\n" + "Name=eth0\n" + "\n[Link]\n" + "\n[Network]\n" + "DHCP=yes\n" + "DNS=10.10.10.10\n" + "DNS=10.10.10.11\n" + "\n[DHCP]\n" + "Hostname=hogehoge\n" + "UseDNS=yes\n" + "\n[Address]\n" + "Address=192.168.0.10/24\n" + "Peer=192.168.0.2\n" + "\n[Route]\n" + "Gateway=192.168.0.1\n" + ); + + test_network_two("eth0", + "ip", "192.168.0.10:192.168.0.2:192.168.0.1:255.255.255.0:hogehoge:eth0:on:10.10.10.10:10.10.10.11", + "bridge", "bridge99:eth0,eth1", + "[Match]\n" + "Name=eth0\n" + "\n[Link]\n" + "\n[Network]\n" + "DHCP=yes\n" + "DNS=10.10.10.10\n" + "DNS=10.10.10.11\n" + "Bridge=bridge99\n" + "\n[DHCP]\n" + "Hostname=hogehoge\n" + "\n[Address]\n" + "Address=192.168.0.10/24\n" + "Peer=192.168.0.2\n" + "\n[Route]\n" + "Gateway=192.168.0.1\n" + ); + + return 0; +} diff --git a/src/network/meson.build b/src/network/meson.build new file mode 100644 index 0000000..f5ca183 --- /dev/null +++ b/src/network/meson.build @@ -0,0 +1,303 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +sources = files(''' + netdev/bareudp.c + netdev/bareudp.h + netdev/bond.c + netdev/bond.h + netdev/bridge.c + netdev/bridge.h + netdev/dummy.c + netdev/dummy.h + netdev/ifb.c + netdev/ifb.h + netdev/ipvlan.c + netdev/ipvlan.h + netdev/macvlan.c + netdev/macvlan.h + netdev/netdev.c + netdev/netdev.h + netdev/nlmon.c + netdev/nlmon.h + netdev/tunnel.c + netdev/tunnel.h + netdev/tuntap.c + netdev/tuntap.h + netdev/vcan.c + netdev/vcan.h + netdev/veth.c + netdev/veth.h + netdev/vlan.c + netdev/vlan.h + netdev/vrf.c + netdev/vrf.h + netdev/vxlan.c + netdev/vxlan.h + netdev/geneve.c + netdev/geneve.h + netdev/vxcan.c + netdev/vxcan.h + netdev/wireguard.c + netdev/wireguard.h + netdev/netdevsim.c + netdev/netdevsim.h + netdev/fou-tunnel.c + netdev/fou-tunnel.h + netdev/l2tp-tunnel.c + netdev/l2tp-tunnel.h + netdev/macsec.c + netdev/macsec.h + netdev/xfrm.c + netdev/xfrm.h + networkd-address-label.c + networkd-address-label.h + networkd-address-pool.c + networkd-address-pool.h + networkd-address.c + networkd-address.h + networkd-brvlan.c + networkd-brvlan.h + networkd-can.c + networkd-can.h + networkd-conf.c + networkd-conf.h + networkd-dhcp-common.c + networkd-dhcp-common.h + networkd-dhcp-server-bus.c + networkd-dhcp-server-bus.h + networkd-dhcp-server.c + networkd-dhcp-server.h + networkd-dhcp4.c + networkd-dhcp4.h + networkd-dhcp6.c + networkd-dhcp6.h + networkd-fdb.c + networkd-fdb.h + networkd-ipv4ll.c + networkd-ipv4ll.h + networkd-ipv6-proxy-ndp.c + networkd-ipv6-proxy-ndp.h + networkd-link-bus.c + networkd-link-bus.h + networkd-link.c + networkd-link.h + networkd-lldp-rx.c + networkd-lldp-rx.h + networkd-lldp-tx.c + networkd-lldp-tx.h + networkd-manager-bus.c + networkd-manager-bus.h + networkd-manager.c + networkd-manager.h + networkd-mdb.c + networkd-mdb.h + networkd-ndisc.c + networkd-ndisc.h + networkd-neighbor.c + networkd-neighbor.h + networkd-radv.c + networkd-radv.h + networkd-network-bus.c + networkd-network-bus.h + networkd-network.c + networkd-network.h + networkd-nexthop.c + networkd-nexthop.h + networkd-route.c + networkd-route.h + networkd-routing-policy-rule.c + networkd-routing-policy-rule.h + networkd-speed-meter.c + networkd-speed-meter.h + networkd-sriov.c + networkd-sriov.h + networkd-sysctl.c + networkd-sysctl.h + networkd-util.c + networkd-util.h + networkd-wifi.c + networkd-wifi.h + tc/cake.c + tc/cake.h + tc/codel.c + tc/codel.h + tc/drr.c + tc/drr.h + tc/ets.c + tc/ets.h + tc/fifo.c + tc/fifo.h + tc/fq.c + tc/fq.h + tc/fq-codel.c + tc/fq-codel.h + tc/fq-pie.c + tc/fq-pie.h + tc/gred.c + tc/gred.h + tc/hhf.c + tc/hhf.h + tc/htb.c + tc/htb.h + tc/netem.c + tc/netem.h + tc/pie.c + tc/pie.h + tc/qdisc.c + tc/qdisc.h + tc/qfq.c + tc/qfq.h + tc/sfb.c + tc/sfb.h + tc/sfq.c + tc/sfq.h + tc/tbf.c + tc/tbf.h + tc/tc-util.c + tc/tc-util.h + tc/tc.c + tc/tc.h + tc/tclass.c + tc/tclass.h + tc/teql.c + tc/teql.h +'''.split()) + +systemd_networkd_sources = files('networkd.c') + +systemd_networkd_wait_online_sources = files(''' + wait-online/link.c + wait-online/link.h + wait-online/manager.c + wait-online/manager.h + wait-online/wait-online.c +'''.split()) + network_internal_h + +networkctl_sources = files('networkctl.c') + +network_generator_sources = files(''' + generator/main.c + generator/network-generator.c + generator/network-generator.h +'''.split()) + +network_include_dir = [includes, include_directories(['.', 'netdev', 'tc'])] + +if conf.get('ENABLE_NETWORKD') == 1 + if get_option('link-networkd-shared') + networkd_link_with = [libshared] + else + networkd_link_with = [libsystemd_static, + libshared_static, + libjournal_client, + libbasic_gcrypt] + endif + + networkd_gperf_c = custom_target( + 'networkd-gperf.c', + input : 'networkd-gperf.gperf', + output : 'networkd-gperf.c', + command : [gperf, '@INPUT@', '--output-file', '@OUTPUT@']) + + networkd_network_gperf_c = custom_target( + 'networkd-network-gperf.c', + input : 'networkd-network-gperf.gperf', + output : 'networkd-network-gperf.c', + command : [gperf, '@INPUT@', '--output-file', '@OUTPUT@']) + + netdev_gperf_c = custom_target( + 'netdev-gperf.c', + input : 'netdev/netdev-gperf.gperf', + output : 'netdev-gperf.c', + command : [gperf, '@INPUT@', '--output-file', '@OUTPUT@']) + + libnetworkd_core = static_library( + 'networkd-core', + sources, + network_internal_h, + networkd_gperf_c, + networkd_network_gperf_c, + netdev_gperf_c, + include_directories : network_include_dir, + link_with : [networkd_link_with]) + + install_data('org.freedesktop.network1.conf', + install_dir : dbuspolicydir) + install_data('org.freedesktop.network1.service', + install_dir : dbussystemservicedir) + install_data('org.freedesktop.network1.policy', + install_dir : polkitpolicydir) + if install_polkit + install_data('systemd-networkd.rules', + install_dir : polkitrulesdir) + endif + if install_polkit_pkla + install_data('systemd-networkd.pkla', + install_dir : polkitpkladir) + endif + + if install_sysconfdir + install_data('networkd.conf', + install_dir : pkgsysconfdir) + endif + + fuzzers += [ + [['src/network/fuzz-netdev-parser.c', + 'src/fuzz/fuzz.h'], + [libnetworkd_core, + libudev_static, + libsystemd_network, + networkd_link_with], + [threads], + [], + network_include_dir], + + [['src/network/fuzz-network-parser.c', + 'src/fuzz/fuzz.h'], + [libnetworkd_core, + libudev_static, + libsystemd_network, + networkd_link_with], + [threads], + [], + network_include_dir], + ] + + tests += [ + [['src/network/test-networkd-conf.c'], + [libnetworkd_core, + libsystemd_network, + libudev], + [], '', '', [], network_include_dir], + + [['src/network/test-network.c'], + [libnetworkd_core, + libudev_static, + libsystemd_network, + networkd_link_with], + [threads], + '', '', [], network_include_dir], + + [['src/network/test-routing-policy-rule.c'], + [libnetworkd_core, + libsystemd_network, + libudev], + [], '', '', [], network_include_dir], + + [['src/network/test-network-tables.c', + test_tables_h], + [libnetworkd_core, + libudev_static, + libsystemd_network, + networkd_link_with], + [threads], + '', '', [], + [network_include_dir]], + + [['src/network/generator/test-network-generator.c', + 'src/network/generator/network-generator.c', + 'src/network/generator/network-generator.h'], + [networkd_link_with], + [], '', '', [], network_include_dir], + ] +endif diff --git a/src/network/netdev/bareudp.c b/src/network/netdev/bareudp.c new file mode 100644 index 0000000..22c0e49 --- /dev/null +++ b/src/network/netdev/bareudp.c @@ -0,0 +1,138 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2020 VMware, Inc. */ + +#include "bareudp.h" +#include "netlink-util.h" +#include "networkd-manager.h" +#include "string-table.h" + +static const char* const bare_udp_protocol_table[_BARE_UDP_PROTOCOL_MAX] = { + [BARE_UDP_PROTOCOL_IPV4] = "ipv4", + [BARE_UDP_PROTOCOL_IPV6] = "ipv6", + [BARE_UDP_PROTOCOL_MPLS_UC] = "mpls-uc", + [BARE_UDP_PROTOCOL_MPLS_MC] = "mpls-mc", +}; + +DEFINE_STRING_TABLE_LOOKUP(bare_udp_protocol, BareUDPProtocol); +DEFINE_CONFIG_PARSE_ENUM(config_parse_bare_udp_iftype, bare_udp_protocol, BareUDPProtocol, + "Failed to parse EtherType="); + +/* callback for bareudp netdev's created without a backing Link */ +static int bare_udp_netdev_create_handler(sd_netlink *rtnl, sd_netlink_message *m, NetDev *netdev) { + int r; + + assert(netdev); + assert(netdev->state != _NETDEV_STATE_INVALID); + + r = sd_netlink_message_get_errno(m); + if (r == -EEXIST) + log_netdev_info(netdev, "BareUDP netdev exists, using existing without changing its parameters."); + else if (r < 0) { + log_netdev_warning_errno(netdev, r, "BareUDP netdev could not be created: %m"); + netdev_drop(netdev); + + return 1; + } + + log_netdev_debug(netdev, "BareUDP created."); + + return 1; +} + +static int netdev_bare_udp_create(NetDev *netdev) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + BareUDP *u; + int r; + + assert(netdev); + + u = BAREUDP(netdev); + + assert(u); + + r = sd_rtnl_message_new_link(netdev->manager->rtnl, &m, RTM_NEWLINK, 0); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not allocate RTM_NEWLINK message: %m"); + + r = sd_netlink_message_append_string(m, IFLA_IFNAME, netdev->ifname); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IFNAME, attribute: %m"); + + r = sd_netlink_message_open_container(m, IFLA_LINKINFO); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_LINKINFO attribute: %m"); + + r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, netdev_kind_to_string(netdev->kind)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_INFO_DATA attribute: %m"); + + r = sd_netlink_message_append_u16(m, IFLA_BAREUDP_ETHERTYPE, htobe16(u->iftype)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BAREUDP_ETHERTYPE attribute: %m"); + + r = sd_netlink_message_append_u16(m, IFLA_BAREUDP_PORT, htobe16(u->dest_port)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BAREUDP_PORT attribute: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_INFO_DATA attribute: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_LINKINFO attribute: %m"); + + r = netlink_call_async(netdev->manager->rtnl, NULL, m, bare_udp_netdev_create_handler, + netdev_destroy_callback, netdev); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not send rtnetlink message: %m"); + + netdev_ref(netdev); + netdev->state = NETDEV_STATE_CREATING; + + log_netdev_debug(netdev, "Creating"); + + return r; +} + +static int netdev_bare_udp_verify(NetDev *netdev, const char *filename) { + BareUDP *u; + + assert(netdev); + assert(filename); + + u = BAREUDP(netdev); + + assert(u); + + if (u->dest_port == 0) + return log_netdev_warning_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s: BareUDP DesinationPort= is not set. Ignoring.", filename); + + if (u->iftype == _BARE_UDP_PROTOCOL_INVALID) + return log_netdev_warning_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s: BareUDP EtherType= is not set. Ignoring.", filename); + + return 0; +} + +static void bare_udp_init(NetDev *netdev) { + BareUDP *u; + + assert(netdev); + + u = BAREUDP(netdev); + + assert(u); + + u->iftype = _BARE_UDP_PROTOCOL_INVALID; +} + +const NetDevVTable bare_udp_vtable = { + .object_size = sizeof(BareUDP), + .sections = NETDEV_COMMON_SECTIONS "BareUDP\0", + .init = bare_udp_init, + .config_verify = netdev_bare_udp_verify, + .create = netdev_bare_udp_create, + .create_type = NETDEV_CREATE_INDEPENDENT, +}; diff --git a/src/network/netdev/bareudp.h b/src/network/netdev/bareudp.h new file mode 100644 index 0000000..ea80bbf --- /dev/null +++ b/src/network/netdev/bareudp.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2020 VMware, Inc. */ +#pragma once + +typedef struct BareUDP BareUDP; + +#include <linux/if_ether.h> + +#include "conf-parser.h" +#include "netdev.h" + +typedef enum BareUDPProtocol { + BARE_UDP_PROTOCOL_IPV4 = ETH_P_IP, + BARE_UDP_PROTOCOL_IPV6 = ETH_P_IPV6, + BARE_UDP_PROTOCOL_MPLS_UC = ETH_P_MPLS_UC, + BARE_UDP_PROTOCOL_MPLS_MC = ETH_P_MPLS_MC, + _BARE_UDP_PROTOCOL_MAX, + _BARE_UDP_PROTOCOL_INVALID = -1 +} BareUDPProtocol; + +struct BareUDP { + NetDev meta; + + BareUDPProtocol iftype; + uint16_t dest_port; +}; + +DEFINE_NETDEV_CAST(BAREUDP, BareUDP); +extern const NetDevVTable bare_udp_vtable; + +const char *bare_udp_protocol_to_string(BareUDPProtocol d) _const_; +BareUDPProtocol bare_udp_protocol_from_string(const char *d) _pure_; + +CONFIG_PARSER_PROTOTYPE(config_parse_bare_udp_iftype); diff --git a/src/network/netdev/bond.c b/src/network/netdev/bond.c new file mode 100644 index 0000000..e27f360 --- /dev/null +++ b/src/network/netdev/bond.c @@ -0,0 +1,527 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "bond.h" +#include "bond-util.h" +#include "conf-parser.h" +#include "ether-addr-util.h" +#include "extract-word.h" +#include "netlink-util.h" +#include "networkd-manager.h" +#include "string-table.h" + +/* + * Number of seconds between instances where the bonding + * driver sends learning packets to each slaves peer switch + */ +#define LEARNING_PACKETS_INTERVAL_MIN_SEC (1 * USEC_PER_SEC) +#define LEARNING_PACKETS_INTERVAL_MAX_SEC (0x7fffffff * USEC_PER_SEC) + +/* Number of IGMP membership reports to be issued after + * a failover event. + */ +#define RESEND_IGMP_MIN 0 +#define RESEND_IGMP_MAX 255 +#define RESEND_IGMP_DEFAULT 1 + +/* + * Number of packets to transmit through a slave before + * moving to the next one. + */ +#define PACKETS_PER_SLAVE_MIN 0 +#define PACKETS_PER_SLAVE_MAX 65535 +#define PACKETS_PER_SLAVE_DEFAULT 1 + +/* + * Number of peer notifications (gratuitous ARPs and + * unsolicited IPv6 Neighbor Advertisements) to be issued after a + * failover event. + */ +#define GRATUITOUS_ARP_MIN 0 +#define GRATUITOUS_ARP_MAX 255 +#define GRATUITOUS_ARP_DEFAULT 1 + +DEFINE_CONFIG_PARSE_ENUM(config_parse_bond_mode, bond_mode, BondMode, "Failed to parse bond mode"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_bond_xmit_hash_policy, + bond_xmit_hash_policy, + BondXmitHashPolicy, + "Failed to parse bond transmit hash policy"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_bond_lacp_rate, bond_lacp_rate, BondLacpRate, "Failed to parse bond lacp rate"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_bond_ad_select, bond_ad_select, BondAdSelect, "Failed to parse bond AD select"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_bond_fail_over_mac, bond_fail_over_mac, BondFailOverMac, "Failed to parse bond fail over MAC"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_bond_arp_validate, bond_arp_validate, BondArpValidate, "Failed to parse bond arp validate"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_bond_arp_all_targets, bond_arp_all_targets, BondArpAllTargets, "Failed to parse bond Arp all targets"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_bond_primary_reselect, bond_primary_reselect, BondPrimaryReselect, "Failed to parse bond primary reselect"); + +static int netdev_bond_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) { + Bond *b; + int r; + + assert(netdev); + assert(!link); + assert(m); + + b = BOND(netdev); + + assert(b); + + if (b->mode != _NETDEV_BOND_MODE_INVALID) { + r = sd_netlink_message_append_u8(m, IFLA_BOND_MODE, b->mode); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_MODE attribute: %m"); + } + + if (b->xmit_hash_policy != _NETDEV_BOND_XMIT_HASH_POLICY_INVALID) { + r = sd_netlink_message_append_u8(m, IFLA_BOND_XMIT_HASH_POLICY, b->xmit_hash_policy); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_XMIT_HASH_POLICY attribute: %m"); + } + + if (b->lacp_rate != _NETDEV_BOND_LACP_RATE_INVALID && + b->mode == NETDEV_BOND_MODE_802_3AD) { + r = sd_netlink_message_append_u8(m, IFLA_BOND_AD_LACP_RATE, b->lacp_rate); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_AD_LACP_RATE attribute: %m"); + } + + if (b->miimon != 0) { + r = sd_netlink_message_append_u32(m, IFLA_BOND_MIIMON, b->miimon / USEC_PER_MSEC); + if (r < 0) + log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_BOND_MIIMON attribute: %m"); + } + + if (b->downdelay != 0) { + r = sd_netlink_message_append_u32(m, IFLA_BOND_DOWNDELAY, b->downdelay / USEC_PER_MSEC); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_DOWNDELAY attribute: %m"); + } + + if (b->updelay != 0) { + r = sd_netlink_message_append_u32(m, IFLA_BOND_UPDELAY, b->updelay / USEC_PER_MSEC); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_UPDELAY attribute: %m"); + } + + if (b->arp_interval != 0) { + r = sd_netlink_message_append_u32(m, IFLA_BOND_ARP_INTERVAL, b->arp_interval / USEC_PER_MSEC); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_ARP_INTERVAL attribute: %m"); + + if (b->lp_interval >= LEARNING_PACKETS_INTERVAL_MIN_SEC && + b->lp_interval <= LEARNING_PACKETS_INTERVAL_MAX_SEC) { + r = sd_netlink_message_append_u32(m, IFLA_BOND_LP_INTERVAL, b->lp_interval / USEC_PER_SEC); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_LP_INTERVAL attribute: %m"); + } + } + + if (b->ad_select != _NETDEV_BOND_AD_SELECT_INVALID && + b->mode == NETDEV_BOND_MODE_802_3AD) { + r = sd_netlink_message_append_u8(m, IFLA_BOND_AD_SELECT, b->ad_select); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_AD_SELECT attribute: %m"); + } + + if (b->fail_over_mac != _NETDEV_BOND_FAIL_OVER_MAC_INVALID && + b->mode == NETDEV_BOND_MODE_ACTIVE_BACKUP) { + r = sd_netlink_message_append_u8(m, IFLA_BOND_FAIL_OVER_MAC, b->fail_over_mac); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_FAIL_OVER_MAC attribute: %m"); + } + + if (b->arp_validate != _NETDEV_BOND_ARP_VALIDATE_INVALID) { + r = sd_netlink_message_append_u32(m, IFLA_BOND_ARP_VALIDATE, b->arp_validate); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_ARP_VALIDATE attribute: %m"); + } + + if (b->arp_all_targets != _NETDEV_BOND_ARP_ALL_TARGETS_INVALID) { + r = sd_netlink_message_append_u32(m, IFLA_BOND_ARP_ALL_TARGETS, b->arp_all_targets); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_ARP_ALL_TARGETS attribute: %m"); + } + + if (b->primary_reselect != _NETDEV_BOND_PRIMARY_RESELECT_INVALID) { + r = sd_netlink_message_append_u8(m, IFLA_BOND_PRIMARY_RESELECT, b->primary_reselect); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_PRIMARY_RESELECT attribute: %m"); + } + + if (b->resend_igmp <= RESEND_IGMP_MAX) { + r = sd_netlink_message_append_u32(m, IFLA_BOND_RESEND_IGMP, b->resend_igmp); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_RESEND_IGMP attribute: %m"); + } + + if (b->packets_per_slave <= PACKETS_PER_SLAVE_MAX && + b->mode == NETDEV_BOND_MODE_BALANCE_RR) { + r = sd_netlink_message_append_u32(m, IFLA_BOND_PACKETS_PER_SLAVE, b->packets_per_slave); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_PACKETS_PER_SLAVE attribute: %m"); + } + + if (b->num_grat_arp <= GRATUITOUS_ARP_MAX) { + r = sd_netlink_message_append_u8(m, IFLA_BOND_NUM_PEER_NOTIF, b->num_grat_arp); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_NUM_PEER_NOTIF attribute: %m"); + } + + if (b->min_links != 0) { + r = sd_netlink_message_append_u32(m, IFLA_BOND_MIN_LINKS, b->min_links); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_MIN_LINKS attribute: %m"); + } + + if (b->ad_actor_sys_prio != 0) { + r = sd_netlink_message_append_u16(m, IFLA_BOND_AD_ACTOR_SYS_PRIO, b->ad_actor_sys_prio); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_AD_ACTOR_SYS_PRIO attribute: %m"); + } + + if (b->ad_user_port_key != 0) { + r = sd_netlink_message_append_u16(m, IFLA_BOND_AD_USER_PORT_KEY, b->ad_user_port_key); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_AD_USER_PORT_KEY attribute: %m"); + } + + if (!ether_addr_is_null(&b->ad_actor_system)) { + r = sd_netlink_message_append_ether_addr(m, IFLA_BOND_AD_ACTOR_SYSTEM, &b->ad_actor_system); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_AD_ACTOR_SYSTEM attribute: %m"); + } + + r = sd_netlink_message_append_u8(m, IFLA_BOND_ALL_SLAVES_ACTIVE, b->all_slaves_active); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_ALL_SLAVES_ACTIVE attribute: %m"); + + if (b->tlb_dynamic_lb >= 0) { + r = sd_netlink_message_append_u8(m, IFLA_BOND_TLB_DYNAMIC_LB, b->tlb_dynamic_lb); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_TLB_DYNAMIC_LB attribute: %m"); + } + + if (b->arp_interval > 0 && !ordered_set_isempty(b->arp_ip_targets)) { + void *val; + int n = 0; + + r = sd_netlink_message_open_container(m, IFLA_BOND_ARP_IP_TARGET); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not open contaniner IFLA_BOND_ARP_IP_TARGET : %m"); + + ORDERED_SET_FOREACH(val, b->arp_ip_targets) { + r = sd_netlink_message_append_u32(m, n++, PTR_TO_UINT32(val)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_ARP_ALL_TARGETS attribute: %m"); + } + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not close contaniner IFLA_BOND_ARP_IP_TARGET : %m"); + } + + return 0; +} + +static int link_set_bond_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(m); + assert(link); + assert(link->ifname); + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0) { + log_link_warning_errno(link, r, "Could not set bonding interface: %m"); + return 1; + } + + return 1; +} + +int link_set_bond(Link *link) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(link); + assert(link->network); + + r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_NEWLINK, link->network->bond->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m"); + + r = sd_netlink_message_set_flags(req, NLM_F_REQUEST | NLM_F_ACK); + if (r < 0) + return log_link_error_errno(link, r, "Could not set netlink flags: %m"); + + r = sd_netlink_message_open_container(req, IFLA_LINKINFO); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_PROTINFO attribute: %m"); + + r = sd_netlink_message_open_container_union(req, IFLA_INFO_DATA, "bond"); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_INFO_DATA attribute: %m"); + + if (link->network->active_slave) { + r = sd_netlink_message_append_u32(req, IFLA_BOND_ACTIVE_SLAVE, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_BOND_ACTIVE_SLAVE attribute: %m"); + } + + if (link->network->primary_slave) { + r = sd_netlink_message_append_u32(req, IFLA_BOND_PRIMARY, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_BOND_PRIMARY attribute: %m"); + } + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_LINKINFO attribute: %m"); + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_INFO_DATA attribute: %m"); + + r = netlink_call_async(link->manager->rtnl, NULL, req, link_set_bond_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + return r; +} + +int config_parse_arp_ip_target_address( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Bond *b = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + b->arp_ip_targets = ordered_set_free(b->arp_ip_targets); + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *n = NULL; + union in_addr_union ip; + + r = extract_first_word(&p, &n, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse Bond ARP IP target address, ignoring assignment: %s", + rvalue); + return 0; + } + if (r == 0) + return 0; + + r = in_addr_from_string(AF_INET, n, &ip); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Bond ARP IP target address is invalid, ignoring assignment: %s", n); + continue; + } + + r = ordered_set_ensure_allocated(&b->arp_ip_targets, NULL); + if (r < 0) + return log_oom(); + + if (ordered_set_size(b->arp_ip_targets) >= NETDEV_BOND_ARP_TARGETS_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Too many ARP IP targets are specified. The maximum number is %d. Ignoring assignment: %s", + NETDEV_BOND_ARP_TARGETS_MAX, n); + continue; + } + + r = ordered_set_put(b->arp_ip_targets, UINT32_TO_PTR(ip.in.s_addr)); + if (r == -EEXIST) + log_syntax(unit, LOG_WARNING, filename, line, r, + "Bond ARP IP target address is duplicated, ignoring assignment: %s", n); + if (r < 0) + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to store bond ARP IP target address '%s', ignoring assignment: %m", n); + } +} + +int config_parse_ad_actor_sys_prio( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + Bond *b = userdata; + uint16_t v; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = safe_atou16(rvalue, &v); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse actor system priority '%s', ignoring: %m", rvalue); + return 0; + } + + if (v == 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Failed to parse actor system priority '%s'. Range is [1,65535], ignoring.", + rvalue); + return 0; + } + + b->ad_actor_sys_prio = v; + + return 0; +} + +int config_parse_ad_user_port_key( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + Bond *b = userdata; + uint16_t v; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = safe_atou16(rvalue, &v); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse user port key '%s', ignoring: %m", rvalue); + return 0; + } + + if (v > 1023) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Failed to parse user port key '%s'. Range is [0…1023], ignoring.", rvalue); + return 0; + } + + b->ad_user_port_key = v; + + return 0; +} + +int config_parse_ad_actor_system( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + Bond *b = userdata; + struct ether_addr n; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = ether_addr_from_string(rvalue, &n); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Not a valid MAC address %s. Ignoring assignment: %m", + rvalue); + return 0; + } + if (ether_addr_is_null(&n) || (n.ether_addr_octet[0] & 0x01)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Not an appropriate MAC address %s, cannot be null or multicast. Ignoring assignment.", + rvalue); + return 0; + } + + b->ad_actor_system = n; + + return 0; +} + +static void bond_done(NetDev *netdev) { + Bond *b; + + assert(netdev); + b = BOND(netdev); + assert(b); + + ordered_set_free(b->arp_ip_targets); +} + +static void bond_init(NetDev *netdev) { + Bond *b; + + assert(netdev); + + b = BOND(netdev); + + assert(b); + + b->mode = _NETDEV_BOND_MODE_INVALID; + b->xmit_hash_policy = _NETDEV_BOND_XMIT_HASH_POLICY_INVALID; + b->lacp_rate = _NETDEV_BOND_LACP_RATE_INVALID; + b->ad_select = _NETDEV_BOND_AD_SELECT_INVALID; + b->fail_over_mac = _NETDEV_BOND_FAIL_OVER_MAC_INVALID; + b->arp_validate = _NETDEV_BOND_ARP_VALIDATE_INVALID; + b->arp_all_targets = _NETDEV_BOND_ARP_ALL_TARGETS_INVALID; + b->primary_reselect = _NETDEV_BOND_PRIMARY_RESELECT_INVALID; + + b->all_slaves_active = false; + b->tlb_dynamic_lb = -1; + + b->resend_igmp = RESEND_IGMP_DEFAULT; + b->packets_per_slave = PACKETS_PER_SLAVE_DEFAULT; + b->num_grat_arp = GRATUITOUS_ARP_DEFAULT; + b->lp_interval = LEARNING_PACKETS_INTERVAL_MIN_SEC; +} + +const NetDevVTable bond_vtable = { + .object_size = sizeof(Bond), + .init = bond_init, + .done = bond_done, + .sections = NETDEV_COMMON_SECTIONS "Bond\0", + .fill_message_create = netdev_bond_fill_message_create, + .create_type = NETDEV_CREATE_MASTER, + .generate_mac = true, +}; diff --git a/src/network/netdev/bond.h b/src/network/netdev/bond.h new file mode 100644 index 0000000..11d3e9b --- /dev/null +++ b/src/network/netdev/bond.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <netinet/in.h> +#include <linux/if_bonding.h> + +#include "bond-util.h" +#include "macro.h" +#include "netdev.h" +#include "ordered-set.h" + +typedef struct Bond { + NetDev meta; + + BondMode mode; + BondXmitHashPolicy xmit_hash_policy; + BondLacpRate lacp_rate; + BondAdSelect ad_select; + BondFailOverMac fail_over_mac; + BondArpValidate arp_validate; + BondArpAllTargets arp_all_targets; + BondPrimaryReselect primary_reselect; + + int tlb_dynamic_lb; + + bool all_slaves_active; + + unsigned resend_igmp; + unsigned packets_per_slave; + unsigned num_grat_arp; + unsigned min_links; + + uint16_t ad_actor_sys_prio; + uint16_t ad_user_port_key; + struct ether_addr ad_actor_system; + + usec_t miimon; + usec_t updelay; + usec_t downdelay; + usec_t arp_interval; + usec_t lp_interval; + + OrderedSet *arp_ip_targets; +} Bond; + +DEFINE_NETDEV_CAST(BOND, Bond); +extern const NetDevVTable bond_vtable; + +int link_set_bond(Link *link); + +CONFIG_PARSER_PROTOTYPE(config_parse_bond_mode); +CONFIG_PARSER_PROTOTYPE(config_parse_bond_xmit_hash_policy); +CONFIG_PARSER_PROTOTYPE(config_parse_bond_lacp_rate); +CONFIG_PARSER_PROTOTYPE(config_parse_bond_ad_select); +CONFIG_PARSER_PROTOTYPE(config_parse_bond_fail_over_mac); +CONFIG_PARSER_PROTOTYPE(config_parse_bond_arp_validate); +CONFIG_PARSER_PROTOTYPE(config_parse_bond_arp_all_targets); +CONFIG_PARSER_PROTOTYPE(config_parse_bond_primary_reselect); +CONFIG_PARSER_PROTOTYPE(config_parse_arp_ip_target_address); +CONFIG_PARSER_PROTOTYPE(config_parse_ad_actor_sys_prio); +CONFIG_PARSER_PROTOTYPE(config_parse_ad_user_port_key); +CONFIG_PARSER_PROTOTYPE(config_parse_ad_actor_system); diff --git a/src/network/netdev/bridge.c b/src/network/netdev/bridge.c new file mode 100644 index 0000000..1f59cd8 --- /dev/null +++ b/src/network/netdev/bridge.c @@ -0,0 +1,368 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <net/if.h> + +#include "bridge.h" +#include "netlink-util.h" +#include "network-internal.h" +#include "networkd-manager.h" +#include "string-table.h" +#include "vlan-util.h" + +static const char* const multicast_router_table[_MULTICAST_ROUTER_MAX] = { + [MULTICAST_ROUTER_NONE] = "no", + [MULTICAST_ROUTER_TEMPORARY_QUERY] = "query", + [MULTICAST_ROUTER_PERMANENT] = "permanent", + [MULTICAST_ROUTER_TEMPORARY] = "temporary", +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(multicast_router, MulticastRouter, _MULTICAST_ROUTER_INVALID); +DEFINE_CONFIG_PARSE_ENUM(config_parse_multicast_router, multicast_router, MulticastRouter, + "Failed to parse bridge multicast router setting"); + +/* callback for bridge netdev's parameter set */ +static int netdev_bridge_set_handler(sd_netlink *rtnl, sd_netlink_message *m, NetDev *netdev) { + int r; + + assert(netdev); + assert(m); + + r = sd_netlink_message_get_errno(m); + if (r < 0) { + log_netdev_warning_errno(netdev, r, "Bridge parameters could not be set: %m"); + return 1; + } + + log_netdev_debug(netdev, "Bridge parameters set success"); + + return 1; +} + +static int netdev_bridge_post_create(NetDev *netdev, Link *link, sd_netlink_message *m) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + Bridge *b; + int r; + + assert(netdev); + + b = BRIDGE(netdev); + + assert(b); + + r = sd_rtnl_message_new_link(netdev->manager->rtnl, &req, RTM_NEWLINK, netdev->ifindex); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not allocate RTM_SETLINK message: %m"); + + r = sd_netlink_message_set_flags(req, NLM_F_REQUEST | NLM_F_ACK); + if (r < 0) + return log_link_error_errno(link, r, "Could not set netlink flags: %m"); + + r = sd_netlink_message_open_container(req, IFLA_LINKINFO); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_LINKINFO attribute: %m"); + + r = sd_netlink_message_open_container_union(req, IFLA_INFO_DATA, netdev_kind_to_string(netdev->kind)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_INFO_DATA attribute: %m"); + + /* convert to jiffes */ + if (b->forward_delay != USEC_INFINITY) { + r = sd_netlink_message_append_u32(req, IFLA_BR_FORWARD_DELAY, usec_to_jiffies(b->forward_delay)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_FORWARD_DELAY attribute: %m"); + } + + if (b->hello_time > 0) { + r = sd_netlink_message_append_u32(req, IFLA_BR_HELLO_TIME, usec_to_jiffies(b->hello_time)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_HELLO_TIME attribute: %m"); + } + + if (b->max_age > 0) { + r = sd_netlink_message_append_u32(req, IFLA_BR_MAX_AGE, usec_to_jiffies(b->max_age)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_MAX_AGE attribute: %m"); + } + + if (b->ageing_time != USEC_INFINITY) { + r = sd_netlink_message_append_u32(req, IFLA_BR_AGEING_TIME, usec_to_jiffies(b->ageing_time)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_AGEING_TIME attribute: %m"); + } + + if (b->priority > 0) { + r = sd_netlink_message_append_u16(req, IFLA_BR_PRIORITY, b->priority); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_PRIORITY attribute: %m"); + } + + if (b->group_fwd_mask > 0) { + r = sd_netlink_message_append_u16(req, IFLA_BR_GROUP_FWD_MASK, b->group_fwd_mask); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_GROUP_FWD_MASK attribute: %m"); + } + + if (b->default_pvid != VLANID_INVALID) { + r = sd_netlink_message_append_u16(req, IFLA_BR_VLAN_DEFAULT_PVID, b->default_pvid); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_VLAN_DEFAULT_PVID attribute: %m"); + } + + if (b->mcast_querier >= 0) { + r = sd_netlink_message_append_u8(req, IFLA_BR_MCAST_QUERIER, b->mcast_querier); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_MCAST_QUERIER attribute: %m"); + } + + if (b->mcast_snooping >= 0) { + r = sd_netlink_message_append_u8(req, IFLA_BR_MCAST_SNOOPING, b->mcast_snooping); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_MCAST_SNOOPING attribute: %m"); + } + + if (b->vlan_filtering >= 0) { + r = sd_netlink_message_append_u8(req, IFLA_BR_VLAN_FILTERING, b->vlan_filtering); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_VLAN_FILTERING attribute: %m"); + } + + if (b->vlan_protocol >= 0) { + r = sd_netlink_message_append_u16(req, IFLA_BR_VLAN_PROTOCOL, b->vlan_protocol); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_VLAN_PROTOCOL attribute: %m"); + } + + if (b->stp >= 0) { + r = sd_netlink_message_append_u32(req, IFLA_BR_STP_STATE, b->stp); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_STP_STATE attribute: %m"); + } + + if (b->igmp_version > 0) { + r = sd_netlink_message_append_u8(req, IFLA_BR_MCAST_IGMP_VERSION, b->igmp_version); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_MCAST_IGMP_VERSION attribute: %m"); + } + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_LINKINFO attribute: %m"); + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_INFO_DATA attribute: %m"); + + r = netlink_call_async(netdev->manager->rtnl, NULL, req, netdev_bridge_set_handler, + netdev_destroy_callback, netdev); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not send rtnetlink message: %m"); + + netdev_ref(netdev); + + return r; +} + +static int link_set_bridge_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(m); + assert(link); + assert(link->ifname); + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0) { + log_link_warning_errno(link, r, "Could not set bridge interface: %m"); + return 1; + } + + return 1; +} + +int link_set_bridge(Link *link) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(link); + assert(link->network); + + r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m"); + + r = sd_rtnl_message_link_set_family(req, AF_BRIDGE); + if (r < 0) + return log_link_error_errno(link, r, "Could not set message family: %m"); + + r = sd_netlink_message_open_container(req, IFLA_PROTINFO); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_PROTINFO attribute: %m"); + + if (link->network->use_bpdu >= 0) { + r = sd_netlink_message_append_u8(req, IFLA_BRPORT_GUARD, link->network->use_bpdu); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_GUARD attribute: %m"); + } + + if (link->network->hairpin >= 0) { + r = sd_netlink_message_append_u8(req, IFLA_BRPORT_MODE, link->network->hairpin); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_MODE attribute: %m"); + } + + if (link->network->fast_leave >= 0) { + r = sd_netlink_message_append_u8(req, IFLA_BRPORT_FAST_LEAVE, link->network->fast_leave); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_FAST_LEAVE attribute: %m"); + } + + if (link->network->allow_port_to_be_root >= 0) { + r = sd_netlink_message_append_u8(req, IFLA_BRPORT_PROTECT, link->network->allow_port_to_be_root); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_PROTECT attribute: %m"); + } + + if (link->network->unicast_flood >= 0) { + r = sd_netlink_message_append_u8(req, IFLA_BRPORT_UNICAST_FLOOD, link->network->unicast_flood); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_UNICAST_FLOOD attribute: %m"); + } + + if (link->network->multicast_flood >= 0) { + r = sd_netlink_message_append_u8(req, IFLA_BRPORT_MCAST_FLOOD, link->network->multicast_flood); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_MCAST_FLOOD attribute: %m"); + } + + if (link->network->multicast_to_unicast >= 0) { + r = sd_netlink_message_append_u8(req, IFLA_BRPORT_MCAST_TO_UCAST, link->network->multicast_to_unicast); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_MCAST_TO_UCAST attribute: %m"); + } + + if (link->network->neighbor_suppression >= 0) { + r = sd_netlink_message_append_u8(req, IFLA_BRPORT_NEIGH_SUPPRESS, link->network->neighbor_suppression); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_NEIGH_SUPPRESS attribute: %m"); + } + + if (link->network->learning >= 0) { + r = sd_netlink_message_append_u8(req, IFLA_BRPORT_LEARNING, link->network->learning); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_LEARNING attribute: %m"); + } + + if (link->network->bridge_proxy_arp >= 0) { + r = sd_netlink_message_append_u8(req, IFLA_BRPORT_PROXYARP, link->network->bridge_proxy_arp); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_PROXYARP attribute: %m"); + } + + if (link->network->bridge_proxy_arp_wifi >= 0) { + r = sd_netlink_message_append_u8(req, IFLA_BRPORT_PROXYARP_WIFI, link->network->bridge_proxy_arp_wifi); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_PROXYARP_WIFI attribute: %m"); + } + + if (link->network->cost != 0) { + r = sd_netlink_message_append_u32(req, IFLA_BRPORT_COST, link->network->cost); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_COST attribute: %m"); + } + + if (link->network->priority != LINK_BRIDGE_PORT_PRIORITY_INVALID) { + r = sd_netlink_message_append_u16(req, IFLA_BRPORT_PRIORITY, link->network->priority); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_PRIORITY attribute: %m"); + } + + if (link->network->multicast_router != _MULTICAST_ROUTER_INVALID) { + r = sd_netlink_message_append_u8(req, IFLA_BRPORT_MULTICAST_ROUTER, link->network->multicast_router); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_MULTICAST_ROUTER attribute: %m"); + } + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_LINKINFO attribute: %m"); + + r = netlink_call_async(link->manager->rtnl, NULL, req, link_set_bridge_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + return r; +} + +int config_parse_bridge_igmp_version( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Bridge *b = userdata; + uint8_t u; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + b->igmp_version = 0; /* 0 means unset. */ + return 0; + } + + r = safe_atou8(rvalue, &u); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse bridge's multicast IGMP version number '%s', ignoring assignment: %m", + rvalue); + return 0; + } + if (!IN_SET(u, 2, 3)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid bridge's multicast IGMP version number '%s', ignoring assignment.", rvalue); + return 0; + } + + b->igmp_version = u; + + return 0; +} + +static void bridge_init(NetDev *n) { + Bridge *b; + + b = BRIDGE(n); + + assert(b); + + b->mcast_querier = -1; + b->mcast_snooping = -1; + b->vlan_filtering = -1; + b->vlan_protocol = -1; + b->stp = -1; + b->default_pvid = VLANID_INVALID; + b->forward_delay = USEC_INFINITY; + b->ageing_time = USEC_INFINITY; +} + +const NetDevVTable bridge_vtable = { + .object_size = sizeof(Bridge), + .init = bridge_init, + .sections = NETDEV_COMMON_SECTIONS "Bridge\0", + .post_create = netdev_bridge_post_create, + .create_type = NETDEV_CREATE_MASTER, +}; diff --git a/src/network/netdev/bridge.h b/src/network/netdev/bridge.h new file mode 100644 index 0000000..d6abda9 --- /dev/null +++ b/src/network/netdev/bridge.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <netinet/in.h> +#include <linux/if_bridge.h> + +#include "conf-parser.h" +#include "netdev.h" + +typedef struct Bridge { + NetDev meta; + + int mcast_querier; + int mcast_snooping; + int vlan_filtering; + int vlan_protocol; + int stp; + uint16_t priority; + uint16_t group_fwd_mask; + uint16_t default_pvid; + uint8_t igmp_version; + + usec_t forward_delay; + usec_t hello_time; + usec_t max_age; + usec_t ageing_time; +} Bridge; + +typedef enum MulticastRouter { + MULTICAST_ROUTER_NONE = MDB_RTR_TYPE_DISABLED, + MULTICAST_ROUTER_TEMPORARY_QUERY = MDB_RTR_TYPE_TEMP_QUERY, + MULTICAST_ROUTER_PERMANENT = MDB_RTR_TYPE_PERM, + MULTICAST_ROUTER_TEMPORARY = MDB_RTR_TYPE_TEMP, + _MULTICAST_ROUTER_MAX, + _MULTICAST_ROUTER_INVALID = -1, +} MulticastRouter; + +DEFINE_NETDEV_CAST(BRIDGE, Bridge); +extern const NetDevVTable bridge_vtable; + +int link_set_bridge(Link *link); + +const char* multicast_router_to_string(MulticastRouter i) _const_; +MulticastRouter multicast_router_from_string(const char *s) _pure_; + +CONFIG_PARSER_PROTOTYPE(config_parse_multicast_router); +CONFIG_PARSER_PROTOTYPE(config_parse_bridge_igmp_version); diff --git a/src/network/netdev/dummy.c b/src/network/netdev/dummy.c new file mode 100644 index 0000000..754ee98 --- /dev/null +++ b/src/network/netdev/dummy.c @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "dummy.h" + +const NetDevVTable dummy_vtable = { + .object_size = sizeof(Dummy), + .sections = NETDEV_COMMON_SECTIONS, + .create_type = NETDEV_CREATE_INDEPENDENT, + .generate_mac = true, +}; diff --git a/src/network/netdev/dummy.h b/src/network/netdev/dummy.h new file mode 100644 index 0000000..eafdf4b --- /dev/null +++ b/src/network/netdev/dummy.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "netdev.h" + +typedef struct Dummy { + NetDev meta; +} Dummy; + +DEFINE_NETDEV_CAST(DUMMY, Dummy); +extern const NetDevVTable dummy_vtable; diff --git a/src/network/netdev/fou-tunnel.c b/src/network/netdev/fou-tunnel.c new file mode 100644 index 0000000..6863257 --- /dev/null +++ b/src/network/netdev/fou-tunnel.c @@ -0,0 +1,279 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <linux/fou.h> +#include <net/if.h> +#include <netinet/in.h> +#include <linux/ip.h> + +#include "conf-parser.h" +#include "fou-tunnel.h" +#include "ip-protocol-list.h" +#include "netlink-util.h" +#include "networkd-manager.h" +#include "parse-util.h" +#include "string-table.h" +#include "string-util.h" +#include "util.h" + +static const char* const fou_encap_type_table[_NETDEV_FOO_OVER_UDP_ENCAP_MAX] = { + [NETDEV_FOO_OVER_UDP_ENCAP_DIRECT] = "FooOverUDP", + [NETDEV_FOO_OVER_UDP_ENCAP_GUE] = "GenericUDPEncapsulation", +}; + +DEFINE_STRING_TABLE_LOOKUP(fou_encap_type, FooOverUDPEncapType); +DEFINE_CONFIG_PARSE_ENUM(config_parse_fou_encap_type, fou_encap_type, FooOverUDPEncapType, + "Failed to parse Encapsulation="); + +static int netdev_fill_fou_tunnel_message(NetDev *netdev, sd_netlink_message **ret) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + FouTunnel *t; + uint8_t encap_type; + int r; + + assert(netdev); + + t = FOU(netdev); + + assert(t); + + r = sd_genl_message_new(netdev->manager->genl, SD_GENL_FOU, FOU_CMD_ADD, &m); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Failed to allocate generic netlink message: %m"); + + r = sd_netlink_message_append_u16(m, FOU_ATTR_PORT, htobe16(t->port)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_PORT attribute: %m"); + + if (IN_SET(t->peer_family, AF_INET, AF_INET6)) { + r = sd_netlink_message_append_u16(m, FOU_ATTR_PEER_PORT, htobe16(t->peer_port)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_PEER_PORT attribute: %m"); + } + + switch (t->fou_encap_type) { + case NETDEV_FOO_OVER_UDP_ENCAP_DIRECT: + encap_type = FOU_ENCAP_DIRECT; + break; + case NETDEV_FOO_OVER_UDP_ENCAP_GUE: + encap_type = FOU_ENCAP_GUE; + break; + default: + assert_not_reached("invalid encap type"); + } + + r = sd_netlink_message_append_u8(m, FOU_ATTR_TYPE, encap_type); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_TYPE attribute: %m"); + + r = sd_netlink_message_append_u8(m, FOU_ATTR_AF, AF_INET); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_AF attribute: %m"); + + r = sd_netlink_message_append_u8(m, FOU_ATTR_IPPROTO, t->fou_protocol); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_IPPROTO attribute: %m"); + + if (t->local_family == AF_INET) { + r = sd_netlink_message_append_in_addr(m, FOU_ATTR_LOCAL_V4, &t->local.in); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_LOCAL_V4 attribute: %m"); + } else if (t->local_family == AF_INET6) { + r = sd_netlink_message_append_in6_addr(m, FOU_ATTR_LOCAL_V6, &t->local.in6); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_LOCAL_V6 attribute: %m"); + } + + if (t->peer_family == AF_INET) { + r = sd_netlink_message_append_in_addr(m, FOU_ATTR_PEER_V4, &t->peer.in); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_PEER_V4 attribute: %m"); + } else if (t->peer_family == AF_INET6){ + r = sd_netlink_message_append_in6_addr(m, FOU_ATTR_PEER_V6, &t->peer.in6); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_PEER_V6 attribute: %m"); + } + + *ret = TAKE_PTR(m); + return 0; +} + +static int fou_tunnel_create_handler(sd_netlink *rtnl, sd_netlink_message *m, NetDev *netdev) { + int r; + + assert(netdev); + assert(netdev->state != _NETDEV_STATE_INVALID); + + r = sd_netlink_message_get_errno(m); + if (r == -EEXIST) + log_netdev_info(netdev, "netdev exists, using existing without changing its parameters"); + else if (r < 0) { + log_netdev_warning_errno(netdev, r, "netdev could not be created: %m"); + netdev_drop(netdev); + + return 1; + } + + log_netdev_debug(netdev, "FooOverUDP tunnel is created"); + return 1; +} + +static int netdev_fou_tunnel_create(NetDev *netdev) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(netdev); + assert(FOU(netdev)); + + r = netdev_fill_fou_tunnel_message(netdev, &m); + if (r < 0) + return r; + + r = netlink_call_async(netdev->manager->genl, NULL, m, fou_tunnel_create_handler, + netdev_destroy_callback, netdev); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Failed to create FooOverUDP tunnel: %m"); + + netdev_ref(netdev); + return 0; +} + +int config_parse_ip_protocol( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + uint8_t *ret = data; + unsigned protocol; + /* linux/fou.h defines the netlink field as one byte, so we need to reject protocols numbers that + * don't fit in one byte. */ + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = parse_ip_protocol(rvalue); + if (r >= 0) + protocol = r; + else { + r = safe_atou(rvalue, &protocol); + if (r < 0) + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse IP protocol '%s' for FooOverUDP tunnel, " + "ignoring assignment: %m", rvalue); + return 0; + } + + if (protocol > UINT8_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "IP protocol '%s' for FooOverUDP tunnel out of range, " + "ignoring assignment: %m", rvalue); + return 0; + } + + *ret = protocol; + return 0; +} + +int config_parse_fou_tunnel_address( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + union in_addr_union *addr = data; + FouTunnel *t = userdata; + int r, *f; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (streq(lvalue, "Local")) + f = &t->local_family; + else + f = &t->peer_family; + + r = in_addr_from_string_auto(rvalue, f, addr); + if (r < 0) + log_syntax(unit, LOG_WARNING, filename, line, r, + "FooOverUDP tunnel '%s' address is invalid, ignoring assignment: %s", + lvalue, rvalue); + + return 0; +} + +static int netdev_fou_tunnel_verify(NetDev *netdev, const char *filename) { + FouTunnel *t; + + assert(netdev); + assert(filename); + + t = FOU(netdev); + + assert(t); + + switch (t->fou_encap_type) { + case NETDEV_FOO_OVER_UDP_ENCAP_DIRECT: + if (t->fou_protocol <= 0) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "FooOverUDP protocol not configured in %s. Rejecting configuration.", + filename); + break; + case NETDEV_FOO_OVER_UDP_ENCAP_GUE: + if (t->fou_protocol > 0) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "FooOverUDP GUE can't be set with protocol configured in %s. Rejecting configuration.", + filename); + break; + default: + assert_not_reached("Invalid fou encap type"); + } + + if (t->peer_family == AF_UNSPEC && t->peer_port > 0) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "FooOverUDP peer port is set but peer address not configured in %s. Rejecting configuration.", + filename); + else if (t->peer_family != AF_UNSPEC && t->peer_port == 0) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "FooOverUDP peer port not set but peer address is configured in %s. Rejecting configuration.", + filename); + return 0; +} + +static void fou_tunnel_init(NetDev *netdev) { + FouTunnel *t; + + assert(netdev); + + t = FOU(netdev); + + assert(t); + + t->fou_encap_type = NETDEV_FOO_OVER_UDP_ENCAP_DIRECT; +} + +const NetDevVTable foutnl_vtable = { + .object_size = sizeof(FouTunnel), + .init = fou_tunnel_init, + .sections = NETDEV_COMMON_SECTIONS "FooOverUDP\0", + .create = netdev_fou_tunnel_create, + .create_type = NETDEV_CREATE_INDEPENDENT, + .config_verify = netdev_fou_tunnel_verify, +}; diff --git a/src/network/netdev/fou-tunnel.h b/src/network/netdev/fou-tunnel.h new file mode 100644 index 0000000..a6f10df --- /dev/null +++ b/src/network/netdev/fou-tunnel.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <netinet/in.h> +#include <linux/fou.h> + +#include "in-addr-util.h" +#include "netdev.h" + +typedef enum FooOverUDPEncapType { + NETDEV_FOO_OVER_UDP_ENCAP_UNSPEC = FOU_ENCAP_UNSPEC, + NETDEV_FOO_OVER_UDP_ENCAP_DIRECT = FOU_ENCAP_DIRECT, + NETDEV_FOO_OVER_UDP_ENCAP_GUE = FOU_ENCAP_GUE, + _NETDEV_FOO_OVER_UDP_ENCAP_MAX, + _NETDEV_FOO_OVER_UDP_ENCAP_INVALID = -1, +} FooOverUDPEncapType; + +typedef struct FouTunnel { + NetDev meta; + + uint8_t fou_protocol; + + uint16_t port; + uint16_t peer_port; + + int local_family; + int peer_family; + + FooOverUDPEncapType fou_encap_type; + union in_addr_union local; + union in_addr_union peer; +} FouTunnel; + +DEFINE_NETDEV_CAST(FOU, FouTunnel); +extern const NetDevVTable foutnl_vtable; + +const char *fou_encap_type_to_string(FooOverUDPEncapType d) _const_; +FooOverUDPEncapType fou_encap_type_from_string(const char *d) _pure_; + +CONFIG_PARSER_PROTOTYPE(config_parse_fou_encap_type); +CONFIG_PARSER_PROTOTYPE(config_parse_ip_protocol); +CONFIG_PARSER_PROTOTYPE(config_parse_fou_tunnel_address); diff --git a/src/network/netdev/geneve.c b/src/network/netdev/geneve.c new file mode 100644 index 0000000..edf92ec --- /dev/null +++ b/src/network/netdev/geneve.c @@ -0,0 +1,356 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <net/if.h> + +#include "alloc-util.h" +#include "conf-parser.h" +#include "extract-word.h" +#include "geneve.h" +#include "netlink-util.h" +#include "networkd-manager.h" +#include "parse-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" + +#define GENEVE_FLOW_LABEL_MAX_MASK 0xFFFFFU +#define DEFAULT_GENEVE_DESTINATION_PORT 6081 + +static const char* const geneve_df_table[_NETDEV_GENEVE_DF_MAX] = { + [NETDEV_GENEVE_DF_NO] = "no", + [NETDEV_GENEVE_DF_YES] = "yes", + [NETDEV_GENEVE_DF_INHERIT] = "inherit", +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(geneve_df, GeneveDF, NETDEV_GENEVE_DF_YES); +DEFINE_CONFIG_PARSE_ENUM(config_parse_geneve_df, geneve_df, GeneveDF, "Failed to parse Geneve IPDoNotFragment= setting"); + +/* callback for geneve netdev's created without a backing Link */ +static int geneve_netdev_create_handler(sd_netlink *rtnl, sd_netlink_message *m, NetDev *netdev) { + int r; + + assert(netdev); + assert(netdev->state != _NETDEV_STATE_INVALID); + + r = sd_netlink_message_get_errno(m); + if (r == -EEXIST) + log_netdev_info(netdev, "Geneve netdev exists, using existing without changing its parameters"); + else if (r < 0) { + log_netdev_warning_errno(netdev, r, "Geneve netdev could not be created: %m"); + netdev_drop(netdev); + + return 1; + } + + log_netdev_debug(netdev, "Geneve created"); + + return 1; +} + +static int netdev_geneve_create(NetDev *netdev) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + Geneve *v; + int r; + + assert(netdev); + + v = GENEVE(netdev); + + r = sd_rtnl_message_new_link(netdev->manager->rtnl, &m, RTM_NEWLINK, 0); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not allocate RTM_NEWLINK message: %m"); + + r = sd_netlink_message_append_string(m, IFLA_IFNAME, netdev->ifname); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IFNAME, attribute: %m"); + + if (netdev->mac) { + r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, netdev->mac); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_ADDRESS attribute: %m"); + } + + if (netdev->mtu != 0) { + r = sd_netlink_message_append_u32(m, IFLA_MTU, netdev->mtu); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_MTU attribute: %m"); + } + + r = sd_netlink_message_open_container(m, IFLA_LINKINFO); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_LINKINFO attribute: %m"); + + r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, netdev_kind_to_string(netdev->kind)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_INFO_DATA attribute: %m"); + + if (v->id <= GENEVE_VID_MAX) { + r = sd_netlink_message_append_u32(m, IFLA_GENEVE_ID, v->id); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GENEVE_ID attribute: %m"); + } + + if (in_addr_is_null(v->remote_family, &v->remote) == 0) { + if (v->remote_family == AF_INET) + r = sd_netlink_message_append_in_addr(m, IFLA_GENEVE_REMOTE, &v->remote.in); + else + r = sd_netlink_message_append_in6_addr(m, IFLA_GENEVE_REMOTE6, &v->remote.in6); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GENEVE_REMOTE/IFLA_GENEVE_REMOTE6 attribute: %m"); + } + + if (v->inherit) { + r = sd_netlink_message_append_u8(m, IFLA_GENEVE_TTL_INHERIT, 1); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GENEVE_TTL_INHERIT attribute: %m"); + } else { + r = sd_netlink_message_append_u8(m, IFLA_GENEVE_TTL, v->ttl); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GENEVE_TTL attribute: %m"); + } + + r = sd_netlink_message_append_u8(m, IFLA_GENEVE_TOS, v->tos); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GENEVE_TOS attribute: %m"); + + r = sd_netlink_message_append_u8(m, IFLA_GENEVE_UDP_CSUM, v->udpcsum); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GENEVE_UDP_CSUM attribute: %m"); + + r = sd_netlink_message_append_u8(m, IFLA_GENEVE_UDP_ZERO_CSUM6_TX, v->udp6zerocsumtx); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GENEVE_UDP_ZERO_CSUM6_TX attribute: %m"); + + r = sd_netlink_message_append_u8(m, IFLA_GENEVE_UDP_ZERO_CSUM6_RX, v->udp6zerocsumrx); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GENEVE_UDP_ZERO_CSUM6_RX attribute: %m"); + + if (v->dest_port != DEFAULT_GENEVE_DESTINATION_PORT) { + r = sd_netlink_message_append_u16(m, IFLA_GENEVE_PORT, htobe16(v->dest_port)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GENEVE_PORT attribute: %m"); + } + + if (v->flow_label > 0) { + r = sd_netlink_message_append_u32(m, IFLA_GENEVE_LABEL, htobe32(v->flow_label)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GENEVE_LABEL attribute: %m"); + } + + if (v->geneve_df != _NETDEV_GENEVE_DF_INVALID) { + r = sd_netlink_message_append_u8(m, IFLA_GENEVE_DF, v->geneve_df); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GENEVE_DF attribute: %m"); + } + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_INFO_DATA attribute: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_LINKINFO attribute: %m"); + + r = netlink_call_async(netdev->manager->rtnl, NULL, m, geneve_netdev_create_handler, + netdev_destroy_callback, netdev); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not send rtnetlink message: %m"); + + netdev_ref(netdev); + netdev->state = NETDEV_STATE_CREATING; + + log_netdev_debug(netdev, "Creating"); + + return r; +} + +int config_parse_geneve_vni(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + Geneve *v = userdata; + uint32_t f; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = safe_atou32(rvalue, &f); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse Geneve VNI '%s'.", rvalue); + return 0; + } + + if (f > GENEVE_VID_MAX){ + log_syntax(unit, LOG_WARNING, filename, line, 0, "Geneve VNI out is of range '%s'.", rvalue); + return 0; + } + + v->id = f; + + return 0; +} + +int config_parse_geneve_address(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + Geneve *v = userdata; + union in_addr_union *addr = data, buffer; + int r, f; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = in_addr_from_string_auto(rvalue, &f, &buffer); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "geneve '%s' address is invalid, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + r = in_addr_is_multicast(f, &buffer); + if (r > 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "geneve invalid multicast '%s' address, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + v->remote_family = f; + *addr = buffer; + + return 0; +} + +int config_parse_geneve_flow_label(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + Geneve *v = userdata; + uint32_t f; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = safe_atou32(rvalue, &f); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse Geneve flow label '%s'.", rvalue); + return 0; + } + + if (f & ~GENEVE_FLOW_LABEL_MAX_MASK) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Geneve flow label '%s' not valid. Flow label range should be [0-1048575].", rvalue); + return 0; + } + + v->flow_label = f; + + return 0; +} + +int config_parse_geneve_ttl(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + Geneve *v = userdata; + unsigned f; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (streq(rvalue, "inherit")) + v->inherit = true; + else { + r = safe_atou(rvalue, &f); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse Geneve TTL '%s', ignoring assignment: %m", rvalue); + return 0; + } + + if (f > 255) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid Geneve TTL '%s'. TTL must be <= 255. Ignoring assignment.", rvalue); + return 0; + } + + v->ttl = f; + } + + return 0; +} + +static int netdev_geneve_verify(NetDev *netdev, const char *filename) { + Geneve *v = GENEVE(netdev); + + assert(netdev); + assert(v); + assert(filename); + + if (v->id > GENEVE_VID_MAX) + return log_netdev_warning_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s: Geneve without valid VNI (or Virtual Network Identifier) configured. Ignoring.", + filename); + + return 0; +} + +static void geneve_init(NetDev *netdev) { + Geneve *v; + + assert(netdev); + + v = GENEVE(netdev); + + assert(v); + + v->id = GENEVE_VID_MAX + 1; + v->geneve_df = _NETDEV_GENEVE_DF_INVALID; + v->dest_port = DEFAULT_GENEVE_DESTINATION_PORT; + v->udpcsum = false; + v->udp6zerocsumtx = false; + v->udp6zerocsumrx = false; +} + +const NetDevVTable geneve_vtable = { + .object_size = sizeof(Geneve), + .init = geneve_init, + .sections = NETDEV_COMMON_SECTIONS "GENEVE\0", + .create = netdev_geneve_create, + .create_type = NETDEV_CREATE_INDEPENDENT, + .config_verify = netdev_geneve_verify, + .generate_mac = true, +}; diff --git a/src/network/netdev/geneve.h b/src/network/netdev/geneve.h new file mode 100644 index 0000000..b62eb7b --- /dev/null +++ b/src/network/netdev/geneve.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +typedef struct Geneve Geneve; + +#include "in-addr-util.h" +#include "netdev.h" +#include "networkd-network.h" + +#define GENEVE_VID_MAX (1u << 24) - 1 + +typedef enum GeneveDF { + NETDEV_GENEVE_DF_NO = GENEVE_DF_UNSET, + NETDEV_GENEVE_DF_YES = GENEVE_DF_SET, + NETDEV_GENEVE_DF_INHERIT = GENEVE_DF_INHERIT, + _NETDEV_GENEVE_DF_MAX, + _NETDEV_GENEVE_DF_INVALID = -1 +} GeneveDF; + +struct Geneve { + NetDev meta; + + uint32_t id; + uint32_t flow_label; + + int remote_family; + + uint8_t tos; + uint8_t ttl; + + uint16_t dest_port; + + bool udpcsum; + bool udp6zerocsumtx; + bool udp6zerocsumrx; + bool inherit; + + GeneveDF geneve_df; + union in_addr_union remote; +}; + +DEFINE_NETDEV_CAST(GENEVE, Geneve); +extern const NetDevVTable geneve_vtable; + +const char *geneve_df_to_string(GeneveDF d) _const_; +GeneveDF geneve_df_from_string(const char *d) _pure_; + +CONFIG_PARSER_PROTOTYPE(config_parse_geneve_vni); +CONFIG_PARSER_PROTOTYPE(config_parse_geneve_address); +CONFIG_PARSER_PROTOTYPE(config_parse_geneve_flow_label); +CONFIG_PARSER_PROTOTYPE(config_parse_geneve_df); +CONFIG_PARSER_PROTOTYPE(config_parse_geneve_ttl); diff --git a/src/network/netdev/ifb.c b/src/network/netdev/ifb.c new file mode 100644 index 0000000..16ff49d --- /dev/null +++ b/src/network/netdev/ifb.c @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2019 VMware, Inc. */ + +#include "ifb.h" + +const NetDevVTable ifb_vtable = { + .object_size = sizeof(IntermediateFunctionalBlock), + .sections = NETDEV_COMMON_SECTIONS, + .create_type = NETDEV_CREATE_INDEPENDENT, + .generate_mac = true, +}; diff --git a/src/network/netdev/ifb.h b/src/network/netdev/ifb.h new file mode 100644 index 0000000..badfb4a --- /dev/null +++ b/src/network/netdev/ifb.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2019 VMware, Inc. */ + +#pragma once + +#include "netdev.h" + +typedef struct IntermediateFunctionalBlock { + NetDev meta; +} IntermediateFunctionalBlock; + +DEFINE_NETDEV_CAST(IFB, IntermediateFunctionalBlock); +extern const NetDevVTable ifb_vtable; diff --git a/src/network/netdev/ipvlan.c b/src/network/netdev/ipvlan.c new file mode 100644 index 0000000..92a8f58 --- /dev/null +++ b/src/network/netdev/ipvlan.c @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <net/if.h> + +#include "conf-parser.h" +#include "ipvlan.h" +#include "ipvlan-util.h" +#include "networkd-link.h" +#include "string-util.h" + +DEFINE_CONFIG_PARSE_ENUM(config_parse_ipvlan_mode, ipvlan_mode, IPVlanMode, "Failed to parse ipvlan mode"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_ipvlan_flags, ipvlan_flags, IPVlanFlags, "Failed to parse ipvlan flags"); + +static int netdev_ipvlan_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *req) { + IPVlan *m; + int r; + + assert(netdev); + assert(link); + assert(netdev->ifname); + + if (netdev->kind == NETDEV_KIND_IPVLAN) + m = IPVLAN(netdev); + else + m = IPVTAP(netdev); + + assert(m); + + if (m->mode != _NETDEV_IPVLAN_MODE_INVALID) { + r = sd_netlink_message_append_u16(req, IFLA_IPVLAN_MODE, m->mode); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPVLAN_MODE attribute: %m"); + } + + if (m->flags != _NETDEV_IPVLAN_FLAGS_INVALID) { + r = sd_netlink_message_append_u16(req, IFLA_IPVLAN_FLAGS, m->flags); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPVLAN_FLAGS attribute: %m"); + } + + return 0; +} + +static void ipvlan_init(NetDev *n) { + IPVlan *m; + + assert(n); + + if (n->kind == NETDEV_KIND_IPVLAN) + m = IPVLAN(n); + else + m = IPVTAP(n); + + assert(m); + + m->mode = _NETDEV_IPVLAN_MODE_INVALID; + m->flags = _NETDEV_IPVLAN_FLAGS_INVALID; +} + +const NetDevVTable ipvlan_vtable = { + .object_size = sizeof(IPVlan), + .init = ipvlan_init, + .sections = NETDEV_COMMON_SECTIONS "IPVLAN\0", + .fill_message_create = netdev_ipvlan_fill_message_create, + .create_type = NETDEV_CREATE_STACKED, + .generate_mac = true, +}; + +const NetDevVTable ipvtap_vtable = { + .object_size = sizeof(IPVlan), + .init = ipvlan_init, + .sections = NETDEV_COMMON_SECTIONS "IPVTAP\0", + .fill_message_create = netdev_ipvlan_fill_message_create, + .create_type = NETDEV_CREATE_STACKED, + .generate_mac = true, +}; + +IPVlanMode link_get_ipvlan_mode(Link *link) { + NetDev *netdev; + + if (!streq_ptr(link->kind, "ipvlan")) + return _NETDEV_IPVLAN_MODE_INVALID; + + if (netdev_get(link->manager, link->ifname, &netdev) < 0) + return _NETDEV_IPVLAN_MODE_INVALID; + + if (netdev->kind != NETDEV_KIND_IPVLAN) + return _NETDEV_IPVLAN_MODE_INVALID; + + return IPVLAN(netdev)->mode; +} diff --git a/src/network/netdev/ipvlan.h b/src/network/netdev/ipvlan.h new file mode 100644 index 0000000..633b0bd --- /dev/null +++ b/src/network/netdev/ipvlan.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <netinet/in.h> +#include <linux/if_link.h> + +#include "ipvlan-util.h" +#include "netdev.h" + +typedef struct IPVlan { + NetDev meta; + + IPVlanMode mode; + IPVlanFlags flags; +} IPVlan; + +DEFINE_NETDEV_CAST(IPVLAN, IPVlan); +DEFINE_NETDEV_CAST(IPVTAP, IPVlan); +extern const NetDevVTable ipvlan_vtable; +extern const NetDevVTable ipvtap_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_ipvlan_mode); +CONFIG_PARSER_PROTOTYPE(config_parse_ipvlan_flags); + +IPVlanMode link_get_ipvlan_mode(Link *link); diff --git a/src/network/netdev/l2tp-tunnel.c b/src/network/netdev/l2tp-tunnel.c new file mode 100644 index 0000000..eeea197 --- /dev/null +++ b/src/network/netdev/l2tp-tunnel.c @@ -0,0 +1,728 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <netinet/in.h> +#include <linux/l2tp.h> +#include <linux/genetlink.h> + +#include "conf-parser.h" +#include "hashmap.h" +#include "l2tp-tunnel.h" +#include "netlink-util.h" +#include "networkd-address.h" +#include "networkd-manager.h" +#include "parse-util.h" +#include "socket-util.h" +#include "string-table.h" +#include "string-util.h" +#include "util.h" + +static const char* const l2tp_l2spec_type_table[_NETDEV_L2TP_L2SPECTYPE_MAX] = { + [NETDEV_L2TP_L2SPECTYPE_NONE] = "none", + [NETDEV_L2TP_L2SPECTYPE_DEFAULT] = "default", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(l2tp_l2spec_type, L2tpL2specType); + +static const char* const l2tp_encap_type_table[_NETDEV_L2TP_ENCAPTYPE_MAX] = { + [NETDEV_L2TP_ENCAPTYPE_UDP] = "udp", + [NETDEV_L2TP_ENCAPTYPE_IP] = "ip", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(l2tp_encap_type, L2tpEncapType); +DEFINE_CONFIG_PARSE_ENUM(config_parse_l2tp_encap_type, l2tp_encap_type, L2tpEncapType, "Failed to parse L2TP Encapsulation Type"); + +static const char* const l2tp_local_address_type_table[_NETDEV_L2TP_LOCAL_ADDRESS_MAX] = { + [NETDEV_L2TP_LOCAL_ADDRESS_AUTO] = "auto", + [NETDEV_L2TP_LOCAL_ADDRESS_STATIC] = "static", + [NETDEV_L2TP_LOCAL_ADDRESS_DYNAMIC] = "dynamic", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(l2tp_local_address_type, L2tpLocalAddressType); + +static void l2tp_session_free(L2tpSession *s) { + if (!s) + return; + + if (s->tunnel && s->section) + ordered_hashmap_remove(s->tunnel->sessions_by_section, s->section); + + network_config_section_free(s->section); + + free(s->name); + + free(s); +} + +DEFINE_NETWORK_SECTION_FUNCTIONS(L2tpSession, l2tp_session_free); + +static int l2tp_session_new_static(L2tpTunnel *t, const char *filename, unsigned section_line, L2tpSession **ret) { + _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; + _cleanup_(l2tp_session_freep) L2tpSession *s = NULL; + int r; + + assert(t); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = network_config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + s = ordered_hashmap_get(t->sessions_by_section, n); + if (s) { + *ret = TAKE_PTR(s); + return 0; + } + + s = new(L2tpSession, 1); + if (!s) + return -ENOMEM; + + *s = (L2tpSession) { + .l2tp_l2spec_type = NETDEV_L2TP_L2SPECTYPE_DEFAULT, + .tunnel = t, + .section = TAKE_PTR(n), + }; + + r = ordered_hashmap_ensure_allocated(&t->sessions_by_section, &network_config_hash_ops); + if (r < 0) + return r; + + r = ordered_hashmap_put(t->sessions_by_section, s->section, s); + if (r < 0) + return r; + + *ret = TAKE_PTR(s); + return 0; +} + +static int netdev_l2tp_fill_message_tunnel(NetDev *netdev, union in_addr_union *local_address, sd_netlink_message **ret) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + uint16_t encap_type; + L2tpTunnel *t; + int r; + + assert(netdev); + assert(local_address); + + t = L2TP(netdev); + + assert(t); + + r = sd_genl_message_new(netdev->manager->genl, SD_GENL_L2TP, L2TP_CMD_TUNNEL_CREATE, &m); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Failed to create generic netlink message: %m"); + + r = sd_netlink_message_append_u32(m, L2TP_ATTR_CONN_ID, t->tunnel_id); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append L2TP_ATTR_CONN_ID attribute: %m"); + + r = sd_netlink_message_append_u32(m, L2TP_ATTR_PEER_CONN_ID, t->peer_tunnel_id); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append L2TP_ATTR_PEER_CONN_ID attribute: %m"); + + r = sd_netlink_message_append_u8(m, L2TP_ATTR_PROTO_VERSION, 3); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append L2TP_ATTR_PROTO_VERSION attribute: %m"); + + switch(t->l2tp_encap_type) { + case NETDEV_L2TP_ENCAPTYPE_IP: + encap_type = L2TP_ENCAPTYPE_IP; + break; + case NETDEV_L2TP_ENCAPTYPE_UDP: + default: + encap_type = L2TP_ENCAPTYPE_UDP; + break; + } + + r = sd_netlink_message_append_u16(m, L2TP_ATTR_ENCAP_TYPE, encap_type); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append L2TP_ATTR_ENCAP_TYPE attribute: %m"); + + if (t->family == AF_INET) { + r = sd_netlink_message_append_in_addr(m, L2TP_ATTR_IP_SADDR, &local_address->in); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append L2TP_ATTR_IP_SADDR attribute: %m"); + + r = sd_netlink_message_append_in_addr(m, L2TP_ATTR_IP_DADDR, &t->remote.in); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append L2TP_ATTR_IP_DADDR attribute: %m"); + } else { + r = sd_netlink_message_append_in6_addr(m, L2TP_ATTR_IP6_SADDR, &local_address->in6); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append L2TP_ATTR_IP6_SADDR attribute: %m"); + + r = sd_netlink_message_append_in6_addr(m, L2TP_ATTR_IP6_DADDR, &t->remote.in6); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append L2TP_ATTR_IP6_DADDR attribute: %m"); + } + + if (encap_type == L2TP_ENCAPTYPE_UDP) { + r = sd_netlink_message_append_u16(m, L2TP_ATTR_UDP_SPORT, t->l2tp_udp_sport); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append L2TP_ATTR_UDP_SPORT, attribute: %m"); + + r = sd_netlink_message_append_u16(m, L2TP_ATTR_UDP_DPORT, t->l2tp_udp_dport); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append L2TP_ATTR_UDP_DPORT attribute: %m"); + + if (t->udp_csum) { + r = sd_netlink_message_append_u8(m, L2TP_ATTR_UDP_CSUM, t->udp_csum); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append L2TP_ATTR_UDP_CSUM attribute: %m"); + } + + if (t->udp6_csum_tx) { + r = sd_netlink_message_append_flag(m, L2TP_ATTR_UDP_ZERO_CSUM6_TX); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append L2TP_ATTR_UDP_ZERO_CSUM6_TX attribute: %m"); + } + + if (t->udp6_csum_rx) { + r = sd_netlink_message_append_flag(m, L2TP_ATTR_UDP_ZERO_CSUM6_RX); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append L2TP_ATTR_UDP_ZERO_CSUM6_RX attribute: %m"); + } + } + + *ret = TAKE_PTR(m); + + return 0; +} + +static int netdev_l2tp_fill_message_session(NetDev *netdev, L2tpSession *session, sd_netlink_message **ret) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + uint16_t l2_spec_len; + uint8_t l2_spec_type; + int r; + + assert(netdev); + assert(session); + assert(session->tunnel); + + r = sd_genl_message_new(netdev->manager->genl, SD_GENL_L2TP, L2TP_CMD_SESSION_CREATE, &m); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Failed to create generic netlink message: %m"); + + r = sd_netlink_message_append_u32(m, L2TP_ATTR_CONN_ID, session->tunnel->tunnel_id); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append L2TP_ATTR_CONN_ID attribute: %m"); + + r = sd_netlink_message_append_u32(m, L2TP_ATTR_PEER_CONN_ID, session->tunnel->peer_tunnel_id); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append L2TP_ATTR_PEER_CONN_ID attribute: %m"); + + r = sd_netlink_message_append_u32(m, L2TP_ATTR_SESSION_ID, session->session_id); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append L2TP_ATTR_SESSION_ID attribute: %m"); + + r = sd_netlink_message_append_u32(m, L2TP_ATTR_PEER_SESSION_ID, session->peer_session_id); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append L2TP_ATTR_PEER_SESSION_ID attribute: %m"); + + r = sd_netlink_message_append_u16(m, L2TP_ATTR_PW_TYPE, L2TP_PWTYPE_ETH); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append L2TP_ATTR_PW_TYPE attribute: %m"); + + switch (session->l2tp_l2spec_type) { + case NETDEV_L2TP_L2SPECTYPE_NONE: + l2_spec_type = L2TP_L2SPECTYPE_NONE; + l2_spec_len = 0; + break; + case NETDEV_L2TP_L2SPECTYPE_DEFAULT: + default: + l2_spec_type = L2TP_L2SPECTYPE_DEFAULT; + l2_spec_len = 4; + break; + } + + r = sd_netlink_message_append_u8(m, L2TP_ATTR_L2SPEC_TYPE, l2_spec_type); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append L2TP_ATTR_L2SPEC_TYPE attribute: %m"); + + r = sd_netlink_message_append_u8(m, L2TP_ATTR_L2SPEC_LEN, l2_spec_len); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append L2TP_ATTR_L2SPEC_LEN attribute: %m"); + + r = sd_netlink_message_append_string(m, L2TP_ATTR_IFNAME, session->name); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append L2TP_ATTR_IFNAME attribute: %m"); + + *ret = TAKE_PTR(m); + + return 0; +} + +static int l2tp_acquire_local_address_one(L2tpTunnel *t, Address *a, union in_addr_union *ret) { + if (a->family != t->family) + return -EINVAL; + + if (in_addr_is_null(a->family, &a->in_addr_peer) <= 0) + return -EINVAL; + + if (t->local_address_type == NETDEV_L2TP_LOCAL_ADDRESS_STATIC && + !FLAGS_SET(a->flags, IFA_F_PERMANENT)) + return -EINVAL; + + if (t->local_address_type == NETDEV_L2TP_LOCAL_ADDRESS_DYNAMIC && + FLAGS_SET(a->flags, IFA_F_PERMANENT)) + return -EINVAL; + + *ret = a->in_addr; + return 0; +} + +static int l2tp_acquire_local_address(L2tpTunnel *t, Link *link, union in_addr_union *ret) { + Address *a; + + assert(t); + assert(link); + assert(ret); + assert(IN_SET(t->family, AF_INET, AF_INET6)); + + if (!in_addr_is_null(t->family, &t->local)) { + /* local address is explicitly specified. */ + *ret = t->local; + return 0; + } + + SET_FOREACH(a, link->addresses) + if (l2tp_acquire_local_address_one(t, a, ret) >= 0) + return 1; + + SET_FOREACH(a, link->addresses_foreign) + if (l2tp_acquire_local_address_one(t, a, ret) >= 0) + return 1; + + return -ENODATA; +} + +static void l2tp_session_destroy_callback(L2tpSession *session) { + if (!session) + return; + + netdev_unref(NETDEV(session->tunnel)); +} + +static int l2tp_create_session_handler(sd_netlink *rtnl, sd_netlink_message *m, L2tpSession *session) { + NetDev *netdev; + int r; + + assert(session); + assert(session->tunnel); + + netdev = NETDEV(session->tunnel); + + r = sd_netlink_message_get_errno(m); + if (r == -EEXIST) + log_netdev_info(netdev, "L2TP session %s exists, using existing without changing its parameters", + session->name); + else if (r < 0) { + log_netdev_warning_errno(netdev, r, "L2TP session %s could not be created: %m", session->name); + return 1; + } + + log_netdev_debug(netdev, "L2TP session %s created", session->name); + return 1; +} + +static int l2tp_create_session(NetDev *netdev, L2tpSession *session) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *n = NULL; + int r; + + r = netdev_l2tp_fill_message_session(netdev, session, &n); + if (r < 0) + return r; + + r = netlink_call_async(netdev->manager->genl, NULL, n, l2tp_create_session_handler, + l2tp_session_destroy_callback, session); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Failed to create L2TP session %s: %m", session->name); + + netdev_ref(netdev); + return 0; +} + +static int l2tp_create_tunnel_handler(sd_netlink *rtnl, sd_netlink_message *m, NetDev *netdev) { + L2tpSession *session; + L2tpTunnel *t; + int r; + + assert(netdev); + assert(netdev->state != _NETDEV_STATE_INVALID); + + t = L2TP(netdev); + + assert(t); + + r = sd_netlink_message_get_errno(m); + if (r == -EEXIST) + log_netdev_info(netdev, "netdev exists, using existing without changing its parameters"); + else if (r < 0) { + log_netdev_warning_errno(netdev, r, "netdev could not be created: %m"); + netdev_drop(netdev); + + return 1; + } + + log_netdev_debug(netdev, "L2TP tunnel is created"); + + ORDERED_HASHMAP_FOREACH(session, t->sessions_by_section) + (void) l2tp_create_session(netdev, session); + + return 1; +} + +static int l2tp_create_tunnel(NetDev *netdev, Link *link) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + union in_addr_union local_address; + L2tpTunnel *t; + int r; + + assert(netdev); + + t = L2TP(netdev); + + assert(t); + + r = l2tp_acquire_local_address(t, link, &local_address); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not find local address."); + + if (r > 0 && DEBUG_LOGGING) { + _cleanup_free_ char *str = NULL; + + (void) in_addr_to_string(t->family, &local_address, &str); + log_netdev_debug(netdev, "Local address %s acquired.", strna(str)); + } + + r = netdev_l2tp_fill_message_tunnel(netdev, &local_address, &m); + if (r < 0) + return r; + + r = netlink_call_async(netdev->manager->genl, NULL, m, l2tp_create_tunnel_handler, + netdev_destroy_callback, netdev); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Failed to create L2TP tunnel: %m"); + + netdev_ref(netdev); + + return 0; +} + +int config_parse_l2tp_tunnel_address( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + L2tpTunnel *t = userdata; + union in_addr_union *addr = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (streq(lvalue, "Local")) { + L2tpLocalAddressType addr_type; + + if (isempty(rvalue)) + addr_type = NETDEV_L2TP_LOCAL_ADDRESS_AUTO; + else + addr_type = l2tp_local_address_type_from_string(rvalue); + + if (addr_type >= 0) { + if (in_addr_is_null(t->family, &t->remote) != 0) + /* If Remote= is not specified yet, then also clear family. */ + t->family = AF_UNSPEC; + + t->local = IN_ADDR_NULL; + t->local_address_type = addr_type; + + return 0; + } + } + + if (t->family == AF_UNSPEC) + r = in_addr_from_string_auto(rvalue, &t->family, addr); + else + r = in_addr_from_string(t->family, rvalue, addr); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid L2TP Tunnel address specified in %s='%s', ignoring assignment: %m", lvalue, rvalue); + return 0; + } + + return 0; +} + +int config_parse_l2tp_tunnel_id( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + uint32_t *id = data, k; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = safe_atou32(rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse L2TP tunnel id. Ignoring assignment: %s", rvalue); + return 0; + } + + if (k == 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid L2TP tunnel id. Ignoring assignment: %s", rvalue); + return 0; + } + + *id = k; + + return 0; +} + +int config_parse_l2tp_session_id( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(l2tp_session_free_or_set_invalidp) L2tpSession *session = NULL; + L2tpTunnel *t = userdata; + uint32_t k; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = l2tp_session_new_static(t, filename, section_line, &session); + if (r < 0) + return log_oom(); + + r = safe_atou32(rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse L2TP session id. Ignoring assignment: %s", rvalue); + return 0; + } + + if (k == 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid L2TP session id. Ignoring assignment: %s", rvalue); + return 0; + } + + if (streq(lvalue, "SessionId")) + session->session_id = k; + else + session->peer_session_id = k; + + session = NULL; + return 0; +} + +int config_parse_l2tp_session_l2spec( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(l2tp_session_free_or_set_invalidp) L2tpSession *session = NULL; + L2tpTunnel *t = userdata; + L2tpL2specType spec; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = l2tp_session_new_static(t, filename, section_line, &session); + if (r < 0) + return log_oom(); + + spec = l2tp_l2spec_type_from_string(rvalue); + if (spec < 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Failed to parse layer2 specific header type. Ignoring assignment: %s", rvalue); + return 0; + } + + session->l2tp_l2spec_type = spec; + + session = NULL; + return 0; +} + +int config_parse_l2tp_session_name( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(l2tp_session_free_or_set_invalidp) L2tpSession *session = NULL; + L2tpTunnel *t = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = l2tp_session_new_static(t, filename, section_line, &session); + if (r < 0) + return log_oom(); + + if (!ifname_valid(rvalue)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Failed to parse L2TP tunnel session name. Ignoring assignment: %s", rvalue); + return 0; + } + + r = free_and_strdup(&session->name, rvalue); + if (r < 0) + return log_oom(); + + session = NULL; + return 0; +} + +static void l2tp_tunnel_init(NetDev *netdev) { + L2tpTunnel *t; + + assert(netdev); + + t = L2TP(netdev); + + assert(t); + + t->l2tp_encap_type = NETDEV_L2TP_ENCAPTYPE_UDP; + t->udp6_csum_rx = true; + t->udp6_csum_tx = true; +} + +static int l2tp_session_verify(L2tpSession *session) { + NetDev *netdev; + + assert(session); + assert(session->tunnel); + + netdev = NETDEV(session->tunnel); + + if (section_is_invalid(session->section)) + return -EINVAL; + + if (!session->name) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s: L2TP session without name configured. " + "Ignoring [L2TPSession] section from line %u", + session->section->filename, session->section->line); + + if (session->session_id == 0 || session->peer_session_id == 0) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s: L2TP session without session IDs configured. " + "Ignoring [L2TPSession] section from line %u", + session->section->filename, session->section->line); + + return 0; +} + +static int netdev_l2tp_tunnel_verify(NetDev *netdev, const char *filename) { + L2tpTunnel *t; + L2tpSession *session; + + assert(netdev); + assert(filename); + + t = L2TP(netdev); + + assert(t); + + if (!IN_SET(t->family, AF_INET, AF_INET6)) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s: L2TP tunnel with invalid address family configured. Ignoring", + filename); + + if (in_addr_is_null(t->family, &t->remote)) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s: L2TP tunnel without a remote address configured. Ignoring", + filename); + + if (t->tunnel_id == 0 || t->peer_tunnel_id == 0) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s: L2TP tunnel without tunnel IDs configured. Ignoring", + filename); + + ORDERED_HASHMAP_FOREACH(session, t->sessions_by_section) + if (l2tp_session_verify(session) < 0) + l2tp_session_free(session); + + return 0; +} + +static void l2tp_tunnel_done(NetDev *netdev) { + L2tpTunnel *t; + + assert(netdev); + + t = L2TP(netdev); + + assert(t); + + ordered_hashmap_free_with_destructor(t->sessions_by_section, l2tp_session_free); +} + +const NetDevVTable l2tptnl_vtable = { + .object_size = sizeof(L2tpTunnel), + .init = l2tp_tunnel_init, + .sections = NETDEV_COMMON_SECTIONS "L2TP\0L2TPSession\0", + .create_after_configured = l2tp_create_tunnel, + .done = l2tp_tunnel_done, + .create_type = NETDEV_CREATE_AFTER_CONFIGURED, + .config_verify = netdev_l2tp_tunnel_verify, +}; diff --git a/src/network/netdev/l2tp-tunnel.h b/src/network/netdev/l2tp-tunnel.h new file mode 100644 index 0000000..048318d --- /dev/null +++ b/src/network/netdev/l2tp-tunnel.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <netinet/in.h> +#include <linux/l2tp.h> + +#include "in-addr-util.h" +#include "netdev.h" +#include "networkd-util.h" + +typedef enum L2tpL2specType { + NETDEV_L2TP_L2SPECTYPE_NONE = L2TP_L2SPECTYPE_NONE, + NETDEV_L2TP_L2SPECTYPE_DEFAULT = L2TP_L2SPECTYPE_DEFAULT, + _NETDEV_L2TP_L2SPECTYPE_MAX, + _NETDEV_L2TP_L2SPECTYPE_INVALID = -1, +} L2tpL2specType; + +typedef enum L2tpEncapType { + NETDEV_L2TP_ENCAPTYPE_UDP = L2TP_ENCAPTYPE_UDP, + NETDEV_L2TP_ENCAPTYPE_IP = L2TP_ENCAPTYPE_IP, + _NETDEV_L2TP_ENCAPTYPE_MAX, + _NETDEV_L2TP_ENCAPTYPE_INVALID = -1, +} L2tpEncapType; + +typedef enum L2tpLocalAddressType { + NETDEV_L2TP_LOCAL_ADDRESS_AUTO, + NETDEV_L2TP_LOCAL_ADDRESS_STATIC, + NETDEV_L2TP_LOCAL_ADDRESS_DYNAMIC, + _NETDEV_L2TP_LOCAL_ADDRESS_MAX, + _NETDEV_L2TP_LOCAL_ADDRESS_INVALID = -1, +} L2tpLocalAddressType; + +typedef struct L2tpTunnel L2tpTunnel; + +typedef struct L2tpSession { + L2tpTunnel *tunnel; + NetworkConfigSection *section; + + char *name; + + uint32_t session_id; + uint32_t peer_session_id; + L2tpL2specType l2tp_l2spec_type; +} L2tpSession; + +struct L2tpTunnel { + NetDev meta; + + uint16_t l2tp_udp_sport; + uint16_t l2tp_udp_dport; + + uint32_t tunnel_id; + uint32_t peer_tunnel_id; + + int family; + + bool udp_csum; + bool udp6_csum_rx; + bool udp6_csum_tx; + + L2tpLocalAddressType local_address_type; + union in_addr_union local; + union in_addr_union remote; + + L2tpEncapType l2tp_encap_type; + + OrderedHashmap *sessions_by_section; +}; + +DEFINE_NETDEV_CAST(L2TP, L2tpTunnel); +extern const NetDevVTable l2tptnl_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_l2tp_tunnel_address); +CONFIG_PARSER_PROTOTYPE(config_parse_l2tp_tunnel_id); +CONFIG_PARSER_PROTOTYPE(config_parse_l2tp_encap_type); +CONFIG_PARSER_PROTOTYPE(config_parse_l2tp_session_l2spec); +CONFIG_PARSER_PROTOTYPE(config_parse_l2tp_session_id); +CONFIG_PARSER_PROTOTYPE(config_parse_l2tp_session_name); diff --git a/src/network/netdev/macsec.c b/src/network/netdev/macsec.c new file mode 100644 index 0000000..82e71c3 --- /dev/null +++ b/src/network/netdev/macsec.c @@ -0,0 +1,1252 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <netinet/in.h> +#include <linux/if_ether.h> +#include <linux/if_macsec.h> +#include <linux/genetlink.h> + +#include "conf-parser.h" +#include "fileio.h" +#include "hashmap.h" +#include "hexdecoct.h" +#include "macsec.h" +#include "memory-util.h" +#include "netlink-util.h" +#include "network-internal.h" +#include "networkd-manager.h" +#include "path-util.h" +#include "socket-util.h" +#include "string-table.h" +#include "string-util.h" +#include "util.h" + +static void security_association_clear(SecurityAssociation *sa) { + if (!sa) + return; + + explicit_bzero_safe(sa->key, sa->key_len); + free(sa->key); + free(sa->key_file); +} + +static void security_association_init(SecurityAssociation *sa) { + assert(sa); + + sa->activate = -1; + sa->use_for_encoding = -1; +} + +static void macsec_receive_association_free(ReceiveAssociation *c) { + if (!c) + return; + + if (c->macsec && c->section) + ordered_hashmap_remove(c->macsec->receive_associations_by_section, c->section); + + network_config_section_free(c->section); + security_association_clear(&c->sa); + + free(c); +} + +DEFINE_NETWORK_SECTION_FUNCTIONS(ReceiveAssociation, macsec_receive_association_free); + +static int macsec_receive_association_new_static(MACsec *s, const char *filename, unsigned section_line, ReceiveAssociation **ret) { + _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; + _cleanup_(macsec_receive_association_freep) ReceiveAssociation *c = NULL; + int r; + + assert(s); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = network_config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + c = ordered_hashmap_get(s->receive_associations_by_section, n); + if (c) { + *ret = TAKE_PTR(c); + return 0; + } + + c = new(ReceiveAssociation, 1); + if (!c) + return -ENOMEM; + + *c = (ReceiveAssociation) { + .macsec = s, + .section = TAKE_PTR(n), + }; + + security_association_init(&c->sa); + + r = ordered_hashmap_ensure_allocated(&s->receive_associations_by_section, &network_config_hash_ops); + if (r < 0) + return r; + + r = ordered_hashmap_put(s->receive_associations_by_section, c->section, c); + if (r < 0) + return r; + + *ret = TAKE_PTR(c); + + return 0; +} + +static void macsec_receive_channel_free(ReceiveChannel *c) { + if (!c) + return; + + if (c->macsec) { + if (c->sci.as_uint64 > 0) + ordered_hashmap_remove_value(c->macsec->receive_channels, &c->sci.as_uint64, c); + + if (c->section) + ordered_hashmap_remove(c->macsec->receive_channels_by_section, c->section); + } + + network_config_section_free(c->section); + + free(c); +} + +DEFINE_NETWORK_SECTION_FUNCTIONS(ReceiveChannel, macsec_receive_channel_free); + +static int macsec_receive_channel_new(MACsec *s, uint64_t sci, ReceiveChannel **ret) { + ReceiveChannel *c; + + assert(s); + + c = new(ReceiveChannel, 1); + if (!c) + return -ENOMEM; + + *c = (ReceiveChannel) { + .macsec = s, + .sci.as_uint64 = sci, + }; + + *ret = c; + return 0; +} + +static int macsec_receive_channel_new_static(MACsec *s, const char *filename, unsigned section_line, ReceiveChannel **ret) { + _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; + _cleanup_(macsec_receive_channel_freep) ReceiveChannel *c = NULL; + int r; + + assert(s); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = network_config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + c = ordered_hashmap_get(s->receive_channels_by_section, n); + if (c) { + *ret = TAKE_PTR(c); + return 0; + } + + r = macsec_receive_channel_new(s, 0, &c); + if (r < 0) + return r; + + c->section = TAKE_PTR(n); + + r = ordered_hashmap_ensure_allocated(&s->receive_channels_by_section, &network_config_hash_ops); + if (r < 0) + return r; + + r = ordered_hashmap_put(s->receive_channels_by_section, c->section, c); + if (r < 0) + return r; + + *ret = TAKE_PTR(c); + + return 0; +} + +static void macsec_transmit_association_free(TransmitAssociation *a) { + if (!a) + return; + + if (a->macsec && a->section) + ordered_hashmap_remove(a->macsec->transmit_associations_by_section, a->section); + + network_config_section_free(a->section); + security_association_clear(&a->sa); + + free(a); +} + +DEFINE_NETWORK_SECTION_FUNCTIONS(TransmitAssociation, macsec_transmit_association_free); + +static int macsec_transmit_association_new_static(MACsec *s, const char *filename, unsigned section_line, TransmitAssociation **ret) { + _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; + _cleanup_(macsec_transmit_association_freep) TransmitAssociation *a = NULL; + int r; + + assert(s); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = network_config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + a = ordered_hashmap_get(s->transmit_associations_by_section, n); + if (a) { + *ret = TAKE_PTR(a); + return 0; + } + + a = new(TransmitAssociation, 1); + if (!a) + return -ENOMEM; + + *a = (TransmitAssociation) { + .macsec = s, + .section = TAKE_PTR(n), + }; + + security_association_init(&a->sa); + + r = ordered_hashmap_ensure_allocated(&s->transmit_associations_by_section, &network_config_hash_ops); + if (r < 0) + return r; + + r = ordered_hashmap_put(s->transmit_associations_by_section, a->section, a); + if (r < 0) + return r; + + *ret = TAKE_PTR(a); + + return 0; +} + +static int netdev_macsec_fill_message(NetDev *netdev, int command, sd_netlink_message **ret) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(netdev); + assert(netdev->ifindex > 0); + + r = sd_genl_message_new(netdev->manager->genl, SD_GENL_MACSEC, command, &m); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Failed to create generic netlink message: %m"); + + r = sd_netlink_message_append_u32(m, MACSEC_ATTR_IFINDEX, netdev->ifindex); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append MACSEC_ATTR_IFINDEX attribute: %m"); + + *ret = TAKE_PTR(m); + + return 0; +} + +static int netdev_macsec_fill_message_sci(NetDev *netdev, MACsecSCI *sci, sd_netlink_message *m) { + int r; + + assert(netdev); + assert(m); + assert(sci); + + r = sd_netlink_message_open_container(m, MACSEC_ATTR_RXSC_CONFIG); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append MACSEC_ATTR_RXSC_CONFIG attribute: %m"); + + r = sd_netlink_message_append_u64(m, MACSEC_RXSC_ATTR_SCI, sci->as_uint64); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append MACSEC_RXSC_ATTR_SCI attribute: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append MACSEC_ATTR_RXSC_CONFIG attribute: %m"); + + return 0; +} + +static int netdev_macsec_fill_message_sa(NetDev *netdev, SecurityAssociation *a, sd_netlink_message *m) { + int r; + + assert(netdev); + assert(a); + assert(m); + + r = sd_netlink_message_open_container(m, MACSEC_ATTR_SA_CONFIG); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append MACSEC_ATTR_SA_CONFIG attribute: %m"); + + r = sd_netlink_message_append_u8(m, MACSEC_SA_ATTR_AN, a->association_number); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append MACSEC_SA_ATTR_AN attribute: %m"); + + if (a->packet_number > 0) { + r = sd_netlink_message_append_u32(m, MACSEC_SA_ATTR_PN, a->packet_number); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append MACSEC_SA_ATTR_PN attribute: %m"); + } + + if (a->key_len > 0) { + r = sd_netlink_message_append_data(m, MACSEC_SA_ATTR_KEYID, a->key_id, MACSEC_KEYID_LEN); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append MACSEC_SA_ATTR_KEYID attribute: %m"); + + r = sd_netlink_message_append_data(m, MACSEC_SA_ATTR_KEY, a->key, a->key_len); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append MACSEC_SA_ATTR_KEY attribute: %m"); + } + + if (a->activate >= 0) { + r = sd_netlink_message_append_u8(m, MACSEC_SA_ATTR_ACTIVE, a->activate); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append MACSEC_SA_ATTR_ACTIVE attribute: %m"); + } + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append MACSEC_ATTR_SA_CONFIG attribute: %m"); + + return 0; +} + +static int macsec_receive_association_handler(sd_netlink *rtnl, sd_netlink_message *m, NetDev *netdev) { + int r; + + assert(netdev); + assert(netdev->state != _NETDEV_STATE_INVALID); + + r = sd_netlink_message_get_errno(m); + if (r == -EEXIST) + log_netdev_info(netdev, + "MACsec receive secure association exists, " + "using existing without changing its parameters"); + else if (r < 0) { + log_netdev_warning_errno(netdev, r, + "Failed to add receive secure association: %m"); + netdev_drop(netdev); + + return 1; + } + + log_netdev_debug(netdev, "Receive secure association is configured"); + + return 1; +} + +static int netdev_macsec_configure_receive_association(NetDev *netdev, ReceiveAssociation *a) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(netdev); + assert(a); + + r = netdev_macsec_fill_message(netdev, MACSEC_CMD_ADD_RXSA, &m); + if (r < 0) + return r; + + r = netdev_macsec_fill_message_sa(netdev, &a->sa, m); + if (r < 0) + return r; + + r = netdev_macsec_fill_message_sci(netdev, &a->sci, m); + if (r < 0) + return r; + + r = netlink_call_async(netdev->manager->genl, NULL, m, macsec_receive_association_handler, + netdev_destroy_callback, netdev); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Failed to configure receive secure association: %m"); + + netdev_ref(netdev); + + return 0; +} + +static int macsec_receive_channel_handler(sd_netlink *rtnl, sd_netlink_message *m, ReceiveChannel *c) { + NetDev *netdev; + unsigned i; + int r; + + assert(c); + assert(c->macsec); + + netdev = NETDEV(c->macsec); + + assert(netdev->state != _NETDEV_STATE_INVALID); + + r = sd_netlink_message_get_errno(m); + if (r == -EEXIST) + log_netdev_debug(netdev, + "MACsec receive channel exists, " + "using existing without changing its parameters"); + else if (r < 0) { + log_netdev_warning_errno(netdev, r, + "Failed to add receive secure channel: %m"); + netdev_drop(netdev); + + return 1; + } + + log_netdev_debug(netdev, "Receive channel is configured"); + + for (i = 0; i < c->n_rxsa; i++) { + r = netdev_macsec_configure_receive_association(netdev, c->rxsa[i]); + if (r < 0) { + log_netdev_warning_errno(netdev, r, + "Failed to configure receive security association: %m"); + netdev_drop(netdev); + return 1; + } + } + + return 1; +} + +static void receive_channel_destroy_callback(ReceiveChannel *c) { + assert(c); + assert(c->macsec); + + netdev_unref(NETDEV(c->macsec)); +} + +static int netdev_macsec_configure_receive_channel(NetDev *netdev, ReceiveChannel *c) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(netdev); + assert(c); + + r = netdev_macsec_fill_message(netdev, MACSEC_CMD_ADD_RXSC, &m); + if (r < 0) + return r; + + r = netdev_macsec_fill_message_sci(netdev, &c->sci, m); + if (r < 0) + return r; + + r = netlink_call_async(netdev->manager->genl, NULL, m, macsec_receive_channel_handler, + receive_channel_destroy_callback, c); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Failed to configure receive channel: %m"); + + netdev_ref(netdev); + + return 0; +} + +static int macsec_transmit_association_handler(sd_netlink *rtnl, sd_netlink_message *m, NetDev *netdev) { + int r; + + assert(netdev); + assert(netdev->state != _NETDEV_STATE_INVALID); + + r = sd_netlink_message_get_errno(m); + if (r == -EEXIST) + log_netdev_info(netdev, + "MACsec transmit secure association exists, " + "using existing without changing its parameters"); + else if (r < 0) { + log_netdev_warning_errno(netdev, r, + "Failed to add transmit secure association: %m"); + netdev_drop(netdev); + + return 1; + } + + log_netdev_debug(netdev, "Transmit secure association is configured"); + + return 1; +} + +static int netdev_macsec_configure_transmit_association(NetDev *netdev, TransmitAssociation *a) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(netdev); + assert(a); + + r = netdev_macsec_fill_message(netdev, MACSEC_CMD_ADD_TXSA, &m); + if (r < 0) + return r; + + r = netdev_macsec_fill_message_sa(netdev, &a->sa, m); + if (r < 0) + return r; + + r = netlink_call_async(netdev->manager->genl, NULL, m, macsec_transmit_association_handler, + netdev_destroy_callback, netdev); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Failed to configure transmit secure association: %m"); + + netdev_ref(netdev); + + return 0; +} + +static int netdev_macsec_configure(NetDev *netdev, Link *link, sd_netlink_message *m) { + TransmitAssociation *a; + ReceiveChannel *c; + MACsec *s; + int r; + + assert(netdev); + s = MACSEC(netdev); + assert(s); + + ORDERED_HASHMAP_FOREACH(a, s->transmit_associations_by_section) { + r = netdev_macsec_configure_transmit_association(netdev, a); + if (r < 0) + return r; + } + + ORDERED_HASHMAP_FOREACH(c, s->receive_channels) { + r = netdev_macsec_configure_receive_channel(netdev, c); + if (r < 0) + return r; + } + + return 0; +} + +static int netdev_macsec_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) { + MACsec *v; + int r; + + assert(netdev); + assert(m); + + v = MACSEC(netdev); + + if (v->port > 0) { + r = sd_netlink_message_append_u16(m, IFLA_MACSEC_PORT, v->port); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_MACSEC_PORT attribute: %m"); + } + + if (v->encrypt >= 0) { + r = sd_netlink_message_append_u8(m, IFLA_MACSEC_ENCRYPT, v->encrypt); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_MACSEC_ENCRYPT attribute: %m"); + } + + r = sd_netlink_message_append_u8(m, IFLA_MACSEC_ENCODING_SA, v->encoding_an); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_MACSEC_ENCODING_SA attribute: %m"); + + return r; +} + +int config_parse_macsec_port( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL; + _cleanup_(macsec_receive_channel_free_or_set_invalidp) ReceiveChannel *c = NULL; + MACsec *s = userdata; + uint16_t port; + void *dest; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + /* This parses port used to make Secure Channel Identifier (SCI) */ + + if (streq(section, "MACsec")) + dest = &s->port; + else if (streq(section, "MACsecReceiveChannel")) { + r = macsec_receive_channel_new_static(s, filename, section_line, &c); + if (r < 0) + return log_oom(); + + dest = &c->sci.port; + } else { + assert(streq(section, "MACsecReceiveAssociation")); + + r = macsec_receive_association_new_static(s, filename, section_line, &b); + if (r < 0) + return log_oom(); + + dest = &b->sci.port; + } + + r = parse_ip_port(rvalue, &port); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse port '%s' for secure channel identifier. Ignoring assignment: %m", + rvalue); + return 0; + } + + unaligned_write_be16(dest, port); + + TAKE_PTR(b); + TAKE_PTR(c); + + return 0; +} + +int config_parse_macsec_hw_address( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL; + _cleanup_(macsec_receive_channel_free_or_set_invalidp) ReceiveChannel *c = NULL; + MACsec *s = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + if (streq(section, "MACsecReceiveChannel")) + r = macsec_receive_channel_new_static(s, filename, section_line, &c); + else + r = macsec_receive_association_new_static(s, filename, section_line, &b); + if (r < 0) + return log_oom(); + + r = ether_addr_from_string(rvalue, b ? &b->sci.mac : &c->sci.mac); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse MAC address for secure channel identifier. " + "Ignoring assignment: %s", rvalue); + return 0; + } + + TAKE_PTR(b); + TAKE_PTR(c); + + return 0; +} + +int config_parse_macsec_packet_number( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(macsec_transmit_association_free_or_set_invalidp) TransmitAssociation *a = NULL; + _cleanup_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL; + MACsec *s = userdata; + uint32_t val, *dest; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + if (streq(section, "MACsecTransmitAssociation")) + r = macsec_transmit_association_new_static(s, filename, section_line, &a); + else + r = macsec_receive_association_new_static(s, filename, section_line, &b); + if (r < 0) + return log_oom(); + + dest = a ? &a->sa.packet_number : &b->sa.packet_number; + + r = safe_atou32(rvalue, &val); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse packet number. Ignoring assignment: %s", rvalue); + return 0; + } + if (streq(section, "MACsecTransmitAssociation") && val == 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid packet number. Ignoring assignment: %s", rvalue); + return 0; + } + + *dest = val; + TAKE_PTR(a); + TAKE_PTR(b); + + return 0; +} + +int config_parse_macsec_key( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(macsec_transmit_association_free_or_set_invalidp) TransmitAssociation *a = NULL; + _cleanup_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL; + _cleanup_(erase_and_freep) void *p = NULL; + MACsec *s = userdata; + SecurityAssociation *dest; + size_t l; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + (void) warn_file_is_world_accessible(filename, NULL, unit, line); + + if (streq(section, "MACsecTransmitAssociation")) + r = macsec_transmit_association_new_static(s, filename, section_line, &a); + else + r = macsec_receive_association_new_static(s, filename, section_line, &b); + if (r < 0) + return log_oom(); + + dest = a ? &a->sa : &b->sa; + + r = unhexmem_full(rvalue, strlen(rvalue), true, &p, &l); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse key. Ignoring assignment: %m"); + return 0; + } + + if (l != 16) { + /* See DEFAULT_SAK_LEN in drivers/net/macsec.c */ + log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid key length (%zu). Ignoring assignment", l); + return 0; + } + + explicit_bzero_safe(dest->key, dest->key_len); + free_and_replace(dest->key, p); + dest->key_len = l; + + TAKE_PTR(a); + TAKE_PTR(b); + + return 0; +} + +int config_parse_macsec_key_file( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(macsec_transmit_association_free_or_set_invalidp) TransmitAssociation *a = NULL; + _cleanup_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL; + _cleanup_free_ char *path = NULL; + MACsec *s = userdata; + char **dest; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + if (streq(section, "MACsecTransmitAssociation")) + r = macsec_transmit_association_new_static(s, filename, section_line, &a); + else + r = macsec_receive_association_new_static(s, filename, section_line, &b); + if (r < 0) + return log_oom(); + + dest = a ? &a->sa.key_file : &b->sa.key_file; + + if (isempty(rvalue)) { + *dest = mfree(*dest); + return 0; + } + + path = strdup(rvalue); + if (!path) + return log_oom(); + + if (path_simplify_and_warn(path, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue) < 0) + return 0; + + free_and_replace(*dest, path); + TAKE_PTR(a); + TAKE_PTR(b); + + return 0; +} + +int config_parse_macsec_key_id( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(macsec_transmit_association_free_or_set_invalidp) TransmitAssociation *a = NULL; + _cleanup_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL; + _cleanup_free_ void *p = NULL; + MACsec *s = userdata; + uint8_t *dest; + size_t l; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + if (streq(section, "MACsecTransmitAssociation")) + r = macsec_transmit_association_new_static(s, filename, section_line, &a); + else + r = macsec_receive_association_new_static(s, filename, section_line, &b); + if (r < 0) + return log_oom(); + + r = unhexmem(rvalue, strlen(rvalue), &p, &l); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse KeyId=%s, ignoring assignment: %m", rvalue); + return 0; + } + if (l > MACSEC_KEYID_LEN) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Specified KeyId= is larger then the allowed maximum (%zu > %u), ignoring: %s", + l, MACSEC_KEYID_LEN, rvalue); + return 0; + } + + dest = a ? a->sa.key_id : b->sa.key_id; + memcpy_safe(dest, p, l); + memzero(dest + l, MACSEC_KEYID_LEN - l); + + TAKE_PTR(a); + TAKE_PTR(b); + + return 0; +} + +int config_parse_macsec_sa_activate( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(macsec_transmit_association_free_or_set_invalidp) TransmitAssociation *a = NULL; + _cleanup_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL; + MACsec *s = userdata; + int *dest; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + if (streq(section, "MACsecTransmitAssociation")) + r = macsec_transmit_association_new_static(s, filename, section_line, &a); + else + r = macsec_receive_association_new_static(s, filename, section_line, &b); + if (r < 0) + return log_oom(); + + dest = a ? &a->sa.activate : &b->sa.activate; + + if (isempty(rvalue)) + r = -1; + else { + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse activation mode of %s security association. " + "Ignoring assignment: %s", + streq(section, "MACsecTransmitAssociation") ? "transmit" : "receive", + rvalue); + return 0; + } + } + + *dest = r; + TAKE_PTR(a); + TAKE_PTR(b); + + return 0; +} + +int config_parse_macsec_use_for_encoding( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(macsec_transmit_association_free_or_set_invalidp) TransmitAssociation *a = NULL; + MACsec *s = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = macsec_transmit_association_new_static(s, filename, section_line, &a); + if (r < 0) + return log_oom(); + + if (isempty(rvalue)) { + a->sa.use_for_encoding = -1; + TAKE_PTR(a); + return 0; + } + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse %s= setting. Ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + a->sa.use_for_encoding = r; + if (a->sa.use_for_encoding > 0) + a->sa.activate = true; + + TAKE_PTR(a); + + return 0; +} + +static int macsec_read_key_file(NetDev *netdev, SecurityAssociation *sa) { + _cleanup_(erase_and_freep) uint8_t *key = NULL; + size_t key_len; + int r; + + assert(netdev); + assert(sa); + + if (!sa->key_file) + return 0; + + (void) warn_file_is_world_accessible(sa->key_file, NULL, NULL, 0); + + r = read_full_file_full( + AT_FDCWD, sa->key_file, + READ_FULL_FILE_SECURE | READ_FULL_FILE_UNHEX | READ_FULL_FILE_WARN_WORLD_READABLE | READ_FULL_FILE_CONNECT_SOCKET, + NULL, (char **) &key, &key_len); + if (r < 0) + return log_netdev_error_errno(netdev, r, + "Failed to read key from '%s', ignoring: %m", + sa->key_file); + + if (key_len != 16) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "Invalid key length (%zu bytes), ignoring: %m", key_len); + + explicit_bzero_safe(sa->key, sa->key_len); + free_and_replace(sa->key, key); + sa->key_len = key_len; + + return 0; +} + +static int macsec_receive_channel_verify(ReceiveChannel *c) { + NetDev *netdev; + int r; + + assert(c); + assert(c->macsec); + + netdev = NETDEV(c->macsec); + + if (section_is_invalid(c->section)) + return -EINVAL; + + if (ether_addr_is_null(&c->sci.mac)) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s: MACsec receive channel without MAC address configured. " + "Ignoring [MACsecReceiveChannel] section from line %u", + c->section->filename, c->section->line); + + if (c->sci.port == 0) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s: MACsec receive channel without port configured. " + "Ignoring [MACsecReceiveChannel] section from line %u", + c->section->filename, c->section->line); + + r = ordered_hashmap_ensure_allocated(&c->macsec->receive_channels, &uint64_hash_ops); + if (r < 0) + return log_oom(); + + r = ordered_hashmap_put(c->macsec->receive_channels, &c->sci.as_uint64, c); + if (r == -EEXIST) + return log_netdev_error_errno(netdev, r, + "%s: Multiple [MACsecReceiveChannel] sections have same SCI, " + "Ignoring [MACsecReceiveChannel] section from line %u", + c->section->filename, c->section->line); + if (r < 0) + return log_netdev_error_errno(netdev, r, + "%s: Failed to store [MACsecReceiveChannel] section at hashmap, " + "Ignoring [MACsecReceiveChannel] section from line %u", + c->section->filename, c->section->line); + return 0; +} + +static int macsec_transmit_association_verify(TransmitAssociation *t) { + NetDev *netdev; + int r; + + assert(t); + assert(t->macsec); + + netdev = NETDEV(t->macsec); + + if (section_is_invalid(t->section)) + return -EINVAL; + + if (t->sa.packet_number == 0) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s: MACsec transmit secure association without PacketNumber= configured. " + "Ignoring [MACsecTransmitAssociation] section from line %u", + t->section->filename, t->section->line); + + r = macsec_read_key_file(netdev, &t->sa); + if (r < 0) + return r; + + if (t->sa.key_len <= 0) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s: MACsec transmit secure association without key configured. " + "Ignoring [MACsecTransmitAssociation] section from line %u", + t->section->filename, t->section->line); + + return 0; +} + +static int macsec_receive_association_verify(ReceiveAssociation *a) { + ReceiveChannel *c; + NetDev *netdev; + int r; + + assert(a); + assert(a->macsec); + + netdev = NETDEV(a->macsec); + + if (section_is_invalid(a->section)) + return -EINVAL; + + r = macsec_read_key_file(netdev, &a->sa); + if (r < 0) + return r; + + if (a->sa.key_len <= 0) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s: MACsec receive secure association without key configured. " + "Ignoring [MACsecReceiveAssociation] section from line %u", + a->section->filename, a->section->line); + + if (ether_addr_is_null(&a->sci.mac)) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s: MACsec receive secure association without MAC address configured. " + "Ignoring [MACsecReceiveAssociation] section from line %u", + a->section->filename, a->section->line); + + if (a->sci.port == 0) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s: MACsec receive secure association without port configured. " + "Ignoring [MACsecReceiveAssociation] section from line %u", + a->section->filename, a->section->line); + + c = ordered_hashmap_get(a->macsec->receive_channels, &a->sci.as_uint64); + if (!c) { + _cleanup_(macsec_receive_channel_freep) ReceiveChannel *new_channel = NULL; + + r = macsec_receive_channel_new(a->macsec, a->sci.as_uint64, &new_channel); + if (r < 0) + return log_oom(); + + r = ordered_hashmap_ensure_allocated(&a->macsec->receive_channels, &uint64_hash_ops); + if (r < 0) + return log_oom(); + + r = ordered_hashmap_put(a->macsec->receive_channels, &new_channel->sci.as_uint64, new_channel); + if (r < 0) + return log_netdev_error_errno(netdev, r, + "%s: Failed to store receive channel at hashmap, " + "Ignoring [MACsecReceiveAssociation] section from line %u", + a->section->filename, a->section->line); + c = TAKE_PTR(new_channel); + } + if (c->n_rxsa >= MACSEC_MAX_ASSOCIATION_NUMBER) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(ERANGE), + "%s: Too many [MACsecReceiveAssociation] sections for the same receive channel, " + "Ignoring [MACsecReceiveAssociation] section from line %u", + a->section->filename, a->section->line); + + a->sa.association_number = c->n_rxsa; + c->rxsa[c->n_rxsa++] = a; + + return 0; +} + +static int netdev_macsec_verify(NetDev *netdev, const char *filename) { + MACsec *v = MACSEC(netdev); + TransmitAssociation *a; + ReceiveAssociation *n; + ReceiveChannel *c; + uint8_t an, encoding_an; + bool use_for_encoding; + int r; + + assert(netdev); + assert(v); + assert(filename); + + ORDERED_HASHMAP_FOREACH(c, v->receive_channels_by_section) { + r = macsec_receive_channel_verify(c); + if (r < 0) + macsec_receive_channel_free(c); + } + + an = 0; + use_for_encoding = false; + encoding_an = 0; + ORDERED_HASHMAP_FOREACH(a, v->transmit_associations_by_section) { + r = macsec_transmit_association_verify(a); + if (r < 0) { + macsec_transmit_association_free(a); + continue; + } + + if (an >= MACSEC_MAX_ASSOCIATION_NUMBER) { + log_netdev_error(netdev, + "%s: Too many [MACsecTransmitAssociation] sections configured. " + "Ignoring [MACsecTransmitAssociation] section from line %u", + a->section->filename, a->section->line); + macsec_transmit_association_free(a); + continue; + } + + a->sa.association_number = an++; + + if (a->sa.use_for_encoding > 0) { + if (use_for_encoding) { + log_netdev_warning(netdev, + "%s: Multiple security associations are set to be used for transmit channel." + "Disabling UseForEncoding= in [MACsecTransmitAssociation] section from line %u", + a->section->filename, a->section->line); + a->sa.use_for_encoding = false; + } else { + encoding_an = a->sa.association_number; + use_for_encoding = true; + } + } + } + + assert(encoding_an < MACSEC_MAX_ASSOCIATION_NUMBER); + v->encoding_an = encoding_an; + + ORDERED_HASHMAP_FOREACH(n, v->receive_associations_by_section) { + r = macsec_receive_association_verify(n); + if (r < 0) + macsec_receive_association_free(n); + } + + return 0; +} + +static void macsec_init(NetDev *netdev) { + MACsec *v; + + assert(netdev); + + v = MACSEC(netdev); + + assert(v); + + v->encrypt = -1; +} + +static void macsec_done(NetDev *netdev) { + MACsec *t; + + assert(netdev); + + t = MACSEC(netdev); + + assert(t); + + ordered_hashmap_free_with_destructor(t->receive_channels, macsec_receive_channel_free); + ordered_hashmap_free_with_destructor(t->receive_channels_by_section, macsec_receive_channel_free); + ordered_hashmap_free_with_destructor(t->transmit_associations_by_section, macsec_transmit_association_free); + ordered_hashmap_free_with_destructor(t->receive_associations_by_section, macsec_receive_association_free); +} + +const NetDevVTable macsec_vtable = { + .object_size = sizeof(MACsec), + .init = macsec_init, + .sections = NETDEV_COMMON_SECTIONS "MACsec\0MACsecReceiveChannel\0MACsecTransmitAssociation\0MACsecReceiveAssociation\0", + .fill_message_create = netdev_macsec_fill_message_create, + .post_create = netdev_macsec_configure, + .done = macsec_done, + .create_type = NETDEV_CREATE_STACKED, + .config_verify = netdev_macsec_verify, + .generate_mac = true, +}; diff --git a/src/network/netdev/macsec.h b/src/network/netdev/macsec.h new file mode 100644 index 0000000..4d88e49 --- /dev/null +++ b/src/network/netdev/macsec.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <netinet/in.h> +#include <linux/if_macsec.h> + +#include "ether-addr-util.h" +#include "in-addr-util.h" +#include "netdev.h" +#include "networkd-util.h" +#include "sparse-endian.h" + +/* See the definition of MACSEC_NUM_AN in kernel's drivers/net/macsec.c */ +#define MACSEC_MAX_ASSOCIATION_NUMBER 4 + +typedef struct MACsec MACsec; + +typedef union MACsecSCI { + uint64_t as_uint64; + + struct { + struct ether_addr mac; + be16_t port; + } _packed_; +} MACsecSCI; + +assert_cc(sizeof(MACsecSCI) == sizeof(uint64_t)); + +typedef struct SecurityAssociation { + uint8_t association_number; + uint32_t packet_number; + uint8_t key_id[MACSEC_KEYID_LEN]; + uint8_t *key; + uint32_t key_len; + char *key_file; + int activate; + int use_for_encoding; +} SecurityAssociation; + +typedef struct TransmitAssociation { + MACsec *macsec; + NetworkConfigSection *section; + + SecurityAssociation sa; +} TransmitAssociation; + +typedef struct ReceiveAssociation { + MACsec *macsec; + NetworkConfigSection *section; + + MACsecSCI sci; + SecurityAssociation sa; +} ReceiveAssociation; + +typedef struct ReceiveChannel { + MACsec *macsec; + NetworkConfigSection *section; + + MACsecSCI sci; + ReceiveAssociation *rxsa[MACSEC_MAX_ASSOCIATION_NUMBER]; + unsigned n_rxsa; +} ReceiveChannel; + +struct MACsec { + NetDev meta; + + uint16_t port; + int encrypt; + uint8_t encoding_an; + + OrderedHashmap *receive_channels; + OrderedHashmap *receive_channels_by_section; + OrderedHashmap *transmit_associations_by_section; + OrderedHashmap *receive_associations_by_section; +}; + +DEFINE_NETDEV_CAST(MACSEC, MACsec); +extern const NetDevVTable macsec_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_macsec_port); +CONFIG_PARSER_PROTOTYPE(config_parse_macsec_hw_address); +CONFIG_PARSER_PROTOTYPE(config_parse_macsec_packet_number); +CONFIG_PARSER_PROTOTYPE(config_parse_macsec_key_id); +CONFIG_PARSER_PROTOTYPE(config_parse_macsec_key); +CONFIG_PARSER_PROTOTYPE(config_parse_macsec_key_file); +CONFIG_PARSER_PROTOTYPE(config_parse_macsec_sa_activate); +CONFIG_PARSER_PROTOTYPE(config_parse_macsec_use_for_encoding); diff --git a/src/network/netdev/macvlan.c b/src/network/netdev/macvlan.c new file mode 100644 index 0000000..9bdcf62 --- /dev/null +++ b/src/network/netdev/macvlan.c @@ -0,0 +1,105 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <net/if.h> + +#include "conf-parser.h" +#include "macvlan.h" +#include "macvlan-util.h" + +DEFINE_CONFIG_PARSE_ENUM(config_parse_macvlan_mode, macvlan_mode, MacVlanMode, "Failed to parse macvlan mode"); + +static int netdev_macvlan_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *req) { + MacVlan *m; + int r; + + assert(netdev); + assert(link); + assert(netdev->ifname); + + if (netdev->kind == NETDEV_KIND_MACVLAN) + m = MACVLAN(netdev); + else + m = MACVTAP(netdev); + + assert(m); + + if (m->mode == NETDEV_MACVLAN_MODE_SOURCE && !set_isempty(m->match_source_mac)) { + const struct ether_addr *mac_addr; + + r = sd_netlink_message_append_u32(req, IFLA_MACVLAN_MACADDR_MODE, MACVLAN_MACADDR_SET); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_MACVLAN_MACADDR_MODE attribute: %m"); + + r = sd_netlink_message_open_container(req, IFLA_MACVLAN_MACADDR_DATA); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not open IFLA_MACVLAN_MACADDR_DATA container: %m"); + + SET_FOREACH(mac_addr, m->match_source_mac) { + r = sd_netlink_message_append_ether_addr(req, IFLA_MACVLAN_MACADDR, mac_addr); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_MACVLAN_MACADDR attribute: %m"); + } + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not close IFLA_MACVLAN_MACADDR_DATA container: %m"); + } + + if (m->mode != _NETDEV_MACVLAN_MODE_INVALID) { + r = sd_netlink_message_append_u32(req, IFLA_MACVLAN_MODE, m->mode); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_MACVLAN_MODE attribute: %m"); + } + + return 0; +} + +static void macvlan_done(NetDev *n) { + MacVlan *m; + + assert(n); + + if (n->kind == NETDEV_KIND_MACVLAN) + m = MACVLAN(n); + else + m = MACVTAP(n); + + assert(m); + + set_free_free(m->match_source_mac); +} + +static void macvlan_init(NetDev *n) { + MacVlan *m; + + assert(n); + + if (n->kind == NETDEV_KIND_MACVLAN) + m = MACVLAN(n); + else + m = MACVTAP(n); + + assert(m); + + m->mode = _NETDEV_MACVLAN_MODE_INVALID; +} + +const NetDevVTable macvtap_vtable = { + .object_size = sizeof(MacVlan), + .init = macvlan_init, + .done = macvlan_done, + .sections = NETDEV_COMMON_SECTIONS "MACVTAP\0", + .fill_message_create = netdev_macvlan_fill_message_create, + .create_type = NETDEV_CREATE_STACKED, + .generate_mac = true, +}; + +const NetDevVTable macvlan_vtable = { + .object_size = sizeof(MacVlan), + .init = macvlan_init, + .done = macvlan_done, + .sections = NETDEV_COMMON_SECTIONS "MACVLAN\0", + .fill_message_create = netdev_macvlan_fill_message_create, + .create_type = NETDEV_CREATE_STACKED, + .generate_mac = true, +}; diff --git a/src/network/netdev/macvlan.h b/src/network/netdev/macvlan.h new file mode 100644 index 0000000..cb7eece --- /dev/null +++ b/src/network/netdev/macvlan.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +typedef struct MacVlan MacVlan; + +#include "macvlan-util.h" +#include "netdev.h" +#include "set.h" + +struct MacVlan { + NetDev meta; + + MacVlanMode mode; + Set *match_source_mac; +}; + +DEFINE_NETDEV_CAST(MACVLAN, MacVlan); +DEFINE_NETDEV_CAST(MACVTAP, MacVlan); +extern const NetDevVTable macvlan_vtable; +extern const NetDevVTable macvtap_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_macvlan_mode); diff --git a/src/network/netdev/netdev-gperf.gperf b/src/network/netdev/netdev-gperf.gperf new file mode 100644 index 0000000..4e89761 --- /dev/null +++ b/src/network/netdev/netdev-gperf.gperf @@ -0,0 +1,232 @@ +%{ +#if __GNUC__ >= 7 +_Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"") +#endif +#include <stddef.h> +#include "bareudp.h" +#include "bond.h" +#include "bridge.h" +#include "conf-parser.h" +#include "geneve.h" +#include "ipvlan.h" +#include "macsec.h" +#include "macvlan.h" +#include "tunnel.h" +#include "tuntap.h" +#include "veth.h" +#include "vlan-util.h" +#include "vlan.h" +#include "vxlan.h" +#include "vrf.h" +#include "netdev.h" +#include "network-internal.h" +#include "vxcan.h" +#include "wireguard.h" +#include "fou-tunnel.h" +#include "l2tp-tunnel.h" +#include "xfrm.h" +%} +struct ConfigPerfItem; +%null_strings +%language=ANSI-C +%define slot-name section_and_lvalue +%define hash-function-name network_netdev_gperf_hash +%define lookup-function-name network_netdev_gperf_lookup +%readonly-tables +%omit-struct-type +%struct-type +%includes +%% +Match.Host, config_parse_net_condition, CONDITION_HOST, offsetof(NetDev, conditions) +Match.Virtualization, config_parse_net_condition, CONDITION_VIRTUALIZATION, offsetof(NetDev, conditions) +Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(NetDev, conditions) +Match.KernelVersion, config_parse_net_condition, CONDITION_KERNEL_VERSION, offsetof(NetDev, conditions) +Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(NetDev, conditions) +NetDev.Description, config_parse_string, 0, offsetof(NetDev, description) +NetDev.Name, config_parse_ifname, 0, offsetof(NetDev, ifname) +NetDev.Kind, config_parse_netdev_kind, 0, offsetof(NetDev, kind) +NetDev.MTUBytes, config_parse_mtu, AF_UNSPEC, offsetof(NetDev, mtu) +NetDev.MACAddress, config_parse_hwaddr, 0, offsetof(NetDev, mac) +VLAN.Id, config_parse_vlanid, 0, offsetof(VLan, id) +VLAN.GVRP, config_parse_tristate, 0, offsetof(VLan, gvrp) +VLAN.MVRP, config_parse_tristate, 0, offsetof(VLan, mvrp) +VLAN.LooseBinding, config_parse_tristate, 0, offsetof(VLan, loose_binding) +VLAN.ReorderHeader, config_parse_tristate, 0, offsetof(VLan, reorder_hdr) +MACVLAN.Mode, config_parse_macvlan_mode, 0, offsetof(MacVlan, mode) +MACVLAN.SourceMACAddress, config_parse_hwaddrs, 0, offsetof(MacVlan, match_source_mac) +MACVTAP.Mode, config_parse_macvlan_mode, 0, offsetof(MacVlan, mode) +MACVTAP.SourceMACAddress, config_parse_hwaddrs, 0, offsetof(MacVlan, match_source_mac) +IPVLAN.Mode, config_parse_ipvlan_mode, 0, offsetof(IPVlan, mode) +IPVLAN.Flags, config_parse_ipvlan_flags, 0, offsetof(IPVlan, flags) +IPVTAP.Mode, config_parse_ipvlan_mode, 0, offsetof(IPVlan, mode) +IPVTAP.Flags, config_parse_ipvlan_flags, 0, offsetof(IPVlan, flags) +Tunnel.Local, config_parse_tunnel_address, 0, offsetof(Tunnel, local) +Tunnel.Remote, config_parse_tunnel_address, 0, offsetof(Tunnel, remote) +Tunnel.TOS, config_parse_unsigned, 0, offsetof(Tunnel, tos) +Tunnel.TTL, config_parse_unsigned, 0, offsetof(Tunnel, ttl) +Tunnel.Key, config_parse_tunnel_key, 0, offsetof(Tunnel, key) +Tunnel.InputKey, config_parse_tunnel_key, 0, offsetof(Tunnel, ikey) +Tunnel.OutputKey, config_parse_tunnel_key, 0, offsetof(Tunnel, okey) +Tunnel.DiscoverPathMTU, config_parse_bool, 0, offsetof(Tunnel, pmtudisc) +Tunnel.Mode, config_parse_ip6tnl_mode, 0, offsetof(Tunnel, ip6tnl_mode) +Tunnel.IPv6FlowLabel, config_parse_ipv6_flowlabel, 0, offsetof(Tunnel, ipv6_flowlabel) +Tunnel.CopyDSCP, config_parse_bool, 0, offsetof(Tunnel, copy_dscp) +Tunnel.EncapsulationLimit, config_parse_encap_limit, 0, offsetof(Tunnel, encap_limit) +Tunnel.Independent, config_parse_bool, 0, offsetof(Tunnel, independent) +Tunnel.AssignToLoopback, config_parse_bool, 0, offsetof(Tunnel, assign_to_loopback) +Tunnel.AllowLocalRemote, config_parse_tristate, 0, offsetof(Tunnel, allow_localremote) +Tunnel.FooOverUDP, config_parse_bool, 0, offsetof(Tunnel, fou_tunnel) +Tunnel.FOUDestinationPort, config_parse_ip_port, 0, offsetof(Tunnel, fou_destination_port) +Tunnel.FOUSourcePort, config_parse_ip_port, 0, offsetof(Tunnel, encap_src_port) +Tunnel.Encapsulation, config_parse_fou_encap_type, 0, offsetof(Tunnel, fou_encap_type) +Tunnel.IPv6RapidDeploymentPrefix, config_parse_6rd_prefix, 0, 0 +Tunnel.ERSPANIndex, config_parse_uint32, 0, offsetof(Tunnel, erspan_index) +Tunnel.SerializeTunneledPackets, config_parse_tristate, 0, offsetof(Tunnel, gre_erspan_sequence) +Tunnel.ISATAP, config_parse_tristate, 0, offsetof(Tunnel, isatap) +FooOverUDP.Protocol, config_parse_ip_protocol, 0, offsetof(FouTunnel, fou_protocol) +FooOverUDP.Encapsulation, config_parse_fou_encap_type, 0, offsetof(FouTunnel, fou_encap_type) +FooOverUDP.Port, config_parse_ip_port, 0, offsetof(FouTunnel, port) +FooOverUDP.PeerPort, config_parse_ip_port, 0, offsetof(FouTunnel, peer_port) +FooOverUDP.Local, config_parse_fou_tunnel_address, 0, offsetof(FouTunnel, local) +FooOverUDP.Peer, config_parse_fou_tunnel_address, 0, offsetof(FouTunnel, peer) +L2TP.TunnelId, config_parse_l2tp_tunnel_id, 0, offsetof(L2tpTunnel, tunnel_id) +L2TP.PeerTunnelId, config_parse_l2tp_tunnel_id, 0, offsetof(L2tpTunnel, peer_tunnel_id) +L2TP.UDPSourcePort, config_parse_ip_port, 0, offsetof(L2tpTunnel, l2tp_udp_sport) +L2TP.UDPDestinationPort, config_parse_ip_port, 0, offsetof(L2tpTunnel, l2tp_udp_dport) +L2TP.Local, config_parse_l2tp_tunnel_address, 0, offsetof(L2tpTunnel, local) +L2TP.Remote, config_parse_l2tp_tunnel_address, 0, offsetof(L2tpTunnel, remote) +L2TP.EncapsulationType, config_parse_l2tp_encap_type, 0, offsetof(L2tpTunnel, l2tp_encap_type) +L2TP.UDPCheckSum, config_parse_bool, 0, offsetof(L2tpTunnel, udp_csum) +L2TP.UDP6CheckSumRx, config_parse_bool, 0, offsetof(L2tpTunnel, udp6_csum_rx) +L2TP.UDP6CheckSumTx, config_parse_bool, 0, offsetof(L2tpTunnel, udp6_csum_tx) +L2TPSession.SessionId, config_parse_l2tp_session_id, 0, 0 +L2TPSession.PeerSessionId, config_parse_l2tp_session_id, 0, 0 +L2TPSession.Layer2SpecificHeader, config_parse_l2tp_session_l2spec, 0, 0 +L2TPSession.Name, config_parse_l2tp_session_name, 0, 0 +Peer.Name, config_parse_ifname, 0, offsetof(Veth, ifname_peer) +Peer.MACAddress, config_parse_hwaddr, 0, offsetof(Veth, mac_peer) +VXCAN.Peer, config_parse_ifname, 0, offsetof(VxCan, ifname_peer) +VXLAN.VNI, config_parse_uint32, 0, offsetof(VxLan, vni) +VXLAN.Id, config_parse_uint32, 0, offsetof(VxLan, vni) /* deprecated */ +VXLAN.Group, config_parse_vxlan_address, 0, offsetof(VxLan, group) +VXLAN.Local, config_parse_vxlan_address, 0, offsetof(VxLan, local) +VXLAN.Remote, config_parse_vxlan_address, 0, offsetof(VxLan, remote) +VXLAN.TOS, config_parse_unsigned, 0, offsetof(VxLan, tos) +VXLAN.TTL, config_parse_vxlan_ttl, 0, offsetof(VxLan, ttl) +VXLAN.MacLearning, config_parse_bool, 0, offsetof(VxLan, learning) +VXLAN.ARPProxy, config_parse_bool, 0, offsetof(VxLan, arp_proxy) +VXLAN.ReduceARPProxy, config_parse_bool, 0, offsetof(VxLan, arp_proxy) +VXLAN.L2MissNotification, config_parse_bool, 0, offsetof(VxLan, l2miss) +VXLAN.L3MissNotification, config_parse_bool, 0, offsetof(VxLan, l3miss) +VXLAN.RouteShortCircuit, config_parse_bool, 0, offsetof(VxLan, route_short_circuit) +VXLAN.UDPCheckSum, config_parse_bool, 0, offsetof(VxLan, udpcsum) +VXLAN.UDPChecksum, config_parse_bool, 0, offsetof(VxLan, udpcsum) +VXLAN.UDP6ZeroCheckSumRx, config_parse_bool, 0, offsetof(VxLan, udp6zerocsumrx) +VXLAN.UDP6ZeroChecksumRx, config_parse_bool, 0, offsetof(VxLan, udp6zerocsumrx) +VXLAN.UDP6ZeroCheckSumTx, config_parse_bool, 0, offsetof(VxLan, udp6zerocsumtx) +VXLAN.UDP6ZeroChecksumTx, config_parse_bool, 0, offsetof(VxLan, udp6zerocsumtx) +VXLAN.RemoteChecksumTx, config_parse_bool, 0, offsetof(VxLan, remote_csum_tx) +VXLAN.RemoteChecksumRx, config_parse_bool, 0, offsetof(VxLan, remote_csum_rx) +VXLAN.FDBAgeingSec, config_parse_sec, 0, offsetof(VxLan, fdb_ageing) +VXLAN.GroupPolicyExtension, config_parse_bool, 0, offsetof(VxLan, group_policy) +VXLAN.GenericProtocolExtension, config_parse_bool, 0, offsetof(VxLan, generic_protocol_extension) +VXLAN.MaximumFDBEntries, config_parse_unsigned, 0, offsetof(VxLan, max_fdb) +VXLAN.PortRange, config_parse_port_range, 0, 0 +VXLAN.DestinationPort, config_parse_ip_port, 0, offsetof(VxLan, dest_port) +VXLAN.FlowLabel, config_parse_flow_label, 0, 0 +VXLAN.IPDoNotFragment, config_parse_df, 0, offsetof(VxLan, df) +VXLAN.Independent, config_parse_bool, 0, offsetof(VxLan, independent) +GENEVE.Id, config_parse_geneve_vni, 0, offsetof(Geneve, id) +GENEVE.Remote, config_parse_geneve_address, 0, offsetof(Geneve, remote) +GENEVE.TOS, config_parse_uint8, 0, offsetof(Geneve, tos) +GENEVE.TTL, config_parse_geneve_ttl, 0, offsetof(Geneve, ttl) +GENEVE.UDPChecksum, config_parse_bool, 0, offsetof(Geneve, udpcsum) +GENEVE.UDP6ZeroCheckSumRx, config_parse_bool, 0, offsetof(Geneve, udp6zerocsumrx) +GENEVE.UDP6ZeroChecksumRx, config_parse_bool, 0, offsetof(Geneve, udp6zerocsumrx) +GENEVE.UDP6ZeroCheckSumTx, config_parse_bool, 0, offsetof(Geneve, udp6zerocsumtx) +GENEVE.UDP6ZeroChecksumTx, config_parse_bool, 0, offsetof(Geneve, udp6zerocsumtx) +GENEVE.DestinationPort, config_parse_ip_port, 0, offsetof(Geneve, dest_port) +GENEVE.IPDoNotFragment, config_parse_geneve_df, 0, offsetof(Geneve, geneve_df) +GENEVE.FlowLabel, config_parse_geneve_flow_label, 0, 0 +MACsec.Port, config_parse_macsec_port, 0, 0 +MACsec.Encrypt, config_parse_tristate, 0, offsetof(MACsec, encrypt) +MACsecReceiveChannel.Port, config_parse_macsec_port, 0, 0 +MACsecReceiveChannel.MACAddress, config_parse_macsec_hw_address, 0, 0 +MACsecTransmitAssociation.PacketNumber, config_parse_macsec_packet_number, 0, 0 +MACsecTransmitAssociation.KeyId, config_parse_macsec_key_id, 0, 0 +MACsecTransmitAssociation.Key, config_parse_macsec_key, 0, 0 +MACsecTransmitAssociation.KeyFile, config_parse_macsec_key_file, 0, 0 +MACsecTransmitAssociation.Activate, config_parse_macsec_sa_activate, 0, 0 +MACsecTransmitAssociation.UseForEncoding, config_parse_macsec_use_for_encoding, 0, 0 +MACsecReceiveAssociation.Port, config_parse_macsec_port, 0, 0 +MACsecReceiveAssociation.MACAddress, config_parse_macsec_hw_address, 0, 0 +MACsecReceiveAssociation.PacketNumber, config_parse_macsec_packet_number, 0, 0 +MACsecReceiveAssociation.KeyId, config_parse_macsec_key_id, 0, 0 +MACsecReceiveAssociation.Key, config_parse_macsec_key, 0, 0 +MACsecReceiveAssociation.KeyFile, config_parse_macsec_key_file, 0, 0 +MACsecReceiveAssociation.Activate, config_parse_macsec_sa_activate, 0, 0 +Tun.OneQueue, config_parse_warn_compat, DISABLED_LEGACY, 0 +Tun.MultiQueue, config_parse_bool, 0, offsetof(TunTap, multi_queue) +Tun.PacketInfo, config_parse_bool, 0, offsetof(TunTap, packet_info) +Tun.VNetHeader, config_parse_bool, 0, offsetof(TunTap, vnet_hdr) +Tun.User, config_parse_string, 0, offsetof(TunTap, user_name) +Tun.Group, config_parse_string, 0, offsetof(TunTap, group_name) +Tap.OneQueue, config_parse_warn_compat, DISABLED_LEGACY, 0 +Tap.MultiQueue, config_parse_bool, 0, offsetof(TunTap, multi_queue) +Tap.PacketInfo, config_parse_bool, 0, offsetof(TunTap, packet_info) +Tap.VNetHeader, config_parse_bool, 0, offsetof(TunTap, vnet_hdr) +Tap.User, config_parse_string, 0, offsetof(TunTap, user_name) +Tap.Group, config_parse_string, 0, offsetof(TunTap, group_name) +Bond.Mode, config_parse_bond_mode, 0, offsetof(Bond, mode) +Bond.TransmitHashPolicy, config_parse_bond_xmit_hash_policy, 0, offsetof(Bond, xmit_hash_policy) +Bond.LACPTransmitRate, config_parse_bond_lacp_rate, 0, offsetof(Bond, lacp_rate) +Bond.AdSelect, config_parse_bond_ad_select, 0, offsetof(Bond, ad_select) +Bond.FailOverMACPolicy, config_parse_bond_fail_over_mac, 0, offsetof(Bond, fail_over_mac) +Bond.ARPIPTargets, config_parse_arp_ip_target_address, 0, 0 +Bond.ARPValidate, config_parse_bond_arp_validate, 0, offsetof(Bond, arp_validate) +Bond.ARPAllTargets, config_parse_bond_arp_all_targets, 0, offsetof(Bond, arp_all_targets) +Bond.PrimaryReselectPolicy, config_parse_bond_primary_reselect, 0, offsetof(Bond, primary_reselect) +Bond.ResendIGMP, config_parse_unsigned, 0, offsetof(Bond, resend_igmp) +Bond.PacketsPerSlave, config_parse_unsigned, 0, offsetof(Bond, packets_per_slave) +Bond.GratuitousARP, config_parse_unsigned, 0, offsetof(Bond, num_grat_arp) +Bond.AllSlavesActive, config_parse_bool, 0, offsetof(Bond, all_slaves_active) +Bond.DynamicTransmitLoadBalancing, config_parse_tristate, 0, offsetof(Bond, tlb_dynamic_lb) +Bond.MinLinks, config_parse_unsigned, 0, offsetof(Bond, min_links) +Bond.MIIMonitorSec, config_parse_sec, 0, offsetof(Bond, miimon) +Bond.UpDelaySec, config_parse_sec, 0, offsetof(Bond, updelay) +Bond.DownDelaySec, config_parse_sec, 0, offsetof(Bond, downdelay) +Bond.ARPIntervalSec, config_parse_sec, 0, offsetof(Bond, arp_interval) +Bond.LearnPacketIntervalSec, config_parse_sec, 0, offsetof(Bond, lp_interval) +Bond.AdActorSystemPriority, config_parse_ad_actor_sys_prio, 0, offsetof(Bond, ad_actor_sys_prio) +Bond.AdUserPortKey, config_parse_ad_user_port_key, 0, offsetof(Bond, ad_user_port_key) +Bond.AdActorSystem, config_parse_ad_actor_system, 0, offsetof(Bond, ad_actor_system) +Bridge.HelloTimeSec, config_parse_sec, 0, offsetof(Bridge, hello_time) +Bridge.MaxAgeSec, config_parse_sec, 0, offsetof(Bridge, max_age) +Bridge.AgeingTimeSec, config_parse_sec, 0, offsetof(Bridge, ageing_time) +Bridge.ForwardDelaySec, config_parse_sec, 0, offsetof(Bridge, forward_delay) +Bridge.Priority, config_parse_uint16, 0, offsetof(Bridge, priority) +Bridge.GroupForwardMask, config_parse_uint16, 0, offsetof(Bridge, group_fwd_mask) +Bridge.DefaultPVID, config_parse_default_port_vlanid, 0, offsetof(Bridge, default_pvid) +Bridge.MulticastQuerier, config_parse_tristate, 0, offsetof(Bridge, mcast_querier) +Bridge.MulticastSnooping, config_parse_tristate, 0, offsetof(Bridge, mcast_snooping) +Bridge.VLANFiltering, config_parse_tristate, 0, offsetof(Bridge, vlan_filtering) +Bridge.VLANProtocol, config_parse_vlanprotocol, 0, offsetof(Bridge, vlan_protocol) +Bridge.STP, config_parse_tristate, 0, offsetof(Bridge, stp) +Bridge.MulticastIGMPVersion, config_parse_uint8, 0, offsetof(Bridge, igmp_version) +VRF.TableId, config_parse_uint32, 0, offsetof(Vrf, table) /* deprecated */ +VRF.Table, config_parse_uint32, 0, offsetof(Vrf, table) +BareUDP.DestinationPort, config_parse_ip_port, 0, offsetof(BareUDP, dest_port) +BareUDP.EtherType, config_parse_bare_udp_iftype, 0, offsetof(BareUDP, iftype) +WireGuard.FirewallMark, config_parse_unsigned, 0, offsetof(Wireguard, fwmark) +WireGuard.FwMark, config_parse_unsigned, 0, offsetof(Wireguard, fwmark) /* deprecated */ +WireGuard.ListenPort, config_parse_wireguard_listen_port, 0, offsetof(Wireguard, port) +WireGuard.PrivateKey, config_parse_wireguard_private_key, 0, 0 +WireGuard.PrivateKeyFile, config_parse_wireguard_private_key_file, 0, 0 +WireGuardPeer.AllowedIPs, config_parse_wireguard_allowed_ips, 0, 0 +WireGuardPeer.Endpoint, config_parse_wireguard_endpoint, 0, 0 +WireGuardPeer.PublicKey, config_parse_wireguard_peer_key, 0, 0 +WireGuardPeer.PresharedKey, config_parse_wireguard_peer_key, 0, 0 +WireGuardPeer.PresharedKeyFile, config_parse_wireguard_preshared_key_file, 0, 0 +WireGuardPeer.PersistentKeepalive, config_parse_wireguard_keepalive, 0, 0 +Xfrm.InterfaceId, config_parse_uint32, 0, offsetof(Xfrm, if_id) +Xfrm.Independent, config_parse_bool, 0, offsetof(Xfrm, independent) diff --git a/src/network/netdev/netdev.c b/src/network/netdev/netdev.c new file mode 100644 index 0000000..9f390b5 --- /dev/null +++ b/src/network/netdev/netdev.c @@ -0,0 +1,868 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <net/if.h> +#include <netinet/in.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "bareudp.h" +#include "bond.h" +#include "bridge.h" +#include "conf-files.h" +#include "conf-parser.h" +#include "dummy.h" +#include "fd-util.h" +#include "fou-tunnel.h" +#include "geneve.h" +#include "ifb.h" +#include "ipvlan.h" +#include "l2tp-tunnel.h" +#include "list.h" +#include "macsec.h" +#include "macvlan.h" +#include "netdev.h" +#include "netdevsim.h" +#include "netlink-util.h" +#include "network-internal.h" +#include "networkd-manager.h" +#include "nlmon.h" +#include "path-lookup.h" +#include "siphash24.h" +#include "stat-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "tunnel.h" +#include "tuntap.h" +#include "vcan.h" +#include "veth.h" +#include "vlan.h" +#include "vrf.h" +#include "vxcan.h" +#include "vxlan.h" +#include "wireguard.h" +#include "xfrm.h" + +const NetDevVTable * const netdev_vtable[_NETDEV_KIND_MAX] = { + [NETDEV_KIND_BRIDGE] = &bridge_vtable, + [NETDEV_KIND_BOND] = &bond_vtable, + [NETDEV_KIND_VLAN] = &vlan_vtable, + [NETDEV_KIND_MACVLAN] = &macvlan_vtable, + [NETDEV_KIND_MACVTAP] = &macvtap_vtable, + [NETDEV_KIND_IPVLAN] = &ipvlan_vtable, + [NETDEV_KIND_IPVTAP] = &ipvtap_vtable, + [NETDEV_KIND_VXLAN] = &vxlan_vtable, + [NETDEV_KIND_IPIP] = &ipip_vtable, + [NETDEV_KIND_GRE] = &gre_vtable, + [NETDEV_KIND_GRETAP] = &gretap_vtable, + [NETDEV_KIND_IP6GRE] = &ip6gre_vtable, + [NETDEV_KIND_IP6GRETAP] = &ip6gretap_vtable, + [NETDEV_KIND_SIT] = &sit_vtable, + [NETDEV_KIND_VTI] = &vti_vtable, + [NETDEV_KIND_VTI6] = &vti6_vtable, + [NETDEV_KIND_VETH] = &veth_vtable, + [NETDEV_KIND_DUMMY] = &dummy_vtable, + [NETDEV_KIND_TUN] = &tun_vtable, + [NETDEV_KIND_TAP] = &tap_vtable, + [NETDEV_KIND_IP6TNL] = &ip6tnl_vtable, + [NETDEV_KIND_VRF] = &vrf_vtable, + [NETDEV_KIND_VCAN] = &vcan_vtable, + [NETDEV_KIND_GENEVE] = &geneve_vtable, + [NETDEV_KIND_VXCAN] = &vxcan_vtable, + [NETDEV_KIND_WIREGUARD] = &wireguard_vtable, + [NETDEV_KIND_NETDEVSIM] = &netdevsim_vtable, + [NETDEV_KIND_FOU] = &foutnl_vtable, + [NETDEV_KIND_ERSPAN] = &erspan_vtable, + [NETDEV_KIND_L2TP] = &l2tptnl_vtable, + [NETDEV_KIND_MACSEC] = &macsec_vtable, + [NETDEV_KIND_NLMON] = &nlmon_vtable, + [NETDEV_KIND_XFRM] = &xfrm_vtable, + [NETDEV_KIND_IFB] = &ifb_vtable, + [NETDEV_KIND_BAREUDP] = &bare_udp_vtable, +}; + +static const char* const netdev_kind_table[_NETDEV_KIND_MAX] = { + [NETDEV_KIND_BAREUDP] = "bareudp", + [NETDEV_KIND_BRIDGE] = "bridge", + [NETDEV_KIND_BOND] = "bond", + [NETDEV_KIND_VLAN] = "vlan", + [NETDEV_KIND_MACVLAN] = "macvlan", + [NETDEV_KIND_MACVTAP] = "macvtap", + [NETDEV_KIND_IPVLAN] = "ipvlan", + [NETDEV_KIND_IPVTAP] = "ipvtap", + [NETDEV_KIND_VXLAN] = "vxlan", + [NETDEV_KIND_IPIP] = "ipip", + [NETDEV_KIND_GRE] = "gre", + [NETDEV_KIND_GRETAP] = "gretap", + [NETDEV_KIND_IP6GRE] = "ip6gre", + [NETDEV_KIND_IP6GRETAP] = "ip6gretap", + [NETDEV_KIND_SIT] = "sit", + [NETDEV_KIND_VETH] = "veth", + [NETDEV_KIND_VTI] = "vti", + [NETDEV_KIND_VTI6] = "vti6", + [NETDEV_KIND_DUMMY] = "dummy", + [NETDEV_KIND_TUN] = "tun", + [NETDEV_KIND_TAP] = "tap", + [NETDEV_KIND_IP6TNL] = "ip6tnl", + [NETDEV_KIND_VRF] = "vrf", + [NETDEV_KIND_VCAN] = "vcan", + [NETDEV_KIND_GENEVE] = "geneve", + [NETDEV_KIND_VXCAN] = "vxcan", + [NETDEV_KIND_WIREGUARD] = "wireguard", + [NETDEV_KIND_NETDEVSIM] = "netdevsim", + [NETDEV_KIND_FOU] = "fou", + [NETDEV_KIND_ERSPAN] = "erspan", + [NETDEV_KIND_L2TP] = "l2tp", + [NETDEV_KIND_MACSEC] = "macsec", + [NETDEV_KIND_NLMON] = "nlmon", + [NETDEV_KIND_XFRM] = "xfrm", + [NETDEV_KIND_IFB] = "ifb", +}; + +DEFINE_STRING_TABLE_LOOKUP(netdev_kind, NetDevKind); + +int config_parse_netdev_kind( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + NetDevKind k, *kind = data; + + assert(rvalue); + assert(data); + + k = netdev_kind_from_string(rvalue); + if (k < 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Failed to parse netdev kind, ignoring assignment: %s", rvalue); + return 0; + } + + if (*kind != _NETDEV_KIND_INVALID && *kind != k) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Specified netdev kind is different from the previous value '%s', ignoring assignment: %s", + netdev_kind_to_string(*kind), rvalue); + return 0; + } + + *kind = k; + + return 0; +} + +static void netdev_callbacks_clear(NetDev *netdev) { + netdev_join_callback *callback; + + if (!netdev) + return; + + while ((callback = netdev->callbacks)) { + LIST_REMOVE(callbacks, netdev->callbacks, callback); + link_unref(callback->link); + free(callback); + } +} + +bool netdev_is_managed(NetDev *netdev) { + if (!netdev || !netdev->manager || !netdev->ifname) + return false; + + return hashmap_get(netdev->manager->netdevs, netdev->ifname) == netdev; +} + +static void netdev_detach_from_manager(NetDev *netdev) { + if (netdev->ifname && netdev->manager) + hashmap_remove(netdev->manager->netdevs, netdev->ifname); +} + +static NetDev *netdev_free(NetDev *netdev) { + assert(netdev); + + netdev_callbacks_clear(netdev); + + netdev_detach_from_manager(netdev); + + free(netdev->filename); + + free(netdev->description); + free(netdev->ifname); + free(netdev->mac); + condition_free_list(netdev->conditions); + + /* Invoke the per-kind done() destructor, but only if the state field is initialized. We conditionalize that + * because we parse .netdev files twice: once to determine the kind (with a short, minimal NetDev structure + * allocation, with no room for per-kind fields), and once to read the kind's properties (with a full, + * comprehensive NetDev structure allocation with enough space for whatever the specific kind needs). Now, in + * the first case we shouldn't try to destruct the per-kind NetDev fields on destruction, in the second case we + * should. We use the state field to discern the two cases: it's _NETDEV_STATE_INVALID on the first "raw" + * call. */ + if (netdev->state != _NETDEV_STATE_INVALID && + NETDEV_VTABLE(netdev) && + NETDEV_VTABLE(netdev)->done) + NETDEV_VTABLE(netdev)->done(netdev); + + return mfree(netdev); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(NetDev, netdev, netdev_free); + +void netdev_drop(NetDev *netdev) { + if (!netdev || netdev->state == NETDEV_STATE_LINGER) + return; + + netdev->state = NETDEV_STATE_LINGER; + + log_netdev_debug(netdev, "netdev removed"); + + netdev_callbacks_clear(netdev); + + netdev_detach_from_manager(netdev); + + netdev_unref(netdev); + + return; +} + +int netdev_get(Manager *manager, const char *name, NetDev **ret) { + NetDev *netdev; + + assert(manager); + assert(name); + assert(ret); + + netdev = hashmap_get(manager->netdevs, name); + if (!netdev) { + *ret = NULL; + return -ENOENT; + } + + *ret = netdev; + + return 0; +} + +static int netdev_enter_failed(NetDev *netdev) { + netdev->state = NETDEV_STATE_FAILED; + + netdev_callbacks_clear(netdev); + + return 0; +} + +static int netdev_enslave_ready(NetDev *netdev, Link* link, link_netlink_message_handler_t callback) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(netdev); + assert(netdev->state == NETDEV_STATE_READY); + assert(netdev->manager); + assert(netdev->manager->rtnl); + assert(IN_SET(netdev->kind, NETDEV_KIND_BRIDGE, NETDEV_KIND_BOND, NETDEV_KIND_VRF)); + assert(link); + assert(callback); + + if (link->flags & IFF_UP && netdev->kind == NETDEV_KIND_BOND) { + log_netdev_debug(netdev, "Link '%s' was up when attempting to enslave it. Bringing link down.", link->ifname); + r = link_down(link, NULL); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not bring link down: %m"); + } + + r = sd_rtnl_message_new_link(netdev->manager->rtnl, &req, RTM_SETLINK, link->ifindex); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not allocate RTM_SETLINK message: %m"); + + r = sd_netlink_message_append_u32(req, IFLA_MASTER, netdev->ifindex); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_MASTER attribute: %m"); + + r = netlink_call_async(netdev->manager->rtnl, NULL, req, callback, + link_netlink_destroy_callback, link); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + log_netdev_debug(netdev, "Enslaving link '%s'", link->ifname); + + return 0; +} + +static int netdev_enter_ready(NetDev *netdev) { + netdev_join_callback *callback, *callback_next; + int r; + + assert(netdev); + assert(netdev->ifname); + + if (netdev->state != NETDEV_STATE_CREATING) + return 0; + + netdev->state = NETDEV_STATE_READY; + + log_netdev_info(netdev, "netdev ready"); + + LIST_FOREACH_SAFE(callbacks, callback, callback_next, netdev->callbacks) { + /* enslave the links that were attempted to be enslaved before the + * link was ready */ + r = netdev_enslave_ready(netdev, callback->link, callback->callback); + if (r < 0) + return r; + + LIST_REMOVE(callbacks, netdev->callbacks, callback); + link_unref(callback->link); + free(callback); + } + + if (NETDEV_VTABLE(netdev)->post_create) + NETDEV_VTABLE(netdev)->post_create(netdev, NULL, NULL); + + return 0; +} + +/* callback for netdev's created without a backing Link */ +static int netdev_create_handler(sd_netlink *rtnl, sd_netlink_message *m, NetDev *netdev) { + int r; + + assert(netdev); + assert(netdev->state != _NETDEV_STATE_INVALID); + + r = sd_netlink_message_get_errno(m); + if (r == -EEXIST) + log_netdev_info(netdev, "netdev exists, using existing without changing its parameters"); + else if (r < 0) { + log_netdev_warning_errno(netdev, r, "netdev could not be created: %m"); + netdev_drop(netdev); + + return 1; + } + + log_netdev_debug(netdev, "Created"); + + return 1; +} + +static int netdev_enslave(NetDev *netdev, Link *link, link_netlink_message_handler_t callback) { + int r; + + assert(netdev); + assert(netdev->manager); + assert(netdev->manager->rtnl); + assert(IN_SET(netdev->kind, NETDEV_KIND_BRIDGE, NETDEV_KIND_BOND, NETDEV_KIND_VRF)); + + if (netdev->state == NETDEV_STATE_READY) { + r = netdev_enslave_ready(netdev, link, callback); + if (r < 0) + return r; + } else if (IN_SET(netdev->state, NETDEV_STATE_LINGER, NETDEV_STATE_FAILED)) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + + r = rtnl_message_new_synthetic_error(netdev->manager->rtnl, -ENODEV, 0, &m); + if (r >= 0) + callback(netdev->manager->rtnl, m, link); + } else { + /* the netdev is not yet ready, save this request for when it is */ + netdev_join_callback *cb; + + cb = new(netdev_join_callback, 1); + if (!cb) + return log_oom(); + + *cb = (netdev_join_callback) { + .callback = callback, + .link = link_ref(link), + }; + + LIST_PREPEND(callbacks, netdev->callbacks, cb); + + log_netdev_debug(netdev, "Will enslave '%s', when ready", link->ifname); + } + + return 0; +} + +int netdev_set_ifindex(NetDev *netdev, sd_netlink_message *message) { + uint16_t type; + const char *kind; + const char *received_kind; + const char *received_name; + int r, ifindex; + + assert(netdev); + assert(message); + + r = sd_netlink_message_get_type(message, &type); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not get rtnl message type: %m"); + + if (type != RTM_NEWLINK) { + log_netdev_error(netdev, "Cannot set ifindex from unexpected rtnl message type."); + return -EINVAL; + } + + r = sd_rtnl_message_link_get_ifindex(message, &ifindex); + if (r < 0) { + log_netdev_error_errno(netdev, r, "Could not get ifindex: %m"); + netdev_enter_failed(netdev); + return r; + } else if (ifindex <= 0) { + log_netdev_error(netdev, "Got invalid ifindex: %d", ifindex); + netdev_enter_failed(netdev); + return -EINVAL; + } + + if (netdev->ifindex > 0) { + if (netdev->ifindex != ifindex) { + log_netdev_error(netdev, "Could not set ifindex to %d, already set to %d", + ifindex, netdev->ifindex); + netdev_enter_failed(netdev); + return -EEXIST; + } else + /* ifindex already set to the same for this netdev */ + return 0; + } + + r = sd_netlink_message_read_string(message, IFLA_IFNAME, &received_name); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not get IFNAME: %m"); + + if (!streq(netdev->ifname, received_name)) { + log_netdev_error(netdev, "Received newlink with wrong IFNAME %s", received_name); + netdev_enter_failed(netdev); + return r; + } + + r = sd_netlink_message_enter_container(message, IFLA_LINKINFO); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not get LINKINFO: %m"); + + r = sd_netlink_message_read_string(message, IFLA_INFO_KIND, &received_kind); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not get KIND: %m"); + + r = sd_netlink_message_exit_container(message); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not exit container: %m"); + + if (netdev->kind == NETDEV_KIND_TAP) + /* the kernel does not distinguish between tun and tap */ + kind = "tun"; + else { + kind = netdev_kind_to_string(netdev->kind); + if (!kind) { + log_netdev_error(netdev, "Could not get kind"); + netdev_enter_failed(netdev); + return -EINVAL; + } + } + + if (!streq(kind, received_kind)) { + log_netdev_error(netdev, + "Received newlink with wrong KIND %s, " + "expected %s", received_kind, kind); + netdev_enter_failed(netdev); + return r; + } + + netdev->ifindex = ifindex; + + log_netdev_debug(netdev, "netdev has index %d", netdev->ifindex); + + netdev_enter_ready(netdev); + + return 0; +} + +#define HASH_KEY SD_ID128_MAKE(52,e1,45,bd,00,6f,29,96,21,c6,30,6d,83,71,04,48) + +int netdev_get_mac(const char *ifname, struct ether_addr **ret) { + _cleanup_free_ struct ether_addr *mac = NULL; + uint64_t result; + size_t l, sz; + uint8_t *v; + int r; + + assert(ifname); + assert(ret); + + mac = new0(struct ether_addr, 1); + if (!mac) + return -ENOMEM; + + l = strlen(ifname); + sz = sizeof(sd_id128_t) + l; + v = newa(uint8_t, sz); + + /* fetch some persistent data unique to the machine */ + r = sd_id128_get_machine((sd_id128_t*) v); + if (r < 0) + return r; + + /* combine with some data unique (on this machine) to this + * netdev */ + memcpy(v + sizeof(sd_id128_t), ifname, l); + + /* Let's hash the host machine ID plus the container name. We + * use a fixed, but originally randomly created hash key here. */ + result = siphash24(v, sz, HASH_KEY.bytes); + + assert_cc(ETH_ALEN <= sizeof(result)); + memcpy(mac->ether_addr_octet, &result, ETH_ALEN); + + /* see eth_random_addr in the kernel */ + mac->ether_addr_octet[0] &= 0xfe; /* clear multicast bit */ + mac->ether_addr_octet[0] |= 0x02; /* set local assignment bit (IEEE802) */ + + *ret = TAKE_PTR(mac); + + return 0; +} + +static int netdev_create(NetDev *netdev, Link *link, link_netlink_message_handler_t callback) { + int r; + + assert(netdev); + assert(!link || callback); + + /* create netdev */ + if (NETDEV_VTABLE(netdev)->create) { + assert(!link); + + r = NETDEV_VTABLE(netdev)->create(netdev); + if (r < 0) + return r; + + log_netdev_debug(netdev, "Created"); + } else { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + + r = sd_rtnl_message_new_link(netdev->manager->rtnl, &m, RTM_NEWLINK, 0); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not allocate RTM_NEWLINK message: %m"); + + r = sd_netlink_message_append_string(m, IFLA_IFNAME, netdev->ifname); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IFNAME, attribute: %m"); + + if (netdev->mac) { + r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, netdev->mac); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_ADDRESS attribute: %m"); + } + + if (netdev->mtu) { + r = sd_netlink_message_append_u32(m, IFLA_MTU, netdev->mtu); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_MTU attribute: %m"); + } + + if (link) { + r = sd_netlink_message_append_u32(m, IFLA_LINK, link->ifindex); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_LINK attribute: %m"); + } + + r = sd_netlink_message_open_container(m, IFLA_LINKINFO); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_LINKINFO attribute: %m"); + + r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, netdev_kind_to_string(netdev->kind)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_INFO_DATA attribute: %m"); + + if (NETDEV_VTABLE(netdev)->fill_message_create) { + r = NETDEV_VTABLE(netdev)->fill_message_create(netdev, link, m); + if (r < 0) + return r; + } + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_INFO_DATA attribute: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_LINKINFO attribute: %m"); + + if (link) { + r = netlink_call_async(netdev->manager->rtnl, NULL, m, callback, + link_netlink_destroy_callback, link); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + } else { + r = netlink_call_async(netdev->manager->rtnl, NULL, m, netdev_create_handler, + netdev_destroy_callback, netdev); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not send rtnetlink message: %m"); + + netdev_ref(netdev); + } + + netdev->state = NETDEV_STATE_CREATING; + + log_netdev_debug(netdev, "Creating"); + } + + return 0; +} + +static int netdev_create_after_configured(NetDev *netdev, Link *link) { + assert(netdev); + assert(link); + assert(NETDEV_VTABLE(netdev)->create_after_configured); + + return NETDEV_VTABLE(netdev)->create_after_configured(netdev, link); +} + +/* the callback must be called, possibly after a timeout, as otherwise the Link will hang */ +int netdev_join(NetDev *netdev, Link *link, link_netlink_message_handler_t callback) { + int r; + + assert(netdev); + assert(netdev->manager); + assert(netdev->manager->rtnl); + + switch (netdev_get_create_type(netdev)) { + case NETDEV_CREATE_MASTER: + r = netdev_enslave(netdev, link, callback); + if (r < 0) + return r; + + break; + case NETDEV_CREATE_STACKED: + r = netdev_create(netdev, link, callback); + if (r < 0) + return r; + + break; + case NETDEV_CREATE_AFTER_CONFIGURED: + r = netdev_create_after_configured(netdev, link); + if (r < 0) + return r; + break; + default: + assert_not_reached("Cannot join independent netdev"); + } + + return 0; +} + +int netdev_load_one(Manager *manager, const char *filename) { + _cleanup_(netdev_unrefp) NetDev *netdev_raw = NULL, *netdev = NULL; + _cleanup_fclose_ FILE *file = NULL; + const char *dropin_dirname; + bool independent = false; + int r; + + assert(manager); + assert(filename); + + file = fopen(filename, "re"); + if (!file) { + if (errno == ENOENT) + return 0; + + return -errno; + } + + if (null_or_empty_fd(fileno(file))) { + log_debug("Skipping empty file: %s", filename); + return 0; + } + + netdev_raw = new(NetDev, 1); + if (!netdev_raw) + return log_oom(); + + *netdev_raw = (NetDev) { + .n_ref = 1, + .kind = _NETDEV_KIND_INVALID, + .state = _NETDEV_STATE_INVALID, /* an invalid state means done() of the implementation won't be called on destruction */ + }; + + dropin_dirname = strjoina(basename(filename), ".d"); + r = config_parse_many( + filename, NETWORK_DIRS, dropin_dirname, + NETDEV_COMMON_SECTIONS NETDEV_OTHER_SECTIONS, + config_item_perf_lookup, network_netdev_gperf_lookup, + CONFIG_PARSE_WARN, + netdev_raw, + NULL); + if (r < 0) + return r; + + /* skip out early if configuration does not match the environment */ + if (!condition_test_list(netdev_raw->conditions, environ, NULL, NULL, NULL)) { + log_debug("%s: Conditions in the file do not match the system environment, skipping.", filename); + return 0; + } + + if (netdev_raw->kind == _NETDEV_KIND_INVALID) { + log_warning("NetDev has no Kind= configured in %s. Ignoring", filename); + return 0; + } + + if (!netdev_raw->ifname) { + log_warning("NetDev without Name= configured in %s. Ignoring", filename); + return 0; + } + + r = fseek(file, 0, SEEK_SET); + if (r < 0) + return -errno; + + netdev = malloc0(NETDEV_VTABLE(netdev_raw)->object_size); + if (!netdev) + return log_oom(); + + netdev->n_ref = 1; + netdev->manager = manager; + netdev->kind = netdev_raw->kind; + netdev->state = NETDEV_STATE_LOADING; /* we initialize the state here for the first time, + so that done() will be called on destruction */ + + if (NETDEV_VTABLE(netdev)->init) + NETDEV_VTABLE(netdev)->init(netdev); + + r = config_parse_many( + filename, NETWORK_DIRS, dropin_dirname, + NETDEV_VTABLE(netdev)->sections, + config_item_perf_lookup, network_netdev_gperf_lookup, + CONFIG_PARSE_WARN, + netdev, NULL); + if (r < 0) + return r; + + /* verify configuration */ + if (NETDEV_VTABLE(netdev)->config_verify) { + r = NETDEV_VTABLE(netdev)->config_verify(netdev, filename); + if (r < 0) + return 0; + } + + netdev->filename = strdup(filename); + if (!netdev->filename) + return log_oom(); + + if (!netdev->mac && NETDEV_VTABLE(netdev)->generate_mac) { + r = netdev_get_mac(netdev->ifname, &netdev->mac); + if (r < 0) + return log_netdev_error_errno(netdev, r, + "Failed to generate predictable MAC address for %s: %m", + netdev->ifname); + } + + r = hashmap_ensure_allocated(&netdev->manager->netdevs, &string_hash_ops); + if (r < 0) + return r; + + r = hashmap_put(netdev->manager->netdevs, netdev->ifname, netdev); + if (r == -EEXIST) { + NetDev *n = hashmap_get(netdev->manager->netdevs, netdev->ifname); + + assert(n); + if (!streq(netdev->filename, n->filename)) + log_netdev_warning_errno(netdev, r, + "The setting Name=%s in %s conflicts with the one in %s, ignoring", + netdev->ifname, netdev->filename, n->filename); + + /* Clear ifname before netdev_free() is called. Otherwise, the NetDev object 'n' is + * removed from the hashmap 'manager->netdevs'. */ + netdev->ifname = mfree(netdev->ifname); + return 0; + } + if (r < 0) + return r; + + LIST_HEAD_INIT(netdev->callbacks); + + log_netdev_debug(netdev, "loaded %s", netdev_kind_to_string(netdev->kind)); + + if (IN_SET(netdev_get_create_type(netdev), NETDEV_CREATE_MASTER, NETDEV_CREATE_INDEPENDENT)) { + r = netdev_create(netdev, NULL, NULL); + if (r < 0) + return r; + } + + switch (netdev->kind) { + case NETDEV_KIND_IPIP: + independent = IPIP(netdev)->independent; + break; + case NETDEV_KIND_GRE: + independent = GRE(netdev)->independent; + break; + case NETDEV_KIND_GRETAP: + independent = GRETAP(netdev)->independent; + break; + case NETDEV_KIND_IP6GRE: + independent = IP6GRE(netdev)->independent; + break; + case NETDEV_KIND_IP6GRETAP: + independent = IP6GRETAP(netdev)->independent; + break; + case NETDEV_KIND_SIT: + independent = SIT(netdev)->independent; + break; + case NETDEV_KIND_VTI: + independent = VTI(netdev)->independent; + break; + case NETDEV_KIND_VTI6: + independent = VTI6(netdev)->independent; + break; + case NETDEV_KIND_IP6TNL: + independent = IP6TNL(netdev)->independent; + break; + case NETDEV_KIND_ERSPAN: + independent = ERSPAN(netdev)->independent; + break; + case NETDEV_KIND_XFRM: + independent = XFRM(netdev)->independent; + break; + case NETDEV_KIND_VXLAN: + independent = VXLAN(netdev)->independent; + break; + default: + break; + } + + if (independent) { + r = netdev_create(netdev, NULL, NULL); + if (r < 0) + return r; + } + + netdev = NULL; + + return 0; +} + +int netdev_load(Manager *manager, bool reload) { + _cleanup_strv_free_ char **files = NULL; + char **f; + int r; + + assert(manager); + + if (!reload) + hashmap_clear_with_destructor(manager->netdevs, netdev_unref); + + r = conf_files_list_strv(&files, ".netdev", NULL, 0, NETWORK_DIRS); + if (r < 0) + return log_error_errno(r, "Failed to enumerate netdev files: %m"); + + STRV_FOREACH(f, files) { + r = netdev_load_one(manager, *f); + if (r < 0) + log_error_errno(r, "Failed to load %s, ignoring: %m", *f); + } + + return 0; +} diff --git a/src/network/netdev/netdev.h b/src/network/netdev/netdev.h new file mode 100644 index 0000000..468fae5 --- /dev/null +++ b/src/network/netdev/netdev.h @@ -0,0 +1,244 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-netlink.h" + +#include "conf-parser.h" +#include "list.h" +#include "networkd-link.h" +#include "time-util.h" + +#define NETDEV_COMMON_SECTIONS "Match\0NetDev\0" +/* This is the list of known sections. We need to ignore them in the initial parsing phase. */ +#define NETDEV_OTHER_SECTIONS \ + "-BareUDP\0" \ + "-Bond\0" \ + "-Bridge\0" \ + "-FooOverUDP\0" \ + "-GENEVE\0" \ + "-IPVLAN\0" \ + "-IPVTAP\0" \ + "-L2TP\0" \ + "-L2TPSession\0" \ + "-MACsec\0" \ + "-MACsecReceiveChannel\0" \ + "-MACsecTransmitAssociation\0" \ + "-MACsecReceiveAssociation\0" \ + "-MACVTAP\0" \ + "-MACVLAN\0" \ + "-Tunnel\0" \ + "-Tun\0" \ + "-Tap\0" \ + "-Peer\0" \ + "-VLAN\0" \ + "-VRF\0" \ + "-VXCAN\0" \ + "-VXLAN\0" \ + "-WireGuard\0" \ + "-WireGuardPeer\0" \ + "-Xfrm\0" + +typedef struct netdev_join_callback netdev_join_callback; + +struct netdev_join_callback { + link_netlink_message_handler_t callback; + Link *link; + + LIST_FIELDS(netdev_join_callback, callbacks); +}; + +typedef enum NetDevKind { + NETDEV_KIND_BRIDGE, + NETDEV_KIND_BOND, + NETDEV_KIND_VLAN, + NETDEV_KIND_MACVLAN, + NETDEV_KIND_MACVTAP, + NETDEV_KIND_IPVLAN, + NETDEV_KIND_IPVTAP, + NETDEV_KIND_VXLAN, + NETDEV_KIND_IPIP, + NETDEV_KIND_GRE, + NETDEV_KIND_GRETAP, + NETDEV_KIND_IP6GRE, + NETDEV_KIND_IP6GRETAP, + NETDEV_KIND_SIT, + NETDEV_KIND_VETH, + NETDEV_KIND_VTI, + NETDEV_KIND_VTI6, + NETDEV_KIND_IP6TNL, + NETDEV_KIND_DUMMY, + NETDEV_KIND_TUN, + NETDEV_KIND_TAP, + NETDEV_KIND_VRF, + NETDEV_KIND_VCAN, + NETDEV_KIND_GENEVE, + NETDEV_KIND_VXCAN, + NETDEV_KIND_WIREGUARD, + NETDEV_KIND_NETDEVSIM, + NETDEV_KIND_FOU, + NETDEV_KIND_ERSPAN, + NETDEV_KIND_L2TP, + NETDEV_KIND_MACSEC, + NETDEV_KIND_NLMON, + NETDEV_KIND_XFRM, + NETDEV_KIND_IFB, + NETDEV_KIND_BAREUDP, + _NETDEV_KIND_MAX, + _NETDEV_KIND_TUNNEL, /* Used by config_parse_stacked_netdev() */ + _NETDEV_KIND_INVALID = -1 +} NetDevKind; + +typedef enum NetDevState { + NETDEV_STATE_LOADING, + NETDEV_STATE_FAILED, + NETDEV_STATE_CREATING, + NETDEV_STATE_READY, + NETDEV_STATE_LINGER, + _NETDEV_STATE_MAX, + _NETDEV_STATE_INVALID = -1, +} NetDevState; + +typedef enum NetDevCreateType { + NETDEV_CREATE_INDEPENDENT, + NETDEV_CREATE_MASTER, + NETDEV_CREATE_STACKED, + NETDEV_CREATE_AFTER_CONFIGURED, + _NETDEV_CREATE_MAX, + _NETDEV_CREATE_INVALID = -1, +} NetDevCreateType; + +typedef struct Manager Manager; +typedef struct Condition Condition; + +typedef struct NetDev { + Manager *manager; + + unsigned n_ref; + + char *filename; + + LIST_HEAD(Condition, conditions); + + NetDevState state; + NetDevKind kind; + char *description; + char *ifname; + struct ether_addr *mac; + uint32_t mtu; + int ifindex; + + LIST_HEAD(netdev_join_callback, callbacks); +} NetDev; + +typedef struct NetDevVTable { + /* How much memory does an object of this unit type need */ + size_t object_size; + + /* Config file sections this netdev kind understands, separated + * by NUL chars */ + const char *sections; + + /* This should reset all type-specific variables. This should + * not allocate memory, and is called with zero-initialized + * data. It should hence only initialize variables that need + * to be set != 0. */ + void (*init)(NetDev *n); + + /* This should free all kind-specific variables. It should be + * idempotent. */ + void (*done)(NetDev *n); + + /* fill in message to create netdev */ + int (*fill_message_create)(NetDev *netdev, Link *link, sd_netlink_message *message); + + /* specifies if netdev is independent, or a master device or a stacked device */ + NetDevCreateType create_type; + + /* create netdev, if not done via rtnl */ + int (*create)(NetDev *netdev); + + /* create netdev after link is fully configured */ + int (*create_after_configured)(NetDev *netdev, Link *link); + + /* perform additional configuration after netdev has been createad */ + int (*post_create)(NetDev *netdev, Link *link, sd_netlink_message *message); + + /* verify that compulsory configuration options were specified */ + int (*config_verify)(NetDev *netdev, const char *filename); + + /* Generate MAC address or not When MACAddress= is not specified. */ + bool generate_mac; +} NetDevVTable; + +extern const NetDevVTable * const netdev_vtable[_NETDEV_KIND_MAX]; + +#define NETDEV_VTABLE(n) ((n)->kind != _NETDEV_KIND_INVALID ? netdev_vtable[(n)->kind] : NULL) + +/* For casting a netdev into the various netdev kinds */ +#define DEFINE_NETDEV_CAST(UPPERCASE, MixedCase) \ + static inline MixedCase* UPPERCASE(NetDev *n) { \ + if (_unlikely_(!n || \ + n->kind != NETDEV_KIND_##UPPERCASE) || \ + n->state == _NETDEV_STATE_INVALID) \ + return NULL; \ + \ + return (MixedCase*) n; \ + } + +/* For casting the various netdev kinds into a netdev */ +#define NETDEV(n) (&(n)->meta) + +int netdev_load(Manager *manager, bool reload); +int netdev_load_one(Manager *manager, const char *filename); +void netdev_drop(NetDev *netdev); + +NetDev *netdev_unref(NetDev *netdev); +NetDev *netdev_ref(NetDev *netdev); +DEFINE_TRIVIAL_DESTRUCTOR(netdev_destroy_callback, NetDev, netdev_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(NetDev*, netdev_unref); + +bool netdev_is_managed(NetDev *netdev); +int netdev_get(Manager *manager, const char *name, NetDev **ret); +int netdev_set_ifindex(NetDev *netdev, sd_netlink_message *newlink); +int netdev_get_mac(const char *ifname, struct ether_addr **ret); +int netdev_join(NetDev *netdev, Link *link, link_netlink_message_handler_t cb); +int netdev_join_after_configured(NetDev *netdev, Link *link, link_netlink_message_handler_t callback); + +const char *netdev_kind_to_string(NetDevKind d) _const_; +NetDevKind netdev_kind_from_string(const char *d) _pure_; + +static inline NetDevCreateType netdev_get_create_type(NetDev *netdev) { + assert(netdev); + assert(NETDEV_VTABLE(netdev)); + + return NETDEV_VTABLE(netdev)->create_type; +} + +CONFIG_PARSER_PROTOTYPE(config_parse_netdev_kind); + +/* gperf */ +const struct ConfigPerfItem* network_netdev_gperf_lookup(const char *key, GPERF_LEN_TYPE length); + +/* Macros which append INTERFACE= to the message */ + +#define log_netdev_full(netdev, level, error, ...) \ + ({ \ + const NetDev *_n = (netdev); \ + _n ? log_object_internal(level, error, PROJECT_FILE, __LINE__, __func__, "INTERFACE=", _n->ifname, NULL, NULL, ##__VA_ARGS__) : \ + log_internal(level, error, PROJECT_FILE, __LINE__, __func__, ##__VA_ARGS__); \ + }) + +#define log_netdev_debug(netdev, ...) log_netdev_full(netdev, LOG_DEBUG, 0, ##__VA_ARGS__) +#define log_netdev_info(netdev, ...) log_netdev_full(netdev, LOG_INFO, 0, ##__VA_ARGS__) +#define log_netdev_notice(netdev, ...) log_netdev_full(netdev, LOG_NOTICE, 0, ##__VA_ARGS__) +#define log_netdev_warning(netdev, ...) log_netdev_full(netdev, LOG_WARNING, 0, ## __VA_ARGS__) +#define log_netdev_error(netdev, ...) log_netdev_full(netdev, LOG_ERR, 0, ##__VA_ARGS__) + +#define log_netdev_debug_errno(netdev, error, ...) log_netdev_full(netdev, LOG_DEBUG, error, ##__VA_ARGS__) +#define log_netdev_info_errno(netdev, error, ...) log_netdev_full(netdev, LOG_INFO, error, ##__VA_ARGS__) +#define log_netdev_notice_errno(netdev, error, ...) log_netdev_full(netdev, LOG_NOTICE, error, ##__VA_ARGS__) +#define log_netdev_warning_errno(netdev, error, ...) log_netdev_full(netdev, LOG_WARNING, error, ##__VA_ARGS__) +#define log_netdev_error_errno(netdev, error, ...) log_netdev_full(netdev, LOG_ERR, error, ##__VA_ARGS__) + +#define LOG_NETDEV_MESSAGE(netdev, fmt, ...) "MESSAGE=%s: " fmt, (netdev)->ifname, ##__VA_ARGS__ +#define LOG_NETDEV_INTERFACE(netdev) "INTERFACE=%s", (netdev)->ifname diff --git a/src/network/netdev/netdevsim.c b/src/network/netdev/netdevsim.c new file mode 100644 index 0000000..b281428 --- /dev/null +++ b/src/network/netdev/netdevsim.c @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "netdevsim.h" + +const NetDevVTable netdevsim_vtable = { + .object_size = sizeof(NetDevSim), + .sections = NETDEV_COMMON_SECTIONS, + .create_type = NETDEV_CREATE_INDEPENDENT, + .generate_mac = true, +}; diff --git a/src/network/netdev/netdevsim.h b/src/network/netdev/netdevsim.h new file mode 100644 index 0000000..27adc59 --- /dev/null +++ b/src/network/netdev/netdevsim.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +typedef struct NetDevSim NetDevSim; + +#include "netdev.h" + +struct NetDevSim { + NetDev meta; +}; + +DEFINE_NETDEV_CAST(NETDEVSIM, NetDevSim); +extern const NetDevVTable netdevsim_vtable; diff --git a/src/network/netdev/nlmon.c b/src/network/netdev/nlmon.c new file mode 100644 index 0000000..a8faed5 --- /dev/null +++ b/src/network/netdev/nlmon.c @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nlmon.h" + +static int netdev_nlmon_verify(NetDev *netdev, const char *filename) { + assert(netdev); + assert(filename); + + if (netdev->mac) { + log_netdev_warning(netdev, "%s: MACAddress= is not supported. Ignoring", filename); + netdev->mac = mfree(netdev->mac); + } + + return 0; +} + +const NetDevVTable nlmon_vtable = { + .object_size = sizeof(NLMon), + .sections = NETDEV_COMMON_SECTIONS, + .create_type = NETDEV_CREATE_INDEPENDENT, + .config_verify = netdev_nlmon_verify, +}; diff --git a/src/network/netdev/nlmon.h b/src/network/netdev/nlmon.h new file mode 100644 index 0000000..edfc504 --- /dev/null +++ b/src/network/netdev/nlmon.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +typedef struct NLMon NLMon; + +#include "netdev.h" + +struct NLMon { + NetDev meta; +}; + +DEFINE_NETDEV_CAST(NLMON, NLMon); + +extern const NetDevVTable nlmon_vtable; diff --git a/src/network/netdev/tunnel.c b/src/network/netdev/tunnel.c new file mode 100644 index 0000000..66e8868 --- /dev/null +++ b/src/network/netdev/tunnel.c @@ -0,0 +1,903 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <netinet/in.h> +#include <linux/fou.h> +#include <linux/ip.h> +#include <linux/if_tunnel.h> +#include <linux/ip6_tunnel.h> + +#include "conf-parser.h" +#include "missing_network.h" +#include "netlink-util.h" +#include "parse-util.h" +#include "string-table.h" +#include "string-util.h" +#include "tunnel.h" +#include "util.h" + +#define DEFAULT_TNL_HOP_LIMIT 64 +#define IP6_FLOWINFO_FLOWLABEL htobe32(0x000FFFFF) +#define IP6_TNL_F_ALLOW_LOCAL_REMOTE 0x40 + +static const char* const ip6tnl_mode_table[_NETDEV_IP6_TNL_MODE_MAX] = { + [NETDEV_IP6_TNL_MODE_IP6IP6] = "ip6ip6", + [NETDEV_IP6_TNL_MODE_IPIP6] = "ipip6", + [NETDEV_IP6_TNL_MODE_ANYIP6] = "any", +}; + +DEFINE_STRING_TABLE_LOOKUP(ip6tnl_mode, Ip6TnlMode); +DEFINE_CONFIG_PARSE_ENUM(config_parse_ip6tnl_mode, ip6tnl_mode, Ip6TnlMode, "Failed to parse ip6 tunnel Mode"); + +static int netdev_ipip_sit_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) { + Tunnel *t; + int r; + + assert(netdev); + + if (netdev->kind == NETDEV_KIND_IPIP) + t = IPIP(netdev); + else + t = SIT(netdev); + + assert(m); + assert(t); + + if (link || t->assign_to_loopback) { + r = sd_netlink_message_append_u32(m, IFLA_IPTUN_LINK, link ? link->ifindex : LOOPBACK_IFINDEX); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_LINK attribute: %m"); + } + + r = sd_netlink_message_append_in_addr(m, IFLA_IPTUN_LOCAL, &t->local.in); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_LOCAL attribute: %m"); + + r = sd_netlink_message_append_in_addr(m, IFLA_IPTUN_REMOTE, &t->remote.in); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_REMOTE attribute: %m"); + + r = sd_netlink_message_append_u8(m, IFLA_IPTUN_TTL, t->ttl); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_TTL attribute: %m"); + + r = sd_netlink_message_append_u8(m, IFLA_IPTUN_PMTUDISC, t->pmtudisc); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_PMTUDISC attribute: %m"); + + if (t->fou_tunnel) { + r = sd_netlink_message_append_u16(m, IFLA_IPTUN_ENCAP_TYPE, t->fou_encap_type); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_ENCAP_TYPE attribute: %m"); + + r = sd_netlink_message_append_u16(m, IFLA_IPTUN_ENCAP_SPORT, htobe16(t->encap_src_port)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_ENCAP_SPORT attribute: %m"); + + r = sd_netlink_message_append_u16(m, IFLA_IPTUN_ENCAP_DPORT, htobe16(t->fou_destination_port)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_ENCAP_DPORT attribute: %m"); + } + + if (netdev->kind == NETDEV_KIND_SIT) { + if (t->sixrd_prefixlen > 0) { + r = sd_netlink_message_append_in6_addr(m, IFLA_IPTUN_6RD_PREFIX, &t->sixrd_prefix); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_6RD_PREFIX attribute: %m"); + + /* u16 is deliberate here, even though we're passing a netmask that can never be >128. The kernel is + * expecting to receive the prefixlen as a u16. + */ + r = sd_netlink_message_append_u16(m, IFLA_IPTUN_6RD_PREFIXLEN, t->sixrd_prefixlen); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_6RD_PREFIXLEN attribute: %m"); + } + + if (t->isatap >= 0) { + uint16_t flags = 0; + + SET_FLAG(flags, SIT_ISATAP, t->isatap); + + r = sd_netlink_message_append_u16(m, IFLA_IPTUN_FLAGS, flags); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_FLAGS attribute: %m"); + } + } + + return r; +} + +static int netdev_gre_erspan_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) { + uint32_t ikey = 0; + uint32_t okey = 0; + uint16_t iflags = 0; + uint16_t oflags = 0; + Tunnel *t; + int r; + + assert(netdev); + assert(m); + + switch (netdev->kind) { + case NETDEV_KIND_GRE: + t = GRE(netdev); + break; + case NETDEV_KIND_ERSPAN: + t = ERSPAN(netdev); + break; + case NETDEV_KIND_GRETAP: + t = GRETAP(netdev); + break; + default: + assert_not_reached("invalid netdev kind"); + } + + assert(t); + + if (link || t->assign_to_loopback) { + r = sd_netlink_message_append_u32(m, IFLA_GRE_LINK, link ? link->ifindex : LOOPBACK_IFINDEX); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_LINK attribute: %m"); + } + + if (netdev->kind == NETDEV_KIND_ERSPAN) { + r = sd_netlink_message_append_u32(m, IFLA_GRE_ERSPAN_INDEX, t->erspan_index); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_ERSPAN_INDEX attribute: %m"); + } + + r = sd_netlink_message_append_in_addr(m, IFLA_GRE_LOCAL, &t->local.in); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_LOCAL attribute: %m"); + + r = sd_netlink_message_append_in_addr(m, IFLA_GRE_REMOTE, &t->remote.in); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_REMOTE attribute: %m"); + + r = sd_netlink_message_append_u8(m, IFLA_GRE_TTL, t->ttl); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_TTL attribute: %m"); + + r = sd_netlink_message_append_u8(m, IFLA_GRE_TOS, t->tos); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_TOS attribute: %m"); + + r = sd_netlink_message_append_u8(m, IFLA_GRE_PMTUDISC, t->pmtudisc); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_PMTUDISC attribute: %m"); + + if (t->key != 0) { + ikey = okey = htobe32(t->key); + iflags |= GRE_KEY; + oflags |= GRE_KEY; + } + + if (t->ikey != 0) { + ikey = htobe32(t->ikey); + iflags |= GRE_KEY; + } + + if (t->okey != 0) { + okey = htobe32(t->okey); + oflags |= GRE_KEY; + } + + if (t->gre_erspan_sequence > 0) { + iflags |= GRE_SEQ; + oflags |= GRE_SEQ; + } else if (t->gre_erspan_sequence == 0) { + iflags &= ~GRE_SEQ; + oflags &= ~GRE_SEQ; + } + + r = sd_netlink_message_append_u32(m, IFLA_GRE_IKEY, ikey); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_IKEY attribute: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_GRE_OKEY, okey); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_OKEY attribute: %m"); + + r = sd_netlink_message_append_u16(m, IFLA_GRE_IFLAGS, iflags); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_IFLAGS attribute: %m"); + + r = sd_netlink_message_append_u16(m, IFLA_GRE_OFLAGS, oflags); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_OFLAGS, attribute: %m"); + + if (t->fou_tunnel) { + r = sd_netlink_message_append_u16(m, IFLA_GRE_ENCAP_TYPE, t->fou_encap_type); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_ENCAP_TYPE attribute: %m"); + + r = sd_netlink_message_append_u16(m, IFLA_GRE_ENCAP_SPORT, htobe16(t->encap_src_port)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_ENCAP_SPORT attribute: %m"); + + r = sd_netlink_message_append_u16(m, IFLA_GRE_ENCAP_DPORT, htobe16(t->fou_destination_port)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_ENCAP_DPORT attribute: %m"); + } + + return r; +} + +static int netdev_ip6gre_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) { + uint32_t ikey = 0; + uint32_t okey = 0; + uint16_t iflags = 0; + uint16_t oflags = 0; + Tunnel *t; + int r; + + assert(netdev); + + if (netdev->kind == NETDEV_KIND_IP6GRE) + t = IP6GRE(netdev); + else + t = IP6GRETAP(netdev); + + assert(t); + assert(m); + + if (link || t->assign_to_loopback) { + r = sd_netlink_message_append_u32(m, IFLA_GRE_LINK, link ? link->ifindex : LOOPBACK_IFINDEX); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_LINK attribute: %m"); + } + + r = sd_netlink_message_append_in6_addr(m, IFLA_GRE_LOCAL, &t->local.in6); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_LOCAL attribute: %m"); + + r = sd_netlink_message_append_in6_addr(m, IFLA_GRE_REMOTE, &t->remote.in6); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_REMOTE attribute: %m"); + + r = sd_netlink_message_append_u8(m, IFLA_GRE_TTL, t->ttl); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_TTL attribute: %m"); + + if (t->ipv6_flowlabel != _NETDEV_IPV6_FLOWLABEL_INVALID) { + r = sd_netlink_message_append_u32(m, IFLA_GRE_FLOWINFO, t->ipv6_flowlabel); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_FLOWINFO attribute: %m"); + } + + r = sd_netlink_message_append_u32(m, IFLA_GRE_FLAGS, t->flags); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_FLAGS attribute: %m"); + + if (t->key != 0) { + ikey = okey = htobe32(t->key); + iflags |= GRE_KEY; + oflags |= GRE_KEY; + } + + if (t->ikey != 0) { + ikey = htobe32(t->ikey); + iflags |= GRE_KEY; + } + + if (t->okey != 0) { + okey = htobe32(t->okey); + oflags |= GRE_KEY; + } + + r = sd_netlink_message_append_u32(m, IFLA_GRE_IKEY, ikey); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_IKEY attribute: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_GRE_OKEY, okey); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_OKEY attribute: %m"); + + r = sd_netlink_message_append_u16(m, IFLA_GRE_IFLAGS, iflags); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_IFLAGS attribute: %m"); + + r = sd_netlink_message_append_u16(m, IFLA_GRE_OFLAGS, oflags); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_OFLAGS, attribute: %m"); + + return r; +} + +static int netdev_vti_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) { + uint32_t ikey, okey; + Tunnel *t; + int r; + + assert(netdev); + assert(m); + + if (netdev->kind == NETDEV_KIND_VTI) + t = VTI(netdev); + else + t = VTI6(netdev); + + assert(t); + + if (link || t->assign_to_loopback) { + r = sd_netlink_message_append_u32(m, IFLA_VTI_LINK, link ? link->ifindex : LOOPBACK_IFINDEX); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VTI_LINK attribute: %m"); + } + + if (t->key != 0) + ikey = okey = htobe32(t->key); + else { + ikey = htobe32(t->ikey); + okey = htobe32(t->okey); + } + + r = sd_netlink_message_append_u32(m, IFLA_VTI_IKEY, ikey); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VTI_IKEY attribute: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_VTI_OKEY, okey); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VTI_OKEY attribute: %m"); + + r = netlink_message_append_in_addr_union(m, IFLA_VTI_LOCAL, t->family, &t->local); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VTI_LOCAL attribute: %m"); + + r = netlink_message_append_in_addr_union(m, IFLA_VTI_REMOTE, t->family, &t->remote); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VTI_REMOTE attribute: %m"); + + return r; +} + +static int netdev_ip6tnl_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) { + Tunnel *t = IP6TNL(netdev); + uint8_t proto; + int r; + + assert(netdev); + assert(m); + assert(t); + + if (link || t->assign_to_loopback) { + r = sd_netlink_message_append_u32(m, IFLA_IPTUN_LINK, link ? link->ifindex : LOOPBACK_IFINDEX); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_LINK attribute: %m"); + } + + r = sd_netlink_message_append_in6_addr(m, IFLA_IPTUN_LOCAL, &t->local.in6); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_LOCAL attribute: %m"); + + r = sd_netlink_message_append_in6_addr(m, IFLA_IPTUN_REMOTE, &t->remote.in6); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_REMOTE attribute: %m"); + + r = sd_netlink_message_append_u8(m, IFLA_IPTUN_TTL, t->ttl); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_TTL attribute: %m"); + + if (t->ipv6_flowlabel != _NETDEV_IPV6_FLOWLABEL_INVALID) { + r = sd_netlink_message_append_u32(m, IFLA_IPTUN_FLOWINFO, t->ipv6_flowlabel); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_FLOWINFO attribute: %m"); + } + + if (t->copy_dscp) + t->flags |= IP6_TNL_F_RCV_DSCP_COPY; + + if (t->allow_localremote >= 0) + SET_FLAG(t->flags, IP6_TNL_F_ALLOW_LOCAL_REMOTE, t->allow_localremote); + + if (t->encap_limit != IPV6_DEFAULT_TNL_ENCAP_LIMIT) { + r = sd_netlink_message_append_u8(m, IFLA_IPTUN_ENCAP_LIMIT, t->encap_limit); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_ENCAP_LIMIT attribute: %m"); + } + + r = sd_netlink_message_append_u32(m, IFLA_IPTUN_FLAGS, t->flags); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_FLAGS attribute: %m"); + + switch (t->ip6tnl_mode) { + case NETDEV_IP6_TNL_MODE_IP6IP6: + proto = IPPROTO_IPV6; + break; + case NETDEV_IP6_TNL_MODE_IPIP6: + proto = IPPROTO_IPIP; + break; + case NETDEV_IP6_TNL_MODE_ANYIP6: + default: + proto = 0; + break; + } + + r = sd_netlink_message_append_u8(m, IFLA_IPTUN_PROTO, proto); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_PROTO attribute: %m"); + + return r; +} + +static int netdev_tunnel_verify(NetDev *netdev, const char *filename) { + Tunnel *t = NULL; + + assert(netdev); + assert(filename); + + switch (netdev->kind) { + case NETDEV_KIND_IPIP: + t = IPIP(netdev); + break; + case NETDEV_KIND_SIT: + t = SIT(netdev); + break; + case NETDEV_KIND_GRE: + t = GRE(netdev); + break; + case NETDEV_KIND_GRETAP: + t = GRETAP(netdev); + break; + case NETDEV_KIND_IP6GRE: + t = IP6GRE(netdev); + break; + case NETDEV_KIND_IP6GRETAP: + t = IP6GRETAP(netdev); + break; + case NETDEV_KIND_VTI: + t = VTI(netdev); + break; + case NETDEV_KIND_VTI6: + t = VTI6(netdev); + break; + case NETDEV_KIND_IP6TNL: + t = IP6TNL(netdev); + break; + case NETDEV_KIND_ERSPAN: + t = ERSPAN(netdev); + break; + default: + assert_not_reached("Invalid tunnel kind"); + } + + assert(t); + + if (IN_SET(netdev->kind, NETDEV_KIND_VTI, NETDEV_KIND_IPIP, NETDEV_KIND_SIT, NETDEV_KIND_GRE) && + !IN_SET(t->family, AF_UNSPEC, AF_INET)) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "vti/ipip/sit/gre tunnel without a local/remote IPv4 address configured in %s. Ignoring", filename); + + if (IN_SET(netdev->kind, NETDEV_KIND_GRETAP, NETDEV_KIND_ERSPAN) && + (t->family != AF_INET || in_addr_is_null(t->family, &t->remote))) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "gretap/erspan tunnel without a remote IPv4 address configured in %s. Ignoring", filename); + + if ((IN_SET(netdev->kind, NETDEV_KIND_VTI6, NETDEV_KIND_IP6TNL) && t->family != AF_INET6) || + (netdev->kind == NETDEV_KIND_IP6GRE && !IN_SET(t->family, AF_UNSPEC, AF_INET6))) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "vti6/ip6tnl/ip6gre tunnel without a local/remote IPv6 address configured in %s. Ignoring", filename); + + if (netdev->kind == NETDEV_KIND_IP6GRETAP && + (t->family != AF_INET6 || in_addr_is_null(t->family, &t->remote))) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "ip6gretap tunnel without a remote IPv6 address configured in %s. Ignoring", filename); + + if (netdev->kind == NETDEV_KIND_IP6TNL && + t->ip6tnl_mode == _NETDEV_IP6_TNL_MODE_INVALID) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "ip6tnl without mode configured in %s. Ignoring", filename); + + if (t->fou_tunnel && t->fou_destination_port <= 0) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "FooOverUDP missing port configured in %s. Ignoring", filename); + + if (netdev->kind == NETDEV_KIND_ERSPAN && (t->erspan_index >= (1 << 20) || t->erspan_index == 0)) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), "Invalid erspan index %d. Ignoring", t->erspan_index); + + /* netlink_message_append_in_addr_union() is used for vti/vti6. So, t->family cannot be AF_UNSPEC. */ + if (netdev->kind == NETDEV_KIND_VTI) + t->family = AF_INET; + + return 0; +} + +int config_parse_tunnel_address(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + Tunnel *t = userdata; + union in_addr_union *addr = data, buffer; + int r, f; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + /* This is used to parse addresses on both local and remote ends of the tunnel. + * Address families must match. + * + * "any" is a special value which means that the address is unspecified. + */ + + if (streq(rvalue, "any")) { + *addr = IN_ADDR_NULL; + + /* As a special case, if both the local and remote addresses are + * unspecified, also clear the address family. + */ + if (t->family != AF_UNSPEC && + in_addr_is_null(t->family, &t->local) != 0 && + in_addr_is_null(t->family, &t->remote) != 0) + t->family = AF_UNSPEC; + return 0; + } + + r = in_addr_from_string_auto(rvalue, &f, &buffer); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Tunnel address \"%s\" invalid, ignoring assignment: %m", rvalue); + return 0; + } + + if (t->family != AF_UNSPEC && t->family != f) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Tunnel addresses incompatible, ignoring assignment: %s", rvalue); + return 0; + } + + t->family = f; + *addr = buffer; + return 0; +} + +int config_parse_tunnel_key(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + union in_addr_union buffer; + Tunnel *t = userdata; + uint32_t k; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = in_addr_from_string(AF_INET, rvalue, &buffer); + if (r < 0) { + r = safe_atou32(rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Failed to parse tunnel key ignoring assignment: %s", rvalue); + return 0; + } + } else + k = be32toh(buffer.in.s_addr); + + if (streq(lvalue, "Key")) + t->key = k; + else if (streq(lvalue, "InputKey")) + t->ikey = k; + else + t->okey = k; + + return 0; +} + +int config_parse_ipv6_flowlabel(const char* unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + IPv6FlowLabel *ipv6_flowlabel = data; + Tunnel *t = userdata; + int k = 0; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(ipv6_flowlabel); + + if (streq(rvalue, "inherit")) { + *ipv6_flowlabel = IP6_FLOWINFO_FLOWLABEL; + t->flags |= IP6_TNL_F_USE_ORIG_FLOWLABEL; + } else { + r = config_parse_int(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &k, userdata); + if (r < 0) + return r; + + if (k > 0xFFFFF) + log_syntax(unit, LOG_WARNING, filename, line, 0, "Failed to parse IPv6 flowlabel option, ignoring: %s", rvalue); + else { + *ipv6_flowlabel = htobe32(k) & IP6_FLOWINFO_FLOWLABEL; + t->flags &= ~IP6_TNL_F_USE_ORIG_FLOWLABEL; + } + } + + return 0; +} + +int config_parse_encap_limit(const char* unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + Tunnel *t = userdata; + int k = 0; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (streq(rvalue, "none")) + t->flags |= IP6_TNL_F_IGN_ENCAP_LIMIT; + else { + r = safe_atoi(rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse Tunnel Encapsulation Limit option, ignoring: %s", rvalue); + return 0; + } + + if (k > 255 || k < 0) + log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid Tunnel Encapsulation value, ignoring: %d", k); + else { + t->encap_limit = k; + t->flags &= ~IP6_TNL_F_IGN_ENCAP_LIMIT; + } + } + + return 0; +} + +int config_parse_6rd_prefix(const char* unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + Tunnel *t = userdata; + + assert(filename); + assert(lvalue); + assert(rvalue); + + union in_addr_union p; + uint8_t l; + int r; + + r = in_addr_prefix_from_string(rvalue, AF_INET6, &p, &l); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse 6rd prefix \"%s\", ignoring: %m", rvalue); + return 0; + } + if (l == 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "6rd prefix length of \"%s\" must be greater than zero, ignoring", rvalue); + return 0; + } + + t->sixrd_prefix = p.in6; + t->sixrd_prefixlen = l; + + return 0; +} + +static void ipip_sit_init(NetDev *n) { + Tunnel *t; + + assert(n); + + switch (n->kind) { + case NETDEV_KIND_IPIP: + t = IPIP(n); + break; + case NETDEV_KIND_SIT: + t = SIT(n); + break; + default: + assert_not_reached("invalid netdev kind"); + } + + assert(t); + + t->pmtudisc = true; + t->fou_encap_type = NETDEV_FOO_OVER_UDP_ENCAP_DIRECT; + t->isatap = -1; +} + +static void vti_init(NetDev *n) { + Tunnel *t; + + assert(n); + + if (n->kind == NETDEV_KIND_VTI) + t = VTI(n); + else + t = VTI6(n); + + assert(t); + + t->pmtudisc = true; +} + +static void gre_erspan_init(NetDev *n) { + Tunnel *t; + + assert(n); + + switch (n->kind) { + case NETDEV_KIND_GRE: + t = GRE(n); + break; + case NETDEV_KIND_ERSPAN: + t = ERSPAN(n); + break; + case NETDEV_KIND_GRETAP: + t = GRETAP(n); + break; + default: + assert_not_reached("invalid netdev kind"); + } + + assert(t); + + t->pmtudisc = true; + t->gre_erspan_sequence = -1; + t->fou_encap_type = NETDEV_FOO_OVER_UDP_ENCAP_DIRECT; +} + +static void ip6gre_init(NetDev *n) { + Tunnel *t; + + assert(n); + + if (n->kind == NETDEV_KIND_IP6GRE) + t = IP6GRE(n); + else + t = IP6GRETAP(n); + + assert(t); + + t->ttl = DEFAULT_TNL_HOP_LIMIT; +} + +static void ip6tnl_init(NetDev *n) { + Tunnel *t = IP6TNL(n); + + assert(n); + assert(t); + + t->ttl = DEFAULT_TNL_HOP_LIMIT; + t->encap_limit = IPV6_DEFAULT_TNL_ENCAP_LIMIT; + t->ip6tnl_mode = _NETDEV_IP6_TNL_MODE_INVALID; + t->ipv6_flowlabel = _NETDEV_IPV6_FLOWLABEL_INVALID; + t->allow_localremote = -1; +} + +const NetDevVTable ipip_vtable = { + .object_size = sizeof(Tunnel), + .init = ipip_sit_init, + .sections = NETDEV_COMMON_SECTIONS "Tunnel\0", + .fill_message_create = netdev_ipip_sit_fill_message_create, + .create_type = NETDEV_CREATE_STACKED, + .config_verify = netdev_tunnel_verify, + .generate_mac = true, +}; + +const NetDevVTable sit_vtable = { + .object_size = sizeof(Tunnel), + .init = ipip_sit_init, + .sections = NETDEV_COMMON_SECTIONS "Tunnel\0", + .fill_message_create = netdev_ipip_sit_fill_message_create, + .create_type = NETDEV_CREATE_STACKED, + .config_verify = netdev_tunnel_verify, + .generate_mac = true, +}; + +const NetDevVTable vti_vtable = { + .object_size = sizeof(Tunnel), + .init = vti_init, + .sections = NETDEV_COMMON_SECTIONS "Tunnel\0", + .fill_message_create = netdev_vti_fill_message_create, + .create_type = NETDEV_CREATE_STACKED, + .config_verify = netdev_tunnel_verify, + .generate_mac = true, +}; + +const NetDevVTable vti6_vtable = { + .object_size = sizeof(Tunnel), + .init = vti_init, + .sections = NETDEV_COMMON_SECTIONS "Tunnel\0", + .fill_message_create = netdev_vti_fill_message_create, + .create_type = NETDEV_CREATE_STACKED, + .config_verify = netdev_tunnel_verify, + .generate_mac = true, +}; + +const NetDevVTable gre_vtable = { + .object_size = sizeof(Tunnel), + .init = gre_erspan_init, + .sections = NETDEV_COMMON_SECTIONS "Tunnel\0", + .fill_message_create = netdev_gre_erspan_fill_message_create, + .create_type = NETDEV_CREATE_STACKED, + .config_verify = netdev_tunnel_verify, + .generate_mac = true, +}; + +const NetDevVTable gretap_vtable = { + .object_size = sizeof(Tunnel), + .init = gre_erspan_init, + .sections = NETDEV_COMMON_SECTIONS "Tunnel\0", + .fill_message_create = netdev_gre_erspan_fill_message_create, + .create_type = NETDEV_CREATE_STACKED, + .config_verify = netdev_tunnel_verify, + .generate_mac = true, +}; + +const NetDevVTable ip6gre_vtable = { + .object_size = sizeof(Tunnel), + .init = ip6gre_init, + .sections = NETDEV_COMMON_SECTIONS "Tunnel\0", + .fill_message_create = netdev_ip6gre_fill_message_create, + .create_type = NETDEV_CREATE_STACKED, + .config_verify = netdev_tunnel_verify, + .generate_mac = true, +}; + +const NetDevVTable ip6gretap_vtable = { + .object_size = sizeof(Tunnel), + .init = ip6gre_init, + .sections = NETDEV_COMMON_SECTIONS "Tunnel\0", + .fill_message_create = netdev_ip6gre_fill_message_create, + .create_type = NETDEV_CREATE_STACKED, + .config_verify = netdev_tunnel_verify, + .generate_mac = true, +}; + +const NetDevVTable ip6tnl_vtable = { + .object_size = sizeof(Tunnel), + .init = ip6tnl_init, + .sections = NETDEV_COMMON_SECTIONS "Tunnel\0", + .fill_message_create = netdev_ip6tnl_fill_message_create, + .create_type = NETDEV_CREATE_STACKED, + .config_verify = netdev_tunnel_verify, + .generate_mac = true, +}; + +const NetDevVTable erspan_vtable = { + .object_size = sizeof(Tunnel), + .init = gre_erspan_init, + .sections = NETDEV_COMMON_SECTIONS "Tunnel\0", + .fill_message_create = netdev_gre_erspan_fill_message_create, + .create_type = NETDEV_CREATE_STACKED, + .config_verify = netdev_tunnel_verify, + .generate_mac = true, +}; diff --git a/src/network/netdev/tunnel.h b/src/network/netdev/tunnel.h new file mode 100644 index 0000000..d58ded7 --- /dev/null +++ b/src/network/netdev/tunnel.h @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "in-addr-util.h" + +#include "conf-parser.h" +#include "fou-tunnel.h" +#include "netdev.h" + +typedef enum Ip6TnlMode { + NETDEV_IP6_TNL_MODE_IP6IP6, + NETDEV_IP6_TNL_MODE_IPIP6, + NETDEV_IP6_TNL_MODE_ANYIP6, + _NETDEV_IP6_TNL_MODE_MAX, + _NETDEV_IP6_TNL_MODE_INVALID = -1, +} Ip6TnlMode; + +typedef enum IPv6FlowLabel { + NETDEV_IPV6_FLOWLABEL_INHERIT = 0xFFFFF + 1, + _NETDEV_IPV6_FLOWLABEL_MAX, + _NETDEV_IPV6_FLOWLABEL_INVALID = -1, +} IPv6FlowLabel; + +typedef struct Tunnel { + NetDev meta; + + uint8_t encap_limit; + + int family; + int ipv6_flowlabel; + int allow_localremote; + int gre_erspan_sequence; + int isatap; + + unsigned ttl; + unsigned tos; + unsigned flags; + + uint32_t key; + uint32_t ikey; + uint32_t okey; + uint32_t erspan_index; + + union in_addr_union local; + union in_addr_union remote; + + Ip6TnlMode ip6tnl_mode; + FooOverUDPEncapType fou_encap_type; + + bool pmtudisc; + bool copy_dscp; + bool independent; + bool fou_tunnel; + bool assign_to_loopback; + + uint16_t encap_src_port; + uint16_t fou_destination_port; + + struct in6_addr sixrd_prefix; + uint8_t sixrd_prefixlen; +} Tunnel; + +DEFINE_NETDEV_CAST(IPIP, Tunnel); +DEFINE_NETDEV_CAST(GRE, Tunnel); +DEFINE_NETDEV_CAST(GRETAP, Tunnel); +DEFINE_NETDEV_CAST(IP6GRE, Tunnel); +DEFINE_NETDEV_CAST(IP6GRETAP, Tunnel); +DEFINE_NETDEV_CAST(SIT, Tunnel); +DEFINE_NETDEV_CAST(VTI, Tunnel); +DEFINE_NETDEV_CAST(VTI6, Tunnel); +DEFINE_NETDEV_CAST(IP6TNL, Tunnel); +DEFINE_NETDEV_CAST(ERSPAN, Tunnel); +extern const NetDevVTable ipip_vtable; +extern const NetDevVTable sit_vtable; +extern const NetDevVTable vti_vtable; +extern const NetDevVTable vti6_vtable; +extern const NetDevVTable gre_vtable; +extern const NetDevVTable gretap_vtable; +extern const NetDevVTable ip6gre_vtable; +extern const NetDevVTable ip6gretap_vtable; +extern const NetDevVTable ip6tnl_vtable; +extern const NetDevVTable erspan_vtable; + +const char *ip6tnl_mode_to_string(Ip6TnlMode d) _const_; +Ip6TnlMode ip6tnl_mode_from_string(const char *d) _pure_; + +CONFIG_PARSER_PROTOTYPE(config_parse_ip6tnl_mode); +CONFIG_PARSER_PROTOTYPE(config_parse_tunnel_address); +CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_flowlabel); +CONFIG_PARSER_PROTOTYPE(config_parse_encap_limit); +CONFIG_PARSER_PROTOTYPE(config_parse_tunnel_key); +CONFIG_PARSER_PROTOTYPE(config_parse_6rd_prefix); diff --git a/src/network/netdev/tuntap.c b/src/network/netdev/tuntap.c new file mode 100644 index 0000000..d9d6544 --- /dev/null +++ b/src/network/netdev/tuntap.c @@ -0,0 +1,164 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> +#include <fcntl.h> +#include <net/if.h> +#include <netinet/if_ether.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <linux/if_tun.h> + +#include "alloc-util.h" +#include "fd-util.h" +#include "tuntap.h" +#include "user-util.h" + +#define TUN_DEV "/dev/net/tun" + +static int netdev_fill_tuntap_message(NetDev *netdev, struct ifreq *ifr) { + TunTap *t; + + assert(netdev); + assert(netdev->ifname); + assert(ifr); + + if (netdev->kind == NETDEV_KIND_TAP) { + t = TAP(netdev); + ifr->ifr_flags |= IFF_TAP; + } else { + t = TUN(netdev); + ifr->ifr_flags |= IFF_TUN; + } + + if (!t->packet_info) + ifr->ifr_flags |= IFF_NO_PI; + + if (t->multi_queue) + ifr->ifr_flags |= IFF_MULTI_QUEUE; + + if (t->vnet_hdr) + ifr->ifr_flags |= IFF_VNET_HDR; + + strncpy(ifr->ifr_name, netdev->ifname, IFNAMSIZ-1); + + return 0; +} + +static int netdev_tuntap_add(NetDev *netdev, struct ifreq *ifr) { + _cleanup_close_ int fd; + TunTap *t = NULL; + const char *user; + const char *group; + uid_t uid; + gid_t gid; + int r; + + assert(netdev); + assert(ifr); + + fd = open(TUN_DEV, O_RDWR|O_CLOEXEC); + if (fd < 0) + return log_netdev_error_errno(netdev, -errno, "Failed to open tun dev: %m"); + + if (ioctl(fd, TUNSETIFF, ifr) < 0) + return log_netdev_error_errno(netdev, -errno, "TUNSETIFF failed on tun dev: %m"); + + if (netdev->kind == NETDEV_KIND_TAP) + t = TAP(netdev); + else + t = TUN(netdev); + + assert(t); + + if (t->user_name) { + user = t->user_name; + + r = get_user_creds(&user, &uid, NULL, NULL, NULL, USER_CREDS_ALLOW_MISSING); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Cannot resolve user name %s: %m", t->user_name); + + if (ioctl(fd, TUNSETOWNER, uid) < 0) + return log_netdev_error_errno(netdev, -errno, "TUNSETOWNER failed on tun dev: %m"); + } + + if (t->group_name) { + group = t->group_name; + + r = get_group_creds(&group, &gid, USER_CREDS_ALLOW_MISSING); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Cannot resolve group name %s: %m", t->group_name); + + if (ioctl(fd, TUNSETGROUP, gid) < 0) + return log_netdev_error_errno(netdev, -errno, "TUNSETGROUP failed on tun dev: %m"); + + } + + if (ioctl(fd, TUNSETPERSIST, 1) < 0) + return log_netdev_error_errno(netdev, -errno, "TUNSETPERSIST failed on tun dev: %m"); + + return 0; +} + +static int netdev_create_tuntap(NetDev *netdev) { + struct ifreq ifr = {}; + int r; + + r = netdev_fill_tuntap_message(netdev, &ifr); + if (r < 0) + return r; + + return netdev_tuntap_add(netdev, &ifr); +} + +static void tuntap_done(NetDev *netdev) { + TunTap *t = NULL; + + assert(netdev); + + if (netdev->kind == NETDEV_KIND_TUN) + t = TUN(netdev); + else + t = TAP(netdev); + + assert(t); + + t->user_name = mfree(t->user_name); + t->group_name = mfree(t->group_name); +} + +static int tuntap_verify(NetDev *netdev, const char *filename) { + assert(netdev); + + if (netdev->mtu != 0) + log_netdev_warning(netdev, + "MTUBytes= configured for %s device in %s will be ignored.\n" + "Please set it in the corresponding .network file.", + netdev_kind_to_string(netdev->kind), filename); + + if (netdev->mac) + log_netdev_warning(netdev, + "MACAddress= configured for %s device in %s will be ignored.\n" + "Please set it in the corresponding .network file.", + netdev_kind_to_string(netdev->kind), filename); + + return 0; +} + +const NetDevVTable tun_vtable = { + .object_size = sizeof(TunTap), + .sections = NETDEV_COMMON_SECTIONS "Tun\0", + .config_verify = tuntap_verify, + .done = tuntap_done, + .create = netdev_create_tuntap, + .create_type = NETDEV_CREATE_INDEPENDENT, +}; + +const NetDevVTable tap_vtable = { + .object_size = sizeof(TunTap), + .sections = NETDEV_COMMON_SECTIONS "Tap\0", + .config_verify = tuntap_verify, + .done = tuntap_done, + .create = netdev_create_tuntap, + .create_type = NETDEV_CREATE_INDEPENDENT, +}; diff --git a/src/network/netdev/tuntap.h b/src/network/netdev/tuntap.h new file mode 100644 index 0000000..4d1e643 --- /dev/null +++ b/src/network/netdev/tuntap.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +typedef struct TunTap TunTap; + +#include "netdev.h" + +struct TunTap { + NetDev meta; + + char *user_name; + char *group_name; + bool multi_queue; + bool packet_info; + bool vnet_hdr; +}; + +DEFINE_NETDEV_CAST(TUN, TunTap); +DEFINE_NETDEV_CAST(TAP, TunTap); +extern const NetDevVTable tun_vtable; +extern const NetDevVTable tap_vtable; diff --git a/src/network/netdev/vcan.c b/src/network/netdev/vcan.c new file mode 100644 index 0000000..3621d4c --- /dev/null +++ b/src/network/netdev/vcan.c @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "vcan.h" + +const NetDevVTable vcan_vtable = { + .object_size = sizeof(VCan), + .sections = NETDEV_COMMON_SECTIONS, + .create_type = NETDEV_CREATE_INDEPENDENT, + .generate_mac = true, +}; diff --git a/src/network/netdev/vcan.h b/src/network/netdev/vcan.h new file mode 100644 index 0000000..843984f --- /dev/null +++ b/src/network/netdev/vcan.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +typedef struct VCan VCan; + +#include <netinet/in.h> +#include <linux/can/netlink.h> + +#include "netdev.h" + +struct VCan { + NetDev meta; +}; + +DEFINE_NETDEV_CAST(VCAN, VCan); + +extern const NetDevVTable vcan_vtable; diff --git a/src/network/netdev/veth.c b/src/network/netdev/veth.c new file mode 100644 index 0000000..840a327 --- /dev/null +++ b/src/network/netdev/veth.c @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> +#include <net/if.h> +#include <linux/veth.h> + +#include "veth.h" + +static int netdev_veth_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) { + Veth *v; + int r; + + assert(netdev); + assert(!link); + assert(m); + + v = VETH(netdev); + + assert(v); + + r = sd_netlink_message_open_container(m, VETH_INFO_PEER); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append VETH_INFO_PEER attribute: %m"); + + if (v->ifname_peer) { + r = sd_netlink_message_append_string(m, IFLA_IFNAME, v->ifname_peer); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Failed to add netlink interface name: %m"); + } + + if (v->mac_peer) { + r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, v->mac_peer); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_ADDRESS attribute: %m"); + } + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_INFO_DATA attribute: %m"); + + return r; +} + +static int netdev_veth_verify(NetDev *netdev, const char *filename) { + Veth *v; + int r; + + assert(netdev); + assert(filename); + + v = VETH(netdev); + + assert(v); + + if (!v->ifname_peer) { + log_netdev_warning(netdev, "Veth NetDev without peer name configured in %s. Ignoring", + filename); + return -EINVAL; + } + + if (!v->mac_peer) { + r = netdev_get_mac(v->ifname_peer, &v->mac_peer); + if (r < 0) { + log_netdev_warning(netdev, + "Failed to generate predictable MAC address for %s. Ignoring", + v->ifname_peer); + return -EINVAL; + } + } + + return 0; +} + +static void veth_done(NetDev *n) { + Veth *v; + + assert(n); + + v = VETH(n); + + assert(v); + + free(v->ifname_peer); + free(v->mac_peer); +} + +const NetDevVTable veth_vtable = { + .object_size = sizeof(Veth), + .sections = NETDEV_COMMON_SECTIONS "Peer\0", + .done = veth_done, + .fill_message_create = netdev_veth_fill_message_create, + .create_type = NETDEV_CREATE_INDEPENDENT, + .config_verify = netdev_veth_verify, + .generate_mac = true, +}; diff --git a/src/network/netdev/veth.h b/src/network/netdev/veth.h new file mode 100644 index 0000000..643f737 --- /dev/null +++ b/src/network/netdev/veth.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +typedef struct Veth Veth; + +#include "netdev.h" + +struct Veth { + NetDev meta; + + char *ifname_peer; + struct ether_addr *mac_peer; +}; + +DEFINE_NETDEV_CAST(VETH, Veth); +extern const NetDevVTable veth_vtable; diff --git a/src/network/netdev/vlan.c b/src/network/netdev/vlan.c new file mode 100644 index 0000000..e7f03f0 --- /dev/null +++ b/src/network/netdev/vlan.c @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> +#include <net/if.h> +#include <linux/if_vlan.h> + +#include "vlan-util.h" +#include "vlan.h" + +static int netdev_vlan_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *req) { + struct ifla_vlan_flags flags = {}; + VLan *v; + int r; + + assert(netdev); + assert(link); + assert(req); + + v = VLAN(netdev); + + assert(v); + + r = sd_netlink_message_append_u16(req, IFLA_VLAN_ID, v->id); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VLAN_ID attribute: %m"); + + if (v->gvrp != -1) { + flags.mask |= VLAN_FLAG_GVRP; + SET_FLAG(flags.flags, VLAN_FLAG_GVRP, v->gvrp); + } + + if (v->mvrp != -1) { + flags.mask |= VLAN_FLAG_MVRP; + SET_FLAG(flags.flags, VLAN_FLAG_MVRP, v->mvrp); + } + + if (v->reorder_hdr != -1) { + flags.mask |= VLAN_FLAG_REORDER_HDR; + SET_FLAG(flags.flags, VLAN_FLAG_REORDER_HDR, v->reorder_hdr); + } + + if (v->loose_binding != -1) { + flags.mask |= VLAN_FLAG_LOOSE_BINDING; + SET_FLAG(flags.flags, VLAN_FLAG_LOOSE_BINDING, v->loose_binding); + } + + r = sd_netlink_message_append_data(req, IFLA_VLAN_FLAGS, &flags, sizeof(struct ifla_vlan_flags)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VLAN_FLAGS attribute: %m"); + + return 0; +} + +static int netdev_vlan_verify(NetDev *netdev, const char *filename) { + VLan *v; + + assert(netdev); + assert(filename); + + v = VLAN(netdev); + + assert(v); + + if (v->id == VLANID_INVALID) { + log_netdev_warning(netdev, "VLAN without valid Id (%"PRIu16") configured in %s.", v->id, filename); + return -EINVAL; + } + + return 0; +} + +static void vlan_init(NetDev *netdev) { + VLan *v = VLAN(netdev); + + assert(netdev); + assert(v); + + v->id = VLANID_INVALID; + v->gvrp = -1; + v->mvrp = -1; + v->loose_binding = -1; + v->reorder_hdr = -1; +} + +const NetDevVTable vlan_vtable = { + .object_size = sizeof(VLan), + .init = vlan_init, + .sections = NETDEV_COMMON_SECTIONS "VLAN\0", + .fill_message_create = netdev_vlan_fill_message_create, + .create_type = NETDEV_CREATE_STACKED, + .config_verify = netdev_vlan_verify, +}; diff --git a/src/network/netdev/vlan.h b/src/network/netdev/vlan.h new file mode 100644 index 0000000..9dff924 --- /dev/null +++ b/src/network/netdev/vlan.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +typedef struct VLan VLan; + +#include "netdev.h" + +struct VLan { + NetDev meta; + + uint16_t id; + + int gvrp; + int mvrp; + int loose_binding; + int reorder_hdr; +}; + +DEFINE_NETDEV_CAST(VLAN, VLan); +extern const NetDevVTable vlan_vtable; diff --git a/src/network/netdev/vrf.c b/src/network/netdev/vrf.c new file mode 100644 index 0000000..ae71ae9 --- /dev/null +++ b/src/network/netdev/vrf.c @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <net/if.h> + +#include "vrf.h" + +static int netdev_vrf_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) { + Vrf *v; + int r; + + assert(netdev); + assert(!link); + assert(m); + + v = VRF(netdev); + + assert(v); + + r = sd_netlink_message_append_u32(m, IFLA_VRF_TABLE, v->table); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IPLA_VRF_TABLE attribute: %m"); + + return r; +} + +const NetDevVTable vrf_vtable = { + .object_size = sizeof(Vrf), + .sections = NETDEV_COMMON_SECTIONS "VRF\0", + .fill_message_create = netdev_vrf_fill_message_create, + .create_type = NETDEV_CREATE_MASTER, + .generate_mac = true, +}; diff --git a/src/network/netdev/vrf.h b/src/network/netdev/vrf.h new file mode 100644 index 0000000..87977e2 --- /dev/null +++ b/src/network/netdev/vrf.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +typedef struct Vrf Vrf; + +#include "netdev.h" + +struct Vrf { + NetDev meta; + + uint32_t table; +}; + +DEFINE_NETDEV_CAST(VRF, Vrf); +extern const NetDevVTable vrf_vtable; diff --git a/src/network/netdev/vxcan.c b/src/network/netdev/vxcan.c new file mode 100644 index 0000000..e4e32ff --- /dev/null +++ b/src/network/netdev/vxcan.c @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <linux/can/vxcan.h> + +#include "vxcan.h" + +static int netdev_vxcan_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) { + VxCan *v; + int r; + + assert(netdev); + assert(!link); + assert(m); + + v = VXCAN(netdev); + + assert(v); + + r = sd_netlink_message_open_container(m, VXCAN_INFO_PEER); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append VXCAN_INFO_PEER attribute: %m"); + + if (v->ifname_peer) { + r = sd_netlink_message_append_string(m, IFLA_IFNAME, v->ifname_peer); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Failed to add vxcan netlink interface peer name: %m"); + } + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append VXCAN_INFO_PEER attribute: %m"); + + return r; +} + +static int netdev_vxcan_verify(NetDev *netdev, const char *filename) { + VxCan *v; + + assert(netdev); + assert(filename); + + v = VXCAN(netdev); + + assert(v); + + if (!v->ifname_peer) { + log_netdev_warning(netdev, "VxCan NetDev without peer name configured in %s. Ignoring", filename); + return -EINVAL; + } + + return 0; +} + +static void vxcan_done(NetDev *n) { + VxCan *v; + + assert(n); + + v = VXCAN(n); + + assert(v); + + free(v->ifname_peer); +} + +const NetDevVTable vxcan_vtable = { + .object_size = sizeof(VxCan), + .sections = NETDEV_COMMON_SECTIONS "VXCAN\0", + .done = vxcan_done, + .fill_message_create = netdev_vxcan_fill_message_create, + .create_type = NETDEV_CREATE_INDEPENDENT, + .config_verify = netdev_vxcan_verify, + .generate_mac = true, +}; diff --git a/src/network/netdev/vxcan.h b/src/network/netdev/vxcan.h new file mode 100644 index 0000000..47be3f0 --- /dev/null +++ b/src/network/netdev/vxcan.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +typedef struct VxCan VxCan; + +#include "netdev.h" + +struct VxCan { + NetDev meta; + + char *ifname_peer; +}; + +DEFINE_NETDEV_CAST(VXCAN, VxCan); + +extern const NetDevVTable vxcan_vtable; diff --git a/src/network/netdev/vxlan.c b/src/network/netdev/vxlan.c new file mode 100644 index 0000000..6748f67 --- /dev/null +++ b/src/network/netdev/vxlan.c @@ -0,0 +1,390 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <net/if.h> + +#include "conf-parser.h" +#include "alloc-util.h" +#include "extract-word.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "parse-util.h" +#include "vxlan.h" + +static const char* const df_table[_NETDEV_VXLAN_DF_MAX] = { + [NETDEV_VXLAN_DF_NO] = "no", + [NETDEV_VXLAN_DF_YES] = "yes", + [NETDEV_VXLAN_DF_INHERIT] = "inherit", +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(df, VxLanDF, NETDEV_VXLAN_DF_YES); +DEFINE_CONFIG_PARSE_ENUM(config_parse_df, df, VxLanDF, "Failed to parse VXLAN IPDoNotFragment= setting"); + +static int netdev_vxlan_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) { + VxLan *v; + int r; + + assert(netdev); + assert(m); + + v = VXLAN(netdev); + + assert(v); + + if (v->vni <= VXLAN_VID_MAX) { + r = sd_netlink_message_append_u32(m, IFLA_VXLAN_ID, v->vni); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_ID attribute: %m"); + } + + if (in_addr_is_null(v->group_family, &v->group) == 0) { + if (v->group_family == AF_INET) + r = sd_netlink_message_append_in_addr(m, IFLA_VXLAN_GROUP, &v->group.in); + else + r = sd_netlink_message_append_in6_addr(m, IFLA_VXLAN_GROUP6, &v->group.in6); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_GROUP attribute: %m"); + } else if (in_addr_is_null(v->remote_family, &v->remote) == 0) { + if (v->remote_family == AF_INET) + r = sd_netlink_message_append_in_addr(m, IFLA_VXLAN_GROUP, &v->remote.in); + else + r = sd_netlink_message_append_in6_addr(m, IFLA_VXLAN_GROUP6, &v->remote.in6); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_GROUP attribute: %m"); + } + + if (in_addr_is_null(v->local_family, &v->local) == 0) { + if (v->local_family == AF_INET) + r = sd_netlink_message_append_in_addr(m, IFLA_VXLAN_LOCAL, &v->local.in); + else + r = sd_netlink_message_append_in6_addr(m, IFLA_VXLAN_LOCAL6, &v->local.in6); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_LOCAL attribute: %m"); + } + + r = sd_netlink_message_append_u32(m, IFLA_VXLAN_LINK, link ? link->ifindex : 0); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_LINK attribute: %m"); + + if (v->inherit) { + r = sd_netlink_message_append_flag(m, IFLA_VXLAN_TTL_INHERIT); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_TTL_INHERIT attribute: %m"); + } else { + r = sd_netlink_message_append_u8(m, IFLA_VXLAN_TTL, v->ttl); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_TTL attribute: %m"); + } + + if (v->tos != 0) { + r = sd_netlink_message_append_u8(m, IFLA_VXLAN_TOS, v->tos); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_TOS attribute: %m"); + } + + r = sd_netlink_message_append_u8(m, IFLA_VXLAN_LEARNING, v->learning); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_LEARNING attribute: %m"); + + r = sd_netlink_message_append_u8(m, IFLA_VXLAN_RSC, v->route_short_circuit); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_RSC attribute: %m"); + + r = sd_netlink_message_append_u8(m, IFLA_VXLAN_PROXY, v->arp_proxy); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_PROXY attribute: %m"); + + r = sd_netlink_message_append_u8(m, IFLA_VXLAN_L2MISS, v->l2miss); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_L2MISS attribute: %m"); + + r = sd_netlink_message_append_u8(m, IFLA_VXLAN_L3MISS, v->l3miss); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_L3MISS attribute: %m"); + + if (v->fdb_ageing != 0) { + r = sd_netlink_message_append_u32(m, IFLA_VXLAN_AGEING, v->fdb_ageing / USEC_PER_SEC); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_AGEING attribute: %m"); + } + + if (v->max_fdb != 0) { + r = sd_netlink_message_append_u32(m, IFLA_VXLAN_LIMIT, v->max_fdb); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_LIMIT attribute: %m"); + } + + r = sd_netlink_message_append_u8(m, IFLA_VXLAN_UDP_CSUM, v->udpcsum); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_UDP_CSUM attribute: %m"); + + r = sd_netlink_message_append_u8(m, IFLA_VXLAN_UDP_ZERO_CSUM6_TX, v->udp6zerocsumtx); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_UDP_ZERO_CSUM6_TX attribute: %m"); + + r = sd_netlink_message_append_u8(m, IFLA_VXLAN_UDP_ZERO_CSUM6_RX, v->udp6zerocsumrx); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_UDP_ZERO_CSUM6_RX attribute: %m"); + + r = sd_netlink_message_append_u8(m, IFLA_VXLAN_REMCSUM_TX, v->remote_csum_tx); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_REMCSUM_TX attribute: %m"); + + r = sd_netlink_message_append_u8(m, IFLA_VXLAN_REMCSUM_RX, v->remote_csum_rx); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_REMCSUM_RX attribute: %m"); + + r = sd_netlink_message_append_u16(m, IFLA_VXLAN_PORT, htobe16(v->dest_port)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_PORT attribute: %m"); + + if (v->port_range.low != 0 || v->port_range.high != 0) { + struct ifla_vxlan_port_range port_range; + + port_range.low = htobe16(v->port_range.low); + port_range.high = htobe16(v->port_range.high); + + r = sd_netlink_message_append_data(m, IFLA_VXLAN_PORT_RANGE, &port_range, sizeof(port_range)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_PORT_RANGE attribute: %m"); + } + + r = sd_netlink_message_append_u32(m, IFLA_VXLAN_LABEL, htobe32(v->flow_label)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_LABEL attribute: %m"); + + if (v->group_policy) { + r = sd_netlink_message_append_flag(m, IFLA_VXLAN_GBP); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_GBP attribute: %m"); + } + + if (v->generic_protocol_extension) { + r = sd_netlink_message_append_flag(m, IFLA_VXLAN_GPE); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_GPE attribute: %m"); + } + + if (v->df != _NETDEV_VXLAN_DF_INVALID) { + r = sd_netlink_message_append_u8(m, IFLA_VXLAN_DF, v->df); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_DF attribute: %m"); + } + + return r; +} + +int config_parse_vxlan_address(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + VxLan *v = userdata; + union in_addr_union *addr = data, buffer; + int r, f; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = in_addr_from_string_auto(rvalue, &f, &buffer); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "vxlan '%s' address is invalid, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + r = in_addr_is_multicast(f, &buffer); + + if (streq(lvalue, "Group")) { + if (r <= 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "vxlan %s invalid multicast address, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + v->group_family = f; + } else { + if (r > 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "vxlan %s cannot be a multicast address, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + if (streq(lvalue, "Remote")) + v->remote_family = f; + else + v->local_family = f; + } + + *addr = buffer; + + return 0; +} + +int config_parse_port_range(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + VxLan *v = userdata; + uint16_t low, high; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = parse_ip_port_range(rvalue, &low, &high); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse VXLAN port range '%s'. Port should be greater than 0 and less than 65535.", rvalue); + return 0; + } + + v->port_range.low = low; + v->port_range.high = high; + + return 0; +} + +int config_parse_flow_label(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + VxLan *v = userdata; + unsigned f; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = safe_atou(rvalue, &f); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse VXLAN flow label '%s'.", rvalue); + return 0; + } + + if (f & ~VXLAN_FLOW_LABEL_MAX_MASK) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "VXLAN flow label '%s' not valid. Flow label range should be [0-1048575].", rvalue); + return 0; + } + + v->flow_label = f; + + return 0; +} + +int config_parse_vxlan_ttl(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + VxLan *v = userdata; + unsigned f; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (streq(rvalue, "inherit")) + v->inherit = true; + else { + r = safe_atou(rvalue, &f); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse VXLAN TTL '%s', ignoring assignment: %m", rvalue); + return 0; + } + + if (f > 255) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid VXLAN TTL '%s'. TTL must be <= 255. Ignoring assignment.", rvalue); + return 0; + } + + v->ttl = f; + } + + return 0; +} + +static int netdev_vxlan_verify(NetDev *netdev, const char *filename) { + VxLan *v = VXLAN(netdev); + + assert(netdev); + assert(v); + assert(filename); + + if (v->vni > VXLAN_VID_MAX) + return log_netdev_warning_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s: VXLAN without valid VNI (or VXLAN Segment ID) configured. Ignoring.", + filename); + + if (v->ttl > 255) + return log_netdev_warning_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s: VXLAN TTL must be <= 255. Ignoring.", + filename); + + if (!v->dest_port && v->generic_protocol_extension) + v->dest_port = 4790; + + if (in_addr_is_null(v->group_family, &v->group) == 0 && in_addr_is_null(v->remote_family, &v->remote) == 0) + return log_netdev_warning_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s: VXLAN both 'Group=' and 'Remote=' cannot be specified. Ignoring.", + filename); + + return 0; +} + +static void vxlan_init(NetDev *netdev) { + VxLan *v; + + assert(netdev); + + v = VXLAN(netdev); + + assert(v); + + v->vni = VXLAN_VID_MAX + 1; + v->df = _NETDEV_VXLAN_DF_INVALID; + v->learning = true; + v->udpcsum = false; + v->udp6zerocsumtx = false; + v->udp6zerocsumrx = false; +} + +const NetDevVTable vxlan_vtable = { + .object_size = sizeof(VxLan), + .init = vxlan_init, + .sections = NETDEV_COMMON_SECTIONS "VXLAN\0", + .fill_message_create = netdev_vxlan_fill_message_create, + .create_type = NETDEV_CREATE_STACKED, + .config_verify = netdev_vxlan_verify, + .generate_mac = true, +}; diff --git a/src/network/netdev/vxlan.h b/src/network/netdev/vxlan.h new file mode 100644 index 0000000..371653c --- /dev/null +++ b/src/network/netdev/vxlan.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +typedef struct VxLan VxLan; + +#include <linux/if_link.h> + +#include "in-addr-util.h" +#include "netdev.h" + +#define VXLAN_VID_MAX (1u << 24) - 1 +#define VXLAN_FLOW_LABEL_MAX_MASK 0xFFFFFU + +typedef enum VxLanDF { + NETDEV_VXLAN_DF_NO = VXLAN_DF_UNSET, + NETDEV_VXLAN_DF_YES = VXLAN_DF_SET, + NETDEV_VXLAN_DF_INHERIT = VXLAN_DF_INHERIT, + _NETDEV_VXLAN_DF_MAX, + _NETDEV_VXLAN_DF_INVALID = -1 +} VxLanDF; + +struct VxLan { + NetDev meta; + + uint32_t vni; + + int remote_family; + int local_family; + int group_family; + + VxLanDF df; + + union in_addr_union remote; + union in_addr_union local; + union in_addr_union group; + + unsigned tos; + unsigned ttl; + unsigned max_fdb; + unsigned flow_label; + + uint16_t dest_port; + + usec_t fdb_ageing; + + bool learning; + bool arp_proxy; + bool route_short_circuit; + bool l2miss; + bool l3miss; + bool udpcsum; + bool udp6zerocsumtx; + bool udp6zerocsumrx; + bool remote_csum_tx; + bool remote_csum_rx; + bool group_policy; + bool generic_protocol_extension; + bool inherit; + bool independent; + + struct ifla_vxlan_port_range port_range; +}; + +DEFINE_NETDEV_CAST(VXLAN, VxLan); +extern const NetDevVTable vxlan_vtable; + +const char *df_to_string(VxLanDF d) _const_; +VxLanDF df_from_string(const char *d) _pure_; + +CONFIG_PARSER_PROTOTYPE(config_parse_vxlan_address); +CONFIG_PARSER_PROTOTYPE(config_parse_port_range); +CONFIG_PARSER_PROTOTYPE(config_parse_flow_label); +CONFIG_PARSER_PROTOTYPE(config_parse_df); +CONFIG_PARSER_PROTOTYPE(config_parse_vxlan_ttl); diff --git a/src/network/netdev/wireguard.c b/src/network/netdev/wireguard.c new file mode 100644 index 0000000..416e9b9 --- /dev/null +++ b/src/network/netdev/wireguard.c @@ -0,0 +1,946 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2015-2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. +***/ + +#include <sys/ioctl.h> +#include <net/if.h> + +#include "sd-resolve.h" + +#include "alloc-util.h" +#include "event-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "hexdecoct.h" +#include "memory-util.h" +#include "netlink-util.h" +#include "networkd-manager.h" +#include "networkd-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "resolve-private.h" +#include "string-util.h" +#include "strv.h" +#include "wireguard.h" + +static void resolve_endpoints(NetDev *netdev); + +static void wireguard_peer_free(WireguardPeer *peer) { + WireguardIPmask *mask; + + if (!peer) + return; + + if (peer->wireguard) { + LIST_REMOVE(peers, peer->wireguard->peers, peer); + + set_remove(peer->wireguard->peers_with_unresolved_endpoint, peer); + set_remove(peer->wireguard->peers_with_failed_endpoint, peer); + + if (peer->section) + hashmap_remove(peer->wireguard->peers_by_section, peer->section); + } + + network_config_section_free(peer->section); + + while ((mask = peer->ipmasks)) { + LIST_REMOVE(ipmasks, peer->ipmasks, mask); + free(mask); + } + + free(peer->endpoint_host); + free(peer->endpoint_port); + free(peer->preshared_key_file); + explicit_bzero_safe(peer->preshared_key, WG_KEY_LEN); + + free(peer); +} + +DEFINE_NETWORK_SECTION_FUNCTIONS(WireguardPeer, wireguard_peer_free); + +static int wireguard_peer_new_static(Wireguard *w, const char *filename, unsigned section_line, WireguardPeer **ret) { + _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; + _cleanup_(wireguard_peer_freep) WireguardPeer *peer = NULL; + int r; + + assert(w); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = network_config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + peer = hashmap_get(w->peers_by_section, n); + if (peer) { + *ret = TAKE_PTR(peer); + return 0; + } + + peer = new(WireguardPeer, 1); + if (!peer) + return -ENOMEM; + + *peer = (WireguardPeer) { + .flags = WGPEER_F_REPLACE_ALLOWEDIPS, + .wireguard = w, + .section = TAKE_PTR(n), + }; + + LIST_PREPEND(peers, w->peers, peer); + + r = hashmap_ensure_allocated(&w->peers_by_section, &network_config_hash_ops); + if (r < 0) + return r; + + r = hashmap_put(w->peers_by_section, peer->section, peer); + if (r < 0) + return r; + + *ret = TAKE_PTR(peer); + return 0; +} + +static int wireguard_set_ipmask_one(NetDev *netdev, sd_netlink_message *message, const WireguardIPmask *mask, uint16_t index) { + int r; + + assert(message); + assert(mask); + assert(index > 0); + + /* This returns 1 on success, 0 on recoverable error, and negative errno on failure. */ + + r = sd_netlink_message_open_array(message, index); + if (r < 0) + return 0; + + r = sd_netlink_message_append_u16(message, WGALLOWEDIP_A_FAMILY, mask->family); + if (r < 0) + goto cancel; + + r = netlink_message_append_in_addr_union(message, WGALLOWEDIP_A_IPADDR, mask->family, &mask->ip); + if (r < 0) + goto cancel; + + r = sd_netlink_message_append_u8(message, WGALLOWEDIP_A_CIDR_MASK, mask->cidr); + if (r < 0) + goto cancel; + + r = sd_netlink_message_close_container(message); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not add wireguard allowed ip: %m"); + + return 1; + +cancel: + r = sd_netlink_message_cancel_array(message); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not cancel wireguard allowed ip message attribute: %m"); + + return 0; +} + +static int wireguard_set_peer_one(NetDev *netdev, sd_netlink_message *message, const WireguardPeer *peer, uint16_t index, WireguardIPmask **mask_start) { + WireguardIPmask *mask, *start; + uint16_t j = 0; + int r; + + assert(message); + assert(peer); + assert(index > 0); + assert(mask_start); + + /* This returns 1 on success, 0 on recoverable error, and negative errno on failure. */ + + start = *mask_start ?: peer->ipmasks; + + r = sd_netlink_message_open_array(message, index); + if (r < 0) + return 0; + + r = sd_netlink_message_append_data(message, WGPEER_A_PUBLIC_KEY, &peer->public_key, sizeof(peer->public_key)); + if (r < 0) + goto cancel; + + if (!*mask_start) { + r = sd_netlink_message_append_data(message, WGPEER_A_PRESHARED_KEY, &peer->preshared_key, WG_KEY_LEN); + if (r < 0) + goto cancel; + + r = sd_netlink_message_append_u32(message, WGPEER_A_FLAGS, peer->flags); + if (r < 0) + goto cancel; + + r = sd_netlink_message_append_u16(message, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, peer->persistent_keepalive_interval); + if (r < 0) + goto cancel; + + if (IN_SET(peer->endpoint.sa.sa_family, AF_INET, AF_INET6)) { + r = netlink_message_append_sockaddr_union(message, WGPEER_A_ENDPOINT, &peer->endpoint); + if (r < 0) + goto cancel; + } + } + + r = sd_netlink_message_open_container(message, WGPEER_A_ALLOWEDIPS); + if (r < 0) + goto cancel; + + LIST_FOREACH(ipmasks, mask, start) { + r = wireguard_set_ipmask_one(netdev, message, mask, ++j); + if (r < 0) + return r; + if (r == 0) + break; + } + + r = sd_netlink_message_close_container(message); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not add wireguard allowed ip: %m"); + + r = sd_netlink_message_close_container(message); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not add wireguard peer: %m"); + + *mask_start = mask; /* Start next cycle from this mask. */ + return !mask; + +cancel: + r = sd_netlink_message_cancel_array(message); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not cancel wireguard peers: %m"); + + return 0; +} + +static int wireguard_set_interface(NetDev *netdev) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + WireguardIPmask *mask_start = NULL; + WireguardPeer *peer, *peer_start; + bool sent_once = false; + uint32_t serial; + Wireguard *w; + int r; + + assert(netdev); + w = WIREGUARD(netdev); + assert(w); + + for (peer_start = w->peers; peer_start || !sent_once; ) { + uint16_t i = 0; + + message = sd_netlink_message_unref(message); + + r = sd_genl_message_new(netdev->manager->genl, SD_GENL_WIREGUARD, WG_CMD_SET_DEVICE, &message); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Failed to allocate generic netlink message: %m"); + + r = sd_netlink_message_append_string(message, WGDEVICE_A_IFNAME, netdev->ifname); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append wireguard interface name: %m"); + + if (peer_start == w->peers) { + r = sd_netlink_message_append_data(message, WGDEVICE_A_PRIVATE_KEY, &w->private_key, WG_KEY_LEN); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append wireguard private key: %m"); + + r = sd_netlink_message_append_u16(message, WGDEVICE_A_LISTEN_PORT, w->port); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append wireguard port: %m"); + + r = sd_netlink_message_append_u32(message, WGDEVICE_A_FWMARK, w->fwmark); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append wireguard fwmark: %m"); + + r = sd_netlink_message_append_u32(message, WGDEVICE_A_FLAGS, w->flags); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append wireguard flags: %m"); + } + + r = sd_netlink_message_open_container(message, WGDEVICE_A_PEERS); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append wireguard peer attributes: %m"); + + LIST_FOREACH(peers, peer, peer_start) { + r = wireguard_set_peer_one(netdev, message, peer, ++i, &mask_start); + if (r < 0) + return r; + if (r == 0) + break; + } + peer_start = peer; /* Start next cycle from this peer. */ + + r = sd_netlink_message_close_container(message); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not close wireguard container: %m"); + + r = sd_netlink_send(netdev->manager->genl, message, &serial); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not set wireguard device: %m"); + + sent_once = true; + } + + return 0; +} + +static void wireguard_peer_destroy_callback(WireguardPeer *peer) { + NetDev *netdev; + + assert(peer); + assert(peer->wireguard); + + netdev = NETDEV(peer->wireguard); + + if (section_is_invalid(peer->section)) + wireguard_peer_free(peer); + + netdev_unref(netdev); +} + +static int on_resolve_retry(sd_event_source *s, usec_t usec, void *userdata) { + NetDev *netdev = userdata; + Wireguard *w; + + assert(netdev); + w = WIREGUARD(netdev); + assert(w); + + if (!netdev_is_managed(netdev)) + return 0; + + assert(set_isempty(w->peers_with_unresolved_endpoint)); + + SWAP_TWO(w->peers_with_unresolved_endpoint, w->peers_with_failed_endpoint); + + resolve_endpoints(netdev); + + return 0; +} + +/* + * Given the number of retries this function will return will an exponential + * increasing time in milliseconds to wait starting at 200ms and capped at 25 seconds. + */ +static int exponential_backoff_milliseconds(unsigned n_retries) { + return (2 << MIN(n_retries, 7U)) * 100 * USEC_PER_MSEC; +} + +static int wireguard_resolve_handler(sd_resolve_query *q, + int ret, + const struct addrinfo *ai, + WireguardPeer *peer) { + NetDev *netdev; + Wireguard *w; + int r; + + assert(peer); + assert(peer->wireguard); + + w = peer->wireguard; + netdev = NETDEV(w); + + if (!netdev_is_managed(netdev)) + return 0; + + if (ret != 0) { + log_netdev_error(netdev, "Failed to resolve host '%s:%s': %s", peer->endpoint_host, peer->endpoint_port, gai_strerror(ret)); + + r = set_ensure_put(&w->peers_with_failed_endpoint, NULL, peer); + if (r < 0) { + log_netdev_error(netdev, "Failed to save a peer, dropping the peer: %m"); + peer->section->invalid = true; + goto resolve_next; + } + + } else if ((ai->ai_family == AF_INET && ai->ai_addrlen == sizeof(struct sockaddr_in)) || + (ai->ai_family == AF_INET6 && ai->ai_addrlen == sizeof(struct sockaddr_in6))) + memcpy(&peer->endpoint, ai->ai_addr, ai->ai_addrlen); + else + log_netdev_error(netdev, "Neither IPv4 nor IPv6 address found for peer endpoint %s:%s, ignoring the address.", + peer->endpoint_host, peer->endpoint_port); + +resolve_next: + if (!set_isempty(w->peers_with_unresolved_endpoint)) { + resolve_endpoints(netdev); + return 0; + } + + (void) wireguard_set_interface(netdev); + + if (!set_isempty(w->peers_with_failed_endpoint)) { + usec_t usec; + + w->n_retries++; + usec = usec_add(now(CLOCK_MONOTONIC), exponential_backoff_milliseconds(w->n_retries)); + r = event_reset_time(netdev->manager->event, &w->resolve_retry_event_source, + CLOCK_MONOTONIC, usec, 0, on_resolve_retry, netdev, + 0, "wireguard-resolve-retry", true); + if (r < 0) { + log_netdev_warning_errno(netdev, r, "Could not arm resolve retry handler: %m"); + return 0; + } + } + + return 0; +} + +static void resolve_endpoints(NetDev *netdev) { + static const struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_DGRAM, + .ai_protocol = IPPROTO_UDP + }; + WireguardPeer *peer; + Wireguard *w; + int r; + + assert(netdev); + w = WIREGUARD(netdev); + assert(w); + + SET_FOREACH(peer, w->peers_with_unresolved_endpoint) { + r = resolve_getaddrinfo(netdev->manager->resolve, + NULL, + peer->endpoint_host, + peer->endpoint_port, + &hints, + wireguard_resolve_handler, + wireguard_peer_destroy_callback, + peer); + if (r == -ENOBUFS) + break; + if (r < 0) { + log_netdev_error_errno(netdev, r, "Failed to create resolver: %m"); + continue; + } + + /* Avoid freeing netdev. It will be unrefed by the destroy callback. */ + netdev_ref(netdev); + + (void) set_remove(w->peers_with_unresolved_endpoint, peer); + } +} + +static int netdev_wireguard_post_create(NetDev *netdev, Link *link, sd_netlink_message *m) { + assert(netdev); + assert(WIREGUARD(netdev)); + + (void) wireguard_set_interface(netdev); + resolve_endpoints(netdev); + return 0; +} + +int config_parse_wireguard_listen_port( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + uint16_t *s = data; + int r; + + assert(rvalue); + assert(data); + + if (isempty(rvalue) || streq(rvalue, "auto")) { + *s = 0; + return 0; + } + + r = parse_ip_port(rvalue, s); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid port specification, ignoring assignment: %s", rvalue); + return 0; + } + + return 0; +} + +static int wireguard_decode_key_and_warn( + const char *rvalue, + uint8_t ret[static WG_KEY_LEN], + const char *unit, + const char *filename, + unsigned line, + const char *lvalue) { + + _cleanup_(erase_and_freep) void *key = NULL; + size_t len; + int r; + + assert(rvalue); + assert(ret); + assert(filename); + assert(lvalue); + + if (isempty(rvalue)) { + memzero(ret, WG_KEY_LEN); + return 0; + } + + if (!streq(lvalue, "PublicKey")) + (void) warn_file_is_world_accessible(filename, NULL, unit, line); + + r = unbase64mem_full(rvalue, strlen(rvalue), true, &key, &len); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to decode wireguard key provided by %s=, ignoring assignment: %m", lvalue); + return 0; + } + if (len != WG_KEY_LEN) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Wireguard key provided by %s= has invalid length (%zu bytes), ignoring assignment.", + lvalue, len); + return 0; + } + + memcpy(ret, key, WG_KEY_LEN); + return 0; +} + +int config_parse_wireguard_private_key( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Wireguard *w; + + assert(data); + w = WIREGUARD(data); + assert(w); + + return wireguard_decode_key_and_warn(rvalue, w->private_key, unit, filename, line, lvalue); +} + +int config_parse_wireguard_private_key_file( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_free_ char *path = NULL; + Wireguard *w; + + assert(data); + w = WIREGUARD(data); + assert(w); + + if (isempty(rvalue)) { + w->private_key_file = mfree(w->private_key_file); + return 0; + } + + path = strdup(rvalue); + if (!path) + return log_oom(); + + if (path_simplify_and_warn(path, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue) < 0) + return 0; + + return free_and_replace(w->private_key_file, path); +} + +int config_parse_wireguard_peer_key( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL; + Wireguard *w; + int r; + + assert(data); + w = WIREGUARD(data); + assert(w); + + r = wireguard_peer_new_static(w, filename, section_line, &peer); + if (r < 0) + return log_oom(); + + r = wireguard_decode_key_and_warn(rvalue, + streq(lvalue, "PublicKey") ? peer->public_key : peer->preshared_key, + unit, filename, line, lvalue); + if (r < 0) + return r; + + TAKE_PTR(peer); + return 0; +} + +int config_parse_wireguard_preshared_key_file( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL; + _cleanup_free_ char *path = NULL; + Wireguard *w; + int r; + + assert(data); + w = WIREGUARD(data); + assert(w); + + r = wireguard_peer_new_static(w, filename, section_line, &peer); + if (r < 0) + return log_oom(); + + if (isempty(rvalue)) { + peer->preshared_key_file = mfree(peer->preshared_key_file); + TAKE_PTR(peer); + return 0; + } + + path = strdup(rvalue); + if (!path) + return log_oom(); + + if (path_simplify_and_warn(path, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue) < 0) + return 0; + + free_and_replace(peer->preshared_key_file, path); + TAKE_PTR(peer); + return 0; +} + +int config_parse_wireguard_allowed_ips( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL; + union in_addr_union addr; + unsigned char prefixlen; + int r, family; + Wireguard *w; + WireguardIPmask *ipmask; + + assert(rvalue); + assert(data); + + w = WIREGUARD(data); + assert(w); + + r = wireguard_peer_new_static(w, filename, section_line, &peer); + if (r < 0) + return log_oom(); + + for (const char *p = rvalue;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, "," WHITESPACE, 0); + if (r == 0) + break; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to split allowed ips \"%s\" option: %m", rvalue); + break; + } + + r = in_addr_prefix_from_string_auto(word, &family, &addr, &prefixlen); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Network address is invalid, ignoring assignment: %s", word); + continue; + } + + ipmask = new(WireguardIPmask, 1); + if (!ipmask) + return log_oom(); + + *ipmask = (WireguardIPmask) { + .family = family, + .ip.in6 = addr.in6, + .cidr = prefixlen, + }; + + LIST_PREPEND(ipmasks, peer->ipmasks, ipmask); + } + + TAKE_PTR(peer); + return 0; +} + +int config_parse_wireguard_endpoint( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL; + const char *begin, *end; + Wireguard *w; + size_t len; + int r; + + assert(data); + assert(rvalue); + + w = WIREGUARD(data); + assert(w); + + if (rvalue[0] == '[') { + begin = &rvalue[1]; + end = strchr(rvalue, ']'); + if (!end) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Unable to find matching brace of endpoint, ignoring assignment: %s", + rvalue); + return 0; + } + len = end - begin; + ++end; + if (*end != ':' || !*(end + 1)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Unable to find port of endpoint, ignoring assignment: %s", + rvalue); + return 0; + } + ++end; + } else { + begin = rvalue; + end = strrchr(rvalue, ':'); + if (!end || !*(end + 1)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Unable to find port of endpoint, ignoring assignment: %s", + rvalue); + return 0; + } + len = end - begin; + ++end; + } + + r = wireguard_peer_new_static(w, filename, section_line, &peer); + if (r < 0) + return log_oom(); + + r = free_and_strndup(&peer->endpoint_host, begin, len); + if (r < 0) + return log_oom(); + + r = free_and_strdup(&peer->endpoint_port, end); + if (r < 0) + return log_oom(); + + r = set_ensure_put(&w->peers_with_unresolved_endpoint, NULL, peer); + if (r < 0) + return log_oom(); + TAKE_PTR(peer); /* The peer may already have been in the hash map, that is fine too. */ + + return 0; +} + +int config_parse_wireguard_keepalive( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL; + uint16_t keepalive = 0; + Wireguard *w; + int r; + + assert(rvalue); + assert(data); + + w = WIREGUARD(data); + assert(w); + + r = wireguard_peer_new_static(w, filename, section_line, &peer); + if (r < 0) + return log_oom(); + + if (streq(rvalue, "off")) + keepalive = 0; + else { + r = safe_atou16(rvalue, &keepalive); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse \"%s\" as keepalive interval (range 0–65535), ignoring assignment: %m", + rvalue); + return 0; + } + } + + peer->persistent_keepalive_interval = keepalive; + + TAKE_PTR(peer); + return 0; +} + +static void wireguard_init(NetDev *netdev) { + Wireguard *w; + + assert(netdev); + w = WIREGUARD(netdev); + assert(w); + + w->flags = WGDEVICE_F_REPLACE_PEERS; +} + +static void wireguard_done(NetDev *netdev) { + Wireguard *w; + + assert(netdev); + w = WIREGUARD(netdev); + assert(w); + + sd_event_source_unref(w->resolve_retry_event_source); + + explicit_bzero_safe(w->private_key, WG_KEY_LEN); + free(w->private_key_file); + + hashmap_free_with_destructor(w->peers_by_section, wireguard_peer_free); + set_free(w->peers_with_unresolved_endpoint); + set_free(w->peers_with_failed_endpoint); +} + +static int wireguard_read_key_file(const char *filename, uint8_t dest[static WG_KEY_LEN]) { + _cleanup_(erase_and_freep) char *key = NULL; + size_t key_len; + int r; + + if (!filename) + return 0; + + assert(dest); + + (void) warn_file_is_world_accessible(filename, NULL, NULL, 0); + + r = read_full_file_full( + AT_FDCWD, filename, + READ_FULL_FILE_SECURE | READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_WARN_WORLD_READABLE | READ_FULL_FILE_CONNECT_SOCKET, + NULL, &key, &key_len); + if (r < 0) + return r; + + if (key_len != WG_KEY_LEN) + return -EINVAL; + + memcpy(dest, key, WG_KEY_LEN); + return 0; +} + +static int wireguard_peer_verify(WireguardPeer *peer) { + NetDev *netdev = NETDEV(peer->wireguard); + int r; + + if (section_is_invalid(peer->section)) + return -EINVAL; + + if (eqzero(peer->public_key)) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s: WireGuardPeer section without PublicKey= configured. " + "Ignoring [WireGuardPeer] section from line %u.", + peer->section->filename, peer->section->line); + + r = wireguard_read_key_file(peer->preshared_key_file, peer->preshared_key); + if (r < 0) + return log_netdev_error_errno(netdev, r, + "%s: Failed to read preshared key from '%s'. " + "Ignoring [WireGuardPeer] section from line %u.", + peer->section->filename, peer->preshared_key_file, + peer->section->line); + + return 0; +} + +static int wireguard_verify(NetDev *netdev, const char *filename) { + WireguardPeer *peer, *peer_next; + Wireguard *w; + int r; + + assert(netdev); + w = WIREGUARD(netdev); + assert(w); + + r = wireguard_read_key_file(w->private_key_file, w->private_key); + if (r < 0) + return log_netdev_error_errno(netdev, r, + "Failed to read private key from %s. Dropping network device %s.", + w->private_key_file, netdev->ifname); + + if (eqzero(w->private_key)) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s: Missing PrivateKey= or PrivateKeyFile=, " + "Dropping network device %s.", + filename, netdev->ifname); + + LIST_FOREACH_SAFE(peers, peer, peer_next, w->peers) + if (wireguard_peer_verify(peer) < 0) + wireguard_peer_free(peer); + + return 0; +} + +const NetDevVTable wireguard_vtable = { + .object_size = sizeof(Wireguard), + .sections = NETDEV_COMMON_SECTIONS "WireGuard\0WireGuardPeer\0", + .post_create = netdev_wireguard_post_create, + .init = wireguard_init, + .done = wireguard_done, + .create_type = NETDEV_CREATE_INDEPENDENT, + .config_verify = wireguard_verify, + .generate_mac = true, +}; diff --git a/src/network/netdev/wireguard.h b/src/network/netdev/wireguard.h new file mode 100644 index 0000000..b9b5ae9 --- /dev/null +++ b/src/network/netdev/wireguard.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#pragma once + +typedef struct Wireguard Wireguard; + +#include <netinet/in.h> +#include <linux/wireguard.h> + +#include "in-addr-util.h" +#include "netdev.h" +#include "socket-util.h" + +typedef struct WireguardIPmask { + uint16_t family; + union in_addr_union ip; + uint8_t cidr; + + LIST_FIELDS(struct WireguardIPmask, ipmasks); +} WireguardIPmask; + +typedef struct WireguardPeer { + Wireguard *wireguard; + NetworkConfigSection *section; + + uint8_t public_key[WG_KEY_LEN]; + uint8_t preshared_key[WG_KEY_LEN]; + char *preshared_key_file; + uint32_t flags; + uint16_t persistent_keepalive_interval; + + union sockaddr_union endpoint; + char *endpoint_host; + char *endpoint_port; + + LIST_HEAD(WireguardIPmask, ipmasks); + LIST_FIELDS(struct WireguardPeer, peers); +} WireguardPeer; + +struct Wireguard { + NetDev meta; + unsigned last_peer_section; + + uint32_t flags; + uint8_t private_key[WG_KEY_LEN]; + char *private_key_file; + uint16_t port; + uint32_t fwmark; + + Hashmap *peers_by_section; + Set *peers_with_unresolved_endpoint; + Set *peers_with_failed_endpoint; + + LIST_HEAD(WireguardPeer, peers); + + unsigned n_retries; + sd_event_source *resolve_retry_event_source; +}; + +DEFINE_NETDEV_CAST(WIREGUARD, Wireguard); +extern const NetDevVTable wireguard_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_allowed_ips); +CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_endpoint); +CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_listen_port); +CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_peer_key); +CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_private_key); +CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_private_key_file); +CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_preshared_key_file); +CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_keepalive); diff --git a/src/network/netdev/xfrm.c b/src/network/netdev/xfrm.c new file mode 100644 index 0000000..a407c54 --- /dev/null +++ b/src/network/netdev/xfrm.c @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "missing_network.h" +#include "xfrm.h" + +static int xfrm_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *message) { + Xfrm *x; + int r; + + assert(netdev); + assert(message); + + x = XFRM(netdev); + + assert(link || x->independent); + + r = sd_netlink_message_append_u32(message, IFLA_XFRM_LINK, link ? link->ifindex : LOOPBACK_IFINDEX); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_XFRM_LINK: %m"); + + r = sd_netlink_message_append_u32(message, IFLA_XFRM_IF_ID, x->if_id); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_XFRM_IF_ID: %m"); + + return 0; +} + +const NetDevVTable xfrm_vtable = { + .object_size = sizeof(Xfrm), + .sections = NETDEV_COMMON_SECTIONS "Xfrm\0", + .fill_message_create = xfrm_fill_message_create, + .create_type = NETDEV_CREATE_STACKED +}; diff --git a/src/network/netdev/xfrm.h b/src/network/netdev/xfrm.h new file mode 100644 index 0000000..f56c4f2 --- /dev/null +++ b/src/network/netdev/xfrm.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "netdev.h" + +typedef struct Xfrm { + NetDev meta; + + uint32_t if_id; + bool independent; +} Xfrm; + +DEFINE_NETDEV_CAST(XFRM, Xfrm); +extern const NetDevVTable xfrm_vtable; diff --git a/src/network/networkctl.c b/src/network/networkctl.c new file mode 100644 index 0000000..63a90bc --- /dev/null +++ b/src/network/networkctl.c @@ -0,0 +1,2830 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <arpa/inet.h> +#include <getopt.h> +#include <linux/if_addrlabel.h> +#include <net/if.h> +#include <stdbool.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <linux/if_bridge.h> +#include <linux/if_tunnel.h> + +#include "sd-bus.h" +#include "sd-device.h" +#include "sd-dhcp-client.h" +#include "sd-hwdb.h" +#include "sd-lldp.h" +#include "sd-netlink.h" +#include "sd-network.h" + +#include "alloc-util.h" +#include "bond-util.h" +#include "bridge-util.h" +#include "bus-common-errors.h" +#include "bus-error.h" +#include "bus-locator.h" +#include "device-util.h" +#include "escape.h" +#include "ether-addr-util.h" +#include "ethtool-util.h" +#include "fd-util.h" +#include "format-table.h" +#include "format-util.h" +#include "geneve-util.h" +#include "glob-util.h" +#include "hwdb-util.h" +#include "ipvlan-util.h" +#include "local-addresses.h" +#include "locale-util.h" +#include "logs-show.h" +#include "macro.h" +#include "macvlan-util.h" +#include "main-func.h" +#include "netlink-util.h" +#include "network-internal.h" +#include "pager.h" +#include "parse-util.h" +#include "pretty-print.h" +#include "set.h" +#include "socket-netlink.h" +#include "socket-util.h" +#include "sort-util.h" +#include "sparse-endian.h" +#include "stdio-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "strxcpyx.h" +#include "terminal-util.h" +#include "unit-def.h" +#include "verbs.h" +#include "wifi-util.h" + +/* Kernel defines MODULE_NAME_LEN as 64 - sizeof(unsigned long). So, 64 is enough. */ +#define NETDEV_KIND_MAX 64 + +/* use 128 kB for receive socket kernel queue, we shouldn't need more here */ +#define RCVBUF_SIZE (128*1024) + +static PagerFlags arg_pager_flags = 0; +static bool arg_legend = true; +static bool arg_all = false; +static bool arg_stats = false; +static bool arg_full = false; +static unsigned arg_lines = 10; + +static void operational_state_to_color(const char *name, const char *state, const char **on, const char **off) { + assert(on); + assert(off); + + if (STRPTR_IN_SET(state, "routable", "enslaved") || + (streq_ptr(name, "lo") && streq_ptr(state, "carrier"))) { + *on = ansi_highlight_green(); + *off = ansi_normal(); + } else if (streq_ptr(state, "degraded")) { + *on = ansi_highlight_yellow(); + *off = ansi_normal(); + } else + *on = *off = ""; +} + +static void setup_state_to_color(const char *state, const char **on, const char **off) { + assert(on); + assert(off); + + if (streq_ptr(state, "configured")) { + *on = ansi_highlight_green(); + *off = ansi_normal(); + } else if (streq_ptr(state, "configuring")) { + *on = ansi_highlight_yellow(); + *off = ansi_normal(); + } else if (STRPTR_IN_SET(state, "failed", "linger")) { + *on = ansi_highlight_red(); + *off = ansi_normal(); + } else + *on = *off = ""; +} + +typedef struct VxLanInfo { + uint32_t vni; + uint32_t link; + + int local_family; + int group_family; + + union in_addr_union local; + union in_addr_union group; + + uint16_t dest_port; + + uint8_t proxy; + uint8_t learning; + uint8_t inerit; + uint8_t rsc; + uint8_t l2miss; + uint8_t l3miss; + uint8_t tos; + uint8_t ttl; +} VxLanInfo; + +typedef struct LinkInfo { + char name[IFNAMSIZ+1]; + char netdev_kind[NETDEV_KIND_MAX]; + sd_device *sd_device; + int ifindex; + unsigned short iftype; + hw_addr_data hw_address; + struct ether_addr permanent_mac_address; + uint32_t master; + uint32_t mtu; + uint32_t min_mtu; + uint32_t max_mtu; + uint32_t tx_queues; + uint32_t rx_queues; + uint8_t addr_gen_mode; + char *qdisc; + char **alternative_names; + + union { + struct rtnl_link_stats64 stats64; + struct rtnl_link_stats stats; + }; + + uint64_t tx_bitrate; + uint64_t rx_bitrate; + + /* bridge info */ + uint32_t forward_delay; + uint32_t hello_time; + uint32_t max_age; + uint32_t ageing_time; + uint32_t stp_state; + uint32_t cost; + uint16_t priority; + uint8_t mcast_igmp_version; + uint8_t port_state; + + /* vxlan info */ + VxLanInfo vxlan_info; + + /* vlan info */ + uint16_t vlan_id; + + /* tunnel info */ + uint8_t ttl; + uint8_t tos; + uint8_t inherit; + uint8_t df; + uint8_t csum; + uint8_t csum6_tx; + uint8_t csum6_rx; + uint16_t tunnel_port; + uint32_t vni; + uint32_t label; + union in_addr_union local; + union in_addr_union remote; + + /* bonding info */ + uint8_t mode; + uint32_t miimon; + uint32_t updelay; + uint32_t downdelay; + + /* macvlan and macvtap info */ + uint32_t macvlan_mode; + + /* ipvlan info */ + uint16_t ipvlan_mode; + uint16_t ipvlan_flags; + + /* ethtool info */ + int autonegotiation; + uint64_t speed; + Duplex duplex; + NetDevPort port; + + /* wlan info */ + enum nl80211_iftype wlan_iftype; + char *ssid; + struct ether_addr bssid; + + bool has_mac_address:1; + bool has_permanent_mac_address:1; + bool has_tx_queues:1; + bool has_rx_queues:1; + bool has_stats64:1; + bool has_stats:1; + bool has_bitrates:1; + bool has_ethtool_link_info:1; + bool has_wlan_link_info:1; + bool has_tunnel_ipv4:1; + bool has_ipv6_address_generation_mode:1; + + bool needs_freeing:1; +} LinkInfo; + +static int link_info_compare(const LinkInfo *a, const LinkInfo *b) { + return CMP(a->ifindex, b->ifindex); +} + +static const LinkInfo* link_info_array_free(LinkInfo *array) { + for (unsigned i = 0; array && array[i].needs_freeing; i++) { + sd_device_unref(array[i].sd_device); + free(array[i].ssid); + free(array[i].qdisc); + strv_free(array[i].alternative_names); + } + + return mfree(array); +} +DEFINE_TRIVIAL_CLEANUP_FUNC(LinkInfo*, link_info_array_free); + +static int decode_netdev(sd_netlink_message *m, LinkInfo *info) { + const char *received_kind; + int r; + + assert(m); + assert(info); + + r = sd_netlink_message_enter_container(m, IFLA_LINKINFO); + if (r < 0) + return r; + + r = sd_netlink_message_read_string(m, IFLA_INFO_KIND, &received_kind); + if (r < 0) + return r; + + r = sd_netlink_message_enter_container(m, IFLA_INFO_DATA); + if (r < 0) + return r; + + if (streq(received_kind, "bridge")) { + (void) sd_netlink_message_read_u32(m, IFLA_BR_FORWARD_DELAY, &info->forward_delay); + (void) sd_netlink_message_read_u32(m, IFLA_BR_HELLO_TIME, &info->hello_time); + (void) sd_netlink_message_read_u32(m, IFLA_BR_MAX_AGE, &info->max_age); + (void) sd_netlink_message_read_u32(m, IFLA_BR_AGEING_TIME, &info->ageing_time); + (void) sd_netlink_message_read_u32(m, IFLA_BR_STP_STATE, &info->stp_state); + (void) sd_netlink_message_read_u32(m, IFLA_BRPORT_COST, &info->cost); + (void) sd_netlink_message_read_u16(m, IFLA_BR_PRIORITY, &info->priority); + (void) sd_netlink_message_read_u8(m, IFLA_BR_MCAST_IGMP_VERSION, &info->mcast_igmp_version); + (void) sd_netlink_message_read_u8(m, IFLA_BRPORT_STATE, &info->port_state); + } if (streq(received_kind, "bond")) { + (void) sd_netlink_message_read_u8(m, IFLA_BOND_MODE, &info->mode); + (void) sd_netlink_message_read_u32(m, IFLA_BOND_MIIMON, &info->miimon); + (void) sd_netlink_message_read_u32(m, IFLA_BOND_DOWNDELAY, &info->downdelay); + (void) sd_netlink_message_read_u32(m, IFLA_BOND_UPDELAY, &info->updelay); + } else if (streq(received_kind, "vxlan")) { + (void) sd_netlink_message_read_u32(m, IFLA_VXLAN_ID, &info->vxlan_info.vni); + + r = sd_netlink_message_read_in_addr(m, IFLA_VXLAN_GROUP, &info->vxlan_info.group.in); + if (r >= 0) + info->vxlan_info.group_family = AF_INET; + else { + r = sd_netlink_message_read_in6_addr(m, IFLA_VXLAN_GROUP6, &info->vxlan_info.group.in6); + if (r >= 0) + info->vxlan_info.group_family = AF_INET6; + } + + r = sd_netlink_message_read_in_addr(m, IFLA_VXLAN_LOCAL, &info->vxlan_info.local.in); + if (r >= 0) + info->vxlan_info.local_family = AF_INET; + else { + r = sd_netlink_message_read_in6_addr(m, IFLA_VXLAN_LOCAL6, &info->vxlan_info.local.in6); + if (r >= 0) + info->vxlan_info.local_family = AF_INET6; + } + + (void) sd_netlink_message_read_u32(m, IFLA_VXLAN_LINK, &info->vxlan_info.link); + (void) sd_netlink_message_read_u16(m, IFLA_VXLAN_PORT, &info->vxlan_info.dest_port); + (void) sd_netlink_message_read_u8(m, IFLA_VXLAN_PROXY, &info->vxlan_info.proxy); + (void) sd_netlink_message_read_u8(m, IFLA_VXLAN_LEARNING, &info->vxlan_info.learning); + (void) sd_netlink_message_read_u8(m, IFLA_VXLAN_RSC, &info->vxlan_info.rsc); + (void) sd_netlink_message_read_u8(m, IFLA_VXLAN_L3MISS, &info->vxlan_info.l3miss); + (void) sd_netlink_message_read_u8(m, IFLA_VXLAN_L2MISS, &info->vxlan_info.l2miss); + (void) sd_netlink_message_read_u8(m, IFLA_VXLAN_TOS, &info->vxlan_info.tos); + (void) sd_netlink_message_read_u8(m, IFLA_VXLAN_TTL, &info->vxlan_info.ttl); + } else if (streq(received_kind, "vlan")) + (void) sd_netlink_message_read_u16(m, IFLA_VLAN_ID, &info->vlan_id); + else if (STR_IN_SET(received_kind, "ipip", "sit")) { + (void) sd_netlink_message_read_in_addr(m, IFLA_IPTUN_LOCAL, &info->local.in); + (void) sd_netlink_message_read_in_addr(m, IFLA_IPTUN_REMOTE, &info->remote.in); + } else if (streq(received_kind, "geneve")) { + (void) sd_netlink_message_read_u32(m, IFLA_GENEVE_ID, &info->vni); + + r = sd_netlink_message_read_in_addr(m, IFLA_GENEVE_REMOTE, &info->remote.in); + if (r >= 0) + info->has_tunnel_ipv4 = true; + else + (void) sd_netlink_message_read_in6_addr(m, IFLA_GENEVE_REMOTE6, &info->remote.in6); + + (void) sd_netlink_message_read_u8(m, IFLA_GENEVE_TTL, &info->ttl); + (void) sd_netlink_message_read_u8(m, IFLA_GENEVE_TTL_INHERIT, &info->inherit); + (void) sd_netlink_message_read_u8(m, IFLA_GENEVE_TOS, &info->tos); + (void) sd_netlink_message_read_u8(m, IFLA_GENEVE_DF, &info->df); + (void) sd_netlink_message_read_u8(m, IFLA_GENEVE_UDP_CSUM, &info->csum); + (void) sd_netlink_message_read_u8(m, IFLA_GENEVE_UDP_ZERO_CSUM6_TX, &info->csum6_tx); + (void) sd_netlink_message_read_u8(m, IFLA_GENEVE_UDP_ZERO_CSUM6_RX, &info->csum6_rx); + (void) sd_netlink_message_read_u16(m, IFLA_GENEVE_PORT, &info->tunnel_port); + (void) sd_netlink_message_read_u32(m, IFLA_GENEVE_LABEL, &info->label); + } else if (STR_IN_SET(received_kind, "gre", "gretap", "erspan")) { + (void) sd_netlink_message_read_in_addr(m, IFLA_GRE_LOCAL, &info->local.in); + (void) sd_netlink_message_read_in_addr(m, IFLA_GRE_REMOTE, &info->remote.in); + } else if (STR_IN_SET(received_kind, "ip6gre", "ip6gretap", "ip6erspan")) { + (void) sd_netlink_message_read_in6_addr(m, IFLA_GRE_LOCAL, &info->local.in6); + (void) sd_netlink_message_read_in6_addr(m, IFLA_GRE_REMOTE, &info->remote.in6); + } else if (streq(received_kind, "vti")) { + (void) sd_netlink_message_read_in_addr(m, IFLA_VTI_LOCAL, &info->local.in); + (void) sd_netlink_message_read_in_addr(m, IFLA_VTI_REMOTE, &info->remote.in); + } else if (streq(received_kind, "vti6")) { + (void) sd_netlink_message_read_in6_addr(m, IFLA_VTI_LOCAL, &info->local.in6); + (void) sd_netlink_message_read_in6_addr(m, IFLA_VTI_REMOTE, &info->remote.in6); + } else if (STR_IN_SET(received_kind, "macvlan", "macvtap")) + (void) sd_netlink_message_read_u32(m, IFLA_MACVLAN_MODE, &info->macvlan_mode); + else if (streq(received_kind, "ipvlan")) { + (void) sd_netlink_message_read_u16(m, IFLA_IPVLAN_MODE, &info->ipvlan_mode); + (void) sd_netlink_message_read_u16(m, IFLA_IPVLAN_FLAGS, &info->ipvlan_flags); + } + + strncpy(info->netdev_kind, received_kind, IFNAMSIZ); + + (void) sd_netlink_message_exit_container(m); + (void) sd_netlink_message_exit_container(m); + + return 0; +} + +static int decode_link(sd_netlink_message *m, LinkInfo *info, char **patterns, bool matched_patterns[]) { + _cleanup_strv_free_ char **altnames = NULL; + const char *name, *qdisc; + int ifindex, r; + uint16_t type; + + assert(m); + assert(info); + + r = sd_netlink_message_get_type(m, &type); + if (r < 0) + return r; + + if (type != RTM_NEWLINK) + return 0; + + r = sd_rtnl_message_link_get_ifindex(m, &ifindex); + if (r < 0) + return r; + + r = sd_netlink_message_read_string(m, IFLA_IFNAME, &name); + if (r < 0) + return r; + + r = sd_netlink_message_read_strv(m, IFLA_PROP_LIST, IFLA_ALT_IFNAME, &altnames); + if (r < 0 && r != -ENODATA) + return r; + + if (patterns) { + char str[DECIMAL_STR_MAX(int)]; + size_t pos; + + assert(matched_patterns); + + xsprintf(str, "%i", ifindex); + if (!strv_fnmatch_full(patterns, str, 0, &pos) && + !strv_fnmatch_full(patterns, name, 0, &pos)) { + bool match = false; + char **p; + + STRV_FOREACH(p, altnames) + if (strv_fnmatch_full(patterns, *p, 0, &pos)) { + match = true; + break; + } + if (!match) + return 0; + } + + matched_patterns[pos] = true; + } + + r = sd_rtnl_message_link_get_type(m, &info->iftype); + if (r < 0) + return r; + + strscpy(info->name, sizeof info->name, name); + info->ifindex = ifindex; + info->alternative_names = TAKE_PTR(altnames); + + info->has_mac_address = + netlink_message_read_hw_addr(m, IFLA_ADDRESS, &info->hw_address) >= 0 && + memcmp(&info->hw_address, &HW_ADDR_NULL, sizeof(hw_addr_data)) != 0; + + info->has_permanent_mac_address = + ethtool_get_permanent_macaddr(NULL, info->name, &info->permanent_mac_address) >= 0 && + memcmp(&info->permanent_mac_address, ÐER_ADDR_NULL, sizeof(struct ether_addr)) != 0 && + (info->hw_address.length != sizeof(struct ether_addr) || + memcmp(&info->permanent_mac_address, info->hw_address.addr.bytes, sizeof(struct ether_addr)) != 0); + + (void) sd_netlink_message_read_u32(m, IFLA_MTU, &info->mtu); + (void) sd_netlink_message_read_u32(m, IFLA_MIN_MTU, &info->min_mtu); + (void) sd_netlink_message_read_u32(m, IFLA_MAX_MTU, &info->max_mtu); + + info->has_rx_queues = + sd_netlink_message_read_u32(m, IFLA_NUM_RX_QUEUES, &info->rx_queues) >= 0 && + info->rx_queues > 0; + + info->has_tx_queues = + sd_netlink_message_read_u32(m, IFLA_NUM_TX_QUEUES, &info->tx_queues) >= 0 && + info->tx_queues > 0; + + if (sd_netlink_message_read(m, IFLA_STATS64, sizeof info->stats64, &info->stats64) >= 0) + info->has_stats64 = true; + else if (sd_netlink_message_read(m, IFLA_STATS, sizeof info->stats, &info->stats) >= 0) + info->has_stats = true; + + r = sd_netlink_message_read_string(m, IFLA_QDISC, &qdisc); + if (r >= 0) { + info->qdisc = strdup(qdisc); + if (!info->qdisc) + return log_oom(); + } + + (void) sd_netlink_message_read_u32(m, IFLA_MASTER, &info->master); + + r = sd_netlink_message_enter_container(m, IFLA_AF_SPEC); + if (r >= 0) { + r = sd_netlink_message_enter_container(m, AF_INET6); + if (r >= 0) { + r = sd_netlink_message_read_u8(m, IFLA_INET6_ADDR_GEN_MODE, &info->addr_gen_mode); + if (r >= 0 && IN_SET(info->addr_gen_mode, + IN6_ADDR_GEN_MODE_EUI64, + IN6_ADDR_GEN_MODE_NONE, + IN6_ADDR_GEN_MODE_STABLE_PRIVACY, + IN6_ADDR_GEN_MODE_RANDOM)) + info->has_ipv6_address_generation_mode = true; + + (void) sd_netlink_message_exit_container(m); + } + (void) sd_netlink_message_exit_container(m); + } + + /* fill kind info */ + (void) decode_netdev(m, info); + + return 1; +} + +static int link_get_property( + sd_bus *bus, + const LinkInfo *link, + sd_bus_error *error, + sd_bus_message **reply, + const char *iface, + const char *propname) { + _cleanup_free_ char *path = NULL, *ifindex_str = NULL; + int r; + + if (asprintf(&ifindex_str, "%i", link->ifindex) < 0) + return -ENOMEM; + + r = sd_bus_path_encode("/org/freedesktop/network1/link", ifindex_str, &path); + if (r < 0) + return r; + + return sd_bus_call_method( + bus, + "org.freedesktop.network1", + path, + "org.freedesktop.DBus.Properties", + "Get", + error, + reply, + "ss", + iface, + propname); +} + +static int acquire_link_bitrates(sd_bus *bus, LinkInfo *link) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + r = link_get_property(bus, link, &error, &reply, "org.freedesktop.network1.Link", "BitRates"); + if (r < 0) { + bool quiet = sd_bus_error_has_names(&error, SD_BUS_ERROR_UNKNOWN_PROPERTY, + BUS_ERROR_SPEED_METER_INACTIVE); + + return log_full_errno(quiet ? LOG_DEBUG : LOG_WARNING, + r, "Failed to query link bit rates: %s", bus_error_message(&error, r)); + } + + r = sd_bus_message_enter_container(reply, 'v', "(tt)"); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(reply, "(tt)", &link->tx_bitrate, &link->rx_bitrate); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + link->has_bitrates = link->tx_bitrate != UINT64_MAX && link->rx_bitrate != UINT64_MAX; + + return 0; +} + +static void acquire_ether_link_info(int *fd, LinkInfo *link) { + if (ethtool_get_link_info(fd, link->name, + &link->autonegotiation, + &link->speed, + &link->duplex, + &link->port) >= 0) + link->has_ethtool_link_info = true; +} + +static void acquire_wlan_link_info(LinkInfo *link) { + _cleanup_(sd_netlink_unrefp) sd_netlink *genl = NULL; + const char *type = NULL; + int r, k = 0; + + if (link->sd_device) + (void) sd_device_get_devtype(link->sd_device, &type); + if (!streq_ptr(type, "wlan")) + return; + + r = sd_genl_socket_open(&genl); + if (r < 0) { + log_debug_errno(r, "Failed to open generic netlink socket: %m"); + return; + } + + (void) sd_netlink_inc_rcvbuf(genl, RCVBUF_SIZE); + + r = wifi_get_interface(genl, link->ifindex, &link->wlan_iftype, &link->ssid); + if (r < 0) + log_debug_errno(r, "%s: failed to query ssid: %m", link->name); + + if (link->wlan_iftype == NL80211_IFTYPE_STATION) { + k = wifi_get_station(genl, link->ifindex, &link->bssid); + if (k < 0) + log_debug_errno(k, "%s: failed to query bssid: %m", link->name); + } + + link->has_wlan_link_info = r > 0 || k > 0; +} + +static int acquire_link_info(sd_bus *bus, sd_netlink *rtnl, char **patterns, LinkInfo **ret) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + _cleanup_(link_info_array_freep) LinkInfo *links = NULL; + _cleanup_close_ int fd = -1; + size_t allocated = 0, c = 0, j; + sd_netlink_message *i; + int r; + + assert(rtnl); + assert(ret); + + r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, 0); + if (r < 0) + return rtnl_log_create_error(r); + + r = sd_netlink_message_request_dump(req, true); + if (r < 0) + return rtnl_log_create_error(r); + + r = sd_netlink_call(rtnl, req, 0, &reply); + if (r < 0) + return log_error_errno(r, "Failed to enumerate links: %m"); + + _cleanup_free_ bool *matched_patterns = NULL; + if (patterns) { + matched_patterns = new0(bool, strv_length(patterns)); + if (!matched_patterns) + return log_oom(); + } + + for (i = reply; i; i = sd_netlink_message_next(i)) { + if (!GREEDY_REALLOC0(links, allocated, c + 2)) /* We keep one trailing one as marker */ + return -ENOMEM; + + r = decode_link(i, links + c, patterns, matched_patterns); + if (r < 0) + return r; + if (r == 0) + continue; + + links[c].needs_freeing = true; + + char devid[2 + DECIMAL_STR_MAX(int)]; + xsprintf(devid, "n%i", links[c].ifindex); + (void) sd_device_new_from_device_id(&links[c].sd_device, devid); + + acquire_ether_link_info(&fd, &links[c]); + acquire_wlan_link_info(&links[c]); + + c++; + } + + /* Look if we matched all our arguments that are not globs. It + * is OK for a glob to match nothing, but not for an exact argument. */ + for (size_t pos = 0; pos < strv_length(patterns); pos++) { + if (matched_patterns[pos]) + continue; + + if (string_is_glob(patterns[pos])) + log_debug("Pattern \"%s\" doesn't match any interface, ignoring.", + patterns[pos]); + else + return log_error_errno(SYNTHETIC_ERRNO(ENODEV), + "Interface \"%s\" not found.", patterns[pos]); + } + + typesafe_qsort(links, c, link_info_compare); + + if (bus) + for (j = 0; j < c; j++) + (void) acquire_link_bitrates(bus, links + j); + + *ret = TAKE_PTR(links); + + if (patterns && c == 0) + log_warning("No interfaces matched."); + + return (int) c; +} + +static int list_links(int argc, char *argv[], void *userdata) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(link_info_array_freep) LinkInfo *links = NULL; + _cleanup_(table_unrefp) Table *table = NULL; + TableCell *cell; + int c, i, r; + + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + c = acquire_link_info(NULL, rtnl, argc > 1 ? argv + 1 : NULL, &links); + if (c < 0) + return c; + + (void) pager_open(arg_pager_flags); + + table = table_new("idx", "link", "type", "operational", "setup"); + if (!table) + return log_oom(); + + if (arg_full) + table_set_width(table, 0); + + table_set_header(table, arg_legend); + + assert_se(cell = table_get_cell(table, 0, 0)); + (void) table_set_minimum_width(table, cell, 3); + (void) table_set_weight(table, cell, 0); + (void) table_set_ellipsize_percent(table, cell, 100); + (void) table_set_align_percent(table, cell, 100); + + assert_se(cell = table_get_cell(table, 0, 1)); + (void) table_set_ellipsize_percent(table, cell, 100); + + for (i = 0; i < c; i++) { + _cleanup_free_ char *setup_state = NULL, *operational_state = NULL; + const char *on_color_operational, *off_color_operational, + *on_color_setup, *off_color_setup; + _cleanup_free_ char *t = NULL; + + (void) sd_network_link_get_operational_state(links[i].ifindex, &operational_state); + operational_state_to_color(links[i].name, operational_state, &on_color_operational, &off_color_operational); + + r = sd_network_link_get_setup_state(links[i].ifindex, &setup_state); + if (r == -ENODATA) /* If there's no info available about this iface, it's unmanaged by networkd */ + setup_state = strdup("unmanaged"); + setup_state_to_color(setup_state, &on_color_setup, &off_color_setup); + + t = link_get_type_string(links[i].iftype, links[i].sd_device); + + r = table_add_many(table, + TABLE_INT, links[i].ifindex, + TABLE_STRING, links[i].name, + TABLE_STRING, strna(t), + TABLE_STRING, strna(operational_state), + TABLE_SET_COLOR, on_color_operational, + TABLE_STRING, strna(setup_state), + TABLE_SET_COLOR, on_color_setup); + if (r < 0) + return table_log_add_error(r); + } + + r = table_print(table, NULL); + if (r < 0) + return table_log_print_error(r); + + if (arg_legend) + printf("\n%i links listed.\n", c); + + return 0; +} + +/* IEEE Organizationally Unique Identifier vendor string */ +static int ieee_oui(sd_hwdb *hwdb, const struct ether_addr *mac, char **ret) { + const char *description; + char modalias[STRLEN("OUI:XXYYXXYYXXYY") + 1], *desc; + int r; + + assert(ret); + + if (!hwdb) + return -EINVAL; + + if (!mac) + return -EINVAL; + + /* skip commonly misused 00:00:00 (Xerox) prefix */ + if (memcmp(mac, "\0\0\0", 3) == 0) + return -EINVAL; + + xsprintf(modalias, "OUI:" ETHER_ADDR_FORMAT_STR, + ETHER_ADDR_FORMAT_VAL(*mac)); + + r = sd_hwdb_get(hwdb, modalias, "ID_OUI_FROM_DATABASE", &description); + if (r < 0) + return r; + + desc = strdup(description); + if (!desc) + return -ENOMEM; + + *ret = desc; + + return 0; +} + +static int get_gateway_description( + sd_netlink *rtnl, + sd_hwdb *hwdb, + int ifindex, + int family, + union in_addr_union *gateway, + char **gateway_description) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + sd_netlink_message *m; + int r; + + assert(rtnl); + assert(ifindex >= 0); + assert(IN_SET(family, AF_INET, AF_INET6)); + assert(gateway); + assert(gateway_description); + + r = sd_rtnl_message_new_neigh(rtnl, &req, RTM_GETNEIGH, ifindex, family); + if (r < 0) + return r; + + r = sd_netlink_message_request_dump(req, true); + if (r < 0) + return r; + + r = sd_netlink_call(rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (m = reply; m; m = sd_netlink_message_next(m)) { + union in_addr_union gw = IN_ADDR_NULL; + struct ether_addr mac = ETHER_ADDR_NULL; + uint16_t type; + int ifi, fam; + + r = sd_netlink_message_get_errno(m); + if (r < 0) { + log_error_errno(r, "got error: %m"); + continue; + } + + r = sd_netlink_message_get_type(m, &type); + if (r < 0) { + log_error_errno(r, "could not get type: %m"); + continue; + } + + if (type != RTM_NEWNEIGH) { + log_error("type is not RTM_NEWNEIGH"); + continue; + } + + r = sd_rtnl_message_neigh_get_family(m, &fam); + if (r < 0) { + log_error_errno(r, "could not get family: %m"); + continue; + } + + if (fam != family) { + log_error("family is not correct"); + continue; + } + + r = sd_rtnl_message_neigh_get_ifindex(m, &ifi); + if (r < 0) { + log_error_errno(r, "could not get ifindex: %m"); + continue; + } + + if (ifindex > 0 && ifi != ifindex) + continue; + + switch (fam) { + case AF_INET: + r = sd_netlink_message_read_in_addr(m, NDA_DST, &gw.in); + if (r < 0) + continue; + + break; + case AF_INET6: + r = sd_netlink_message_read_in6_addr(m, NDA_DST, &gw.in6); + if (r < 0) + continue; + + break; + default: + continue; + } + + if (!in_addr_equal(fam, &gw, gateway)) + continue; + + r = sd_netlink_message_read(m, NDA_LLADDR, sizeof(mac), &mac); + if (r < 0) + continue; + + r = ieee_oui(hwdb, &mac, gateway_description); + if (r < 0) + continue; + + return 0; + } + + return -ENODATA; +} + +static int dump_list(Table *table, const char *prefix, char * const *l) { + int r; + + if (strv_isempty(l)) + return 0; + + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, prefix, + TABLE_STRV, l); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_gateways( + sd_netlink *rtnl, + sd_hwdb *hwdb, + Table *table, + int ifindex) { + _cleanup_free_ struct local_address *local = NULL; + _cleanup_strv_free_ char **buf = NULL; + int r, n, i; + + assert(rtnl); + assert(table); + + n = local_gateways(rtnl, ifindex, AF_UNSPEC, &local); + if (n <= 0) + return n; + + for (i = 0; i < n; i++) { + _cleanup_free_ char *gateway = NULL, *description = NULL, *with_description = NULL; + char name[IF_NAMESIZE+1]; + + r = in_addr_to_string(local[i].family, &local[i].address, &gateway); + if (r < 0) + return r; + + r = get_gateway_description(rtnl, hwdb, local[i].ifindex, local[i].family, &local[i].address, &description); + if (r < 0) + log_debug_errno(r, "Could not get description of gateway, ignoring: %m"); + + if (description) { + with_description = strjoin(gateway, " (", description, ")"); + if (!with_description) + return log_oom(); + } + + /* Show interface name for the entry if we show entries for all interfaces */ + r = strv_extendf(&buf, "%s%s%s", + with_description ?: gateway, + ifindex <= 0 ? " on " : "", + ifindex <= 0 ? format_ifname_full(local[i].ifindex, name, FORMAT_IFNAME_IFINDEX_WITH_PERCENT) : ""); + if (r < 0) + return log_oom(); + } + + return dump_list(table, "Gateway:", buf); +} + +static int dump_addresses( + sd_netlink *rtnl, + sd_dhcp_lease *lease, + Table *table, + int ifindex) { + + _cleanup_free_ struct local_address *local = NULL; + _cleanup_strv_free_ char **buf = NULL; + struct in_addr dhcp4_address = {}; + int r, n, i; + + assert(rtnl); + assert(table); + + n = local_addresses(rtnl, ifindex, AF_UNSPEC, &local); + if (n <= 0) + return n; + + if (lease) + (void) sd_dhcp_lease_get_address(lease, &dhcp4_address); + + for (i = 0; i < n; i++) { + _cleanup_free_ char *pretty = NULL; + char name[IF_NAMESIZE+1]; + + r = in_addr_to_string(local[i].family, &local[i].address, &pretty); + if (r < 0) + return r; + + if (local[i].family == AF_INET && in4_addr_equal(&local[i].address.in, &dhcp4_address)) { + struct in_addr server_address; + char *p, s[INET_ADDRSTRLEN]; + + r = sd_dhcp_lease_get_server_identifier(lease, &server_address); + if (r >= 0 && inet_ntop(AF_INET, &server_address, s, sizeof(s))) + p = strjoin(pretty, " (DHCP4 via ", s, ")"); + else + p = strjoin(pretty, " (DHCP4)"); + if (!p) + return log_oom(); + + free_and_replace(pretty, p); + } + + r = strv_extendf(&buf, "%s%s%s", + pretty, + ifindex <= 0 ? " on " : "", + ifindex <= 0 ? format_ifname_full(local[i].ifindex, name, FORMAT_IFNAME_IFINDEX_WITH_PERCENT) : ""); + if (r < 0) + return log_oom(); + } + + return dump_list(table, "Address:", buf); +} + +static int dump_address_labels(sd_netlink *rtnl) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + _cleanup_(table_unrefp) Table *table = NULL; + sd_netlink_message *m; + TableCell *cell; + int r; + + assert(rtnl); + + r = sd_rtnl_message_new_addrlabel(rtnl, &req, RTM_GETADDRLABEL, 0, AF_INET6); + if (r < 0) + return log_error_errno(r, "Could not allocate RTM_GETADDRLABEL message: %m"); + + r = sd_netlink_message_request_dump(req, true); + if (r < 0) + return r; + + r = sd_netlink_call(rtnl, req, 0, &reply); + if (r < 0) + return r; + + table = table_new("label", "prefix/prefixlen"); + if (!table) + return log_oom(); + + if (arg_full) + table_set_width(table, 0); + + r = table_set_sort(table, (size_t) 0, (size_t) SIZE_MAX); + if (r < 0) + return r; + + assert_se(cell = table_get_cell(table, 0, 0)); + (void) table_set_align_percent(table, cell, 100); + (void) table_set_ellipsize_percent(table, cell, 100); + + assert_se(cell = table_get_cell(table, 0, 1)); + (void) table_set_align_percent(table, cell, 100); + + for (m = reply; m; m = sd_netlink_message_next(m)) { + _cleanup_free_ char *pretty = NULL; + union in_addr_union prefix = IN_ADDR_NULL; + uint8_t prefixlen; + uint32_t label; + + r = sd_netlink_message_get_errno(m); + if (r < 0) { + log_error_errno(r, "got error: %m"); + continue; + } + + r = sd_netlink_message_read_u32(m, IFAL_LABEL, &label); + if (r < 0 && r != -ENODATA) { + log_error_errno(r, "Could not read IFAL_LABEL, ignoring: %m"); + continue; + } + + r = sd_netlink_message_read_in6_addr(m, IFAL_ADDRESS, &prefix.in6); + if (r < 0) + continue; + + r = in_addr_to_string(AF_INET6, &prefix, &pretty); + if (r < 0) + continue; + + r = sd_rtnl_message_addrlabel_get_prefixlen(m, &prefixlen); + if (r < 0) + continue; + + r = table_add_cell(table, NULL, TABLE_UINT32, &label); + if (r < 0) + return table_log_add_error(r); + + r = table_add_cell_stringf(table, NULL, "%s/%u", pretty, prefixlen); + if (r < 0) + return table_log_add_error(r); + } + + r = table_print(table, NULL); + if (r < 0) + return table_log_print_error(r); + + return 0; +} + +static int list_address_labels(int argc, char *argv[], void *userdata) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + int r; + + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + dump_address_labels(rtnl); + + return 0; +} + +static int open_lldp_neighbors(int ifindex, FILE **ret) { + _cleanup_free_ char *p = NULL; + FILE *f; + + if (asprintf(&p, "/run/systemd/netif/lldp/%i", ifindex) < 0) + return -ENOMEM; + + f = fopen(p, "re"); + if (!f) + return -errno; + + *ret = f; + return 0; +} + +static int next_lldp_neighbor(FILE *f, sd_lldp_neighbor **ret) { + _cleanup_free_ void *raw = NULL; + size_t l; + le64_t u; + int r; + + assert(f); + assert(ret); + + l = fread(&u, 1, sizeof(u), f); + if (l == 0 && feof(f)) + return 0; + if (l != sizeof(u)) + return -EBADMSG; + + /* each LLDP packet is at most MTU size, but let's allow up to 4KiB just in case */ + if (le64toh(u) >= 4096) + return -EBADMSG; + + raw = new(uint8_t, le64toh(u)); + if (!raw) + return -ENOMEM; + + if (fread(raw, 1, le64toh(u), f) != le64toh(u)) + return -EBADMSG; + + r = sd_lldp_neighbor_from_raw(ret, raw, le64toh(u)); + if (r < 0) + return r; + + return 1; +} + +static int dump_lldp_neighbors(Table *table, const char *prefix, int ifindex) { + _cleanup_strv_free_ char **buf = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(table); + assert(prefix); + assert(ifindex > 0); + + r = open_lldp_neighbors(ifindex, &f); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + for (;;) { + const char *system_name = NULL, *port_id = NULL, *port_description = NULL; + _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL; + + r = next_lldp_neighbor(f, &n); + if (r < 0) + return r; + if (r == 0) + break; + + (void) sd_lldp_neighbor_get_system_name(n, &system_name); + (void) sd_lldp_neighbor_get_port_id_as_string(n, &port_id); + (void) sd_lldp_neighbor_get_port_description(n, &port_description); + + r = strv_extendf(&buf, "%s on port %s%s%s%s", + strna(system_name), + strna(port_id), + isempty(port_description) ? "" : " (", + strempty(port_description), + isempty(port_description) ? "" : ")"); + if (r < 0) + return log_oom(); + } + + return dump_list(table, prefix, buf); +} + +static int dump_dhcp_leases(Table *table, const char *prefix, sd_bus *bus, const LinkInfo *link) { + _cleanup_strv_free_ char **buf = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + r = link_get_property(bus, link, &error, &reply, "org.freedesktop.network1.DHCPServer", "Leases"); + if (r < 0) { + bool quiet = sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_PROPERTY); + + log_full_errno(quiet ? LOG_DEBUG : LOG_WARNING, + r, "Failed to query link DHCP leases: %s", bus_error_message(&error, r)); + return 0; + } + + r = sd_bus_message_enter_container(reply, 'v', "a(uayayayayt)"); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_enter_container(reply, 'a', "(uayayayayt)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_enter_container(reply, 'r', "uayayayayt")) > 0) { + _cleanup_free_ char *id = NULL, *ip = NULL; + const void *client_id, *addr, *gtw, *hwaddr; + size_t client_id_sz, sz; + uint64_t expiration; + uint32_t family; + + r = sd_bus_message_read(reply, "u", &family); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read_array(reply, 'y', &client_id, &client_id_sz); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read_array(reply, 'y', &addr, &sz); + if (r < 0 || sz != 4) + return bus_log_parse_error(r); + + r = sd_bus_message_read_array(reply, 'y', >w, &sz); + if (r < 0 || sz != 4) + return bus_log_parse_error(r); + + r = sd_bus_message_read_array(reply, 'y', &hwaddr, &sz); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read_basic(reply, 't', &expiration); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_dhcp_client_id_to_string(client_id, client_id_sz, &id); + if (r < 0) + return bus_log_parse_error(r); + + r = in_addr_to_string(family, addr, &ip); + if (r < 0) + return bus_log_parse_error(r); + + r = strv_extendf(&buf, "%s (to %s)", ip, id); + if (r < 0) + return log_oom(); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (strv_isempty(buf)) { + r = strv_extendf(&buf, "none"); + if (r < 0) + return log_oom(); + } + + return dump_list(table, prefix, buf); +} + +static int dump_ifindexes(Table *table, const char *prefix, const int *ifindexes) { + unsigned c; + int r; + + assert(prefix); + + if (!ifindexes || ifindexes[0] <= 0) + return 0; + + for (c = 0; ifindexes[c] > 0; c++) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, c == 0 ? prefix : "", + TABLE_IFINDEX, ifindexes[c]); + if (r < 0) + return table_log_add_error(r); + } + + return 0; +} + +#define DUMP_STATS_ONE(name, val_name) \ + r = table_add_many(table, \ + TABLE_EMPTY, \ + TABLE_STRING, name ":"); \ + if (r < 0) \ + return table_log_add_error(r); \ + r = table_add_cell(table, NULL, \ + info->has_stats64 ? TABLE_UINT64 : TABLE_UINT32, \ + info->has_stats64 ? (void*) &info->stats64.val_name : (void*) &info->stats.val_name); \ + if (r < 0) \ + return table_log_add_error(r); + +static int dump_statistics(Table *table, const LinkInfo *info) { + int r; + + if (!arg_stats) + return 0; + + if (!info->has_stats64 && !info->has_stats) + return 0; + + DUMP_STATS_ONE("Rx Packets", rx_packets); + DUMP_STATS_ONE("Tx Packets", tx_packets); + DUMP_STATS_ONE("Rx Bytes", rx_bytes); + DUMP_STATS_ONE("Tx Bytes", tx_bytes); + DUMP_STATS_ONE("Rx Errors", rx_errors); + DUMP_STATS_ONE("Tx Errors", tx_errors); + DUMP_STATS_ONE("Rx Dropped", rx_dropped); + DUMP_STATS_ONE("Tx Dropped", tx_dropped); + DUMP_STATS_ONE("Multicast Packets", multicast); + DUMP_STATS_ONE("Collisions", collisions); + + return 0; +} + +static OutputFlags get_output_flags(void) { + return + arg_all * OUTPUT_SHOW_ALL | + (arg_full || !on_tty() || pager_have()) * OUTPUT_FULL_WIDTH | + colors_enabled() * OUTPUT_COLOR; +} + +static int show_logs(const LinkInfo *info) { + _cleanup_(sd_journal_closep) sd_journal *j = NULL; + int r; + + if (arg_lines == 0) + return 0; + + r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY); + if (r < 0) + return log_error_errno(r, "Failed to open journal: %m"); + + r = add_match_this_boot(j, NULL); + if (r < 0) + return log_error_errno(r, "Failed to add boot matches: %m"); + + if (info) { + char m1[STRLEN("_KERNEL_DEVICE=n") + DECIMAL_STR_MAX(int)]; + const char *m2, *m3; + + /* kernel */ + xsprintf(m1, "_KERNEL_DEVICE=n%i", info->ifindex); + /* networkd */ + m2 = strjoina("INTERFACE=", info->name); + /* udevd */ + m3 = strjoina("DEVICE=", info->name); + + (void)( + (r = sd_journal_add_match(j, m1, 0)) || + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, m2, 0)) || + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, m3, 0)) + ); + if (r < 0) + return log_error_errno(r, "Failed to add link matches: %m"); + } else { + r = add_matches_for_unit(j, "systemd-networkd.service"); + if (r < 0) + return log_error_errno(r, "Failed to add unit matches: %m"); + + r = add_matches_for_unit(j, "systemd-networkd-wait-online.service"); + if (r < 0) + return log_error_errno(r, "Failed to add unit matches: %m"); + } + + return show_journal( + stdout, + j, + OUTPUT_SHORT, + 0, + 0, + arg_lines, + get_output_flags() | OUTPUT_BEGIN_NEWLINE, + NULL); +} + +static int link_status_one( + sd_bus *bus, + sd_netlink *rtnl, + sd_hwdb *hwdb, + const LinkInfo *info) { + + _cleanup_strv_free_ char **dns = NULL, **ntp = NULL, **sip = NULL, **search_domains = NULL, **route_domains = NULL; + _cleanup_free_ char *t = NULL, *network = NULL, *iaid = NULL, *duid = NULL, + *setup_state = NULL, *operational_state = NULL, *lease_file = NULL; + const char *driver = NULL, *path = NULL, *vendor = NULL, *model = NULL, *link = NULL, + *on_color_operational, *off_color_operational, *on_color_setup, *off_color_setup; + _cleanup_free_ int *carrier_bound_to = NULL, *carrier_bound_by = NULL; + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + _cleanup_(table_unrefp) Table *table = NULL; + TableCell *cell; + int r; + + assert(rtnl); + assert(info); + + (void) sd_network_link_get_operational_state(info->ifindex, &operational_state); + operational_state_to_color(info->name, operational_state, &on_color_operational, &off_color_operational); + + r = sd_network_link_get_setup_state(info->ifindex, &setup_state); + if (r == -ENODATA) /* If there's no info available about this iface, it's unmanaged by networkd */ + setup_state = strdup("unmanaged"); + setup_state_to_color(setup_state, &on_color_setup, &off_color_setup); + + (void) sd_network_link_get_dns(info->ifindex, &dns); + (void) sd_network_link_get_search_domains(info->ifindex, &search_domains); + (void) sd_network_link_get_route_domains(info->ifindex, &route_domains); + (void) sd_network_link_get_ntp(info->ifindex, &ntp); + (void) sd_network_link_get_sip(info->ifindex, &sip); + + if (info->sd_device) { + (void) sd_device_get_property_value(info->sd_device, "ID_NET_LINK_FILE", &link); + (void) sd_device_get_property_value(info->sd_device, "ID_NET_DRIVER", &driver); + (void) sd_device_get_property_value(info->sd_device, "ID_PATH", &path); + + if (sd_device_get_property_value(info->sd_device, "ID_VENDOR_FROM_DATABASE", &vendor) < 0) + (void) sd_device_get_property_value(info->sd_device, "ID_VENDOR", &vendor); + + if (sd_device_get_property_value(info->sd_device, "ID_MODEL_FROM_DATABASE", &model) < 0) + (void) sd_device_get_property_value(info->sd_device, "ID_MODEL", &model); + } + + t = link_get_type_string(info->iftype, info->sd_device); + + (void) sd_network_link_get_network_file(info->ifindex, &network); + + (void) sd_network_link_get_carrier_bound_to(info->ifindex, &carrier_bound_to); + (void) sd_network_link_get_carrier_bound_by(info->ifindex, &carrier_bound_by); + + if (asprintf(&lease_file, "/run/systemd/netif/leases/%d", info->ifindex) < 0) + return log_oom(); + + (void) dhcp_lease_load(&lease, lease_file); + + table = table_new("dot", "key", "value"); + if (!table) + return log_oom(); + + if (arg_full) + table_set_width(table, 0); + + assert_se(cell = table_get_cell(table, 0, 0)); + (void) table_set_ellipsize_percent(table, cell, 100); + + assert_se(cell = table_get_cell(table, 0, 1)); + (void) table_set_ellipsize_percent(table, cell, 100); + + table_set_header(table, false); + + r = table_add_many(table, + TABLE_STRING, special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE), + TABLE_SET_COLOR, on_color_operational); + if (r < 0) + return table_log_add_error(r); + r = table_add_cell_stringf(table, &cell, "%i: %s", info->ifindex, info->name); + if (r < 0) + return table_log_add_error(r); + (void) table_set_align_percent(table, cell, 0); + + r = table_add_many(table, + TABLE_EMPTY, + TABLE_EMPTY, + TABLE_STRING, "Link File:", + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_STRING, strna(link), + TABLE_EMPTY, + TABLE_STRING, "Network File:", + TABLE_STRING, strna(network), + TABLE_EMPTY, + TABLE_STRING, "Type:", + TABLE_STRING, strna(t), + TABLE_EMPTY, + TABLE_STRING, "State:"); + if (r < 0) + return table_log_add_error(r); + r = table_add_cell_stringf(table, NULL, "%s%s%s (%s%s%s)", + on_color_operational, strna(operational_state), off_color_operational, + on_color_setup, strna(setup_state), off_color_setup); + if (r < 0) + return table_log_add_error(r); + + strv_sort(info->alternative_names); + r = dump_list(table, "Alternative Names:", info->alternative_names); + if (r < 0) + return r; + + if (path) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Path:", + TABLE_STRING, path); + if (r < 0) + return table_log_add_error(r); + } + if (driver) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Driver:", + TABLE_STRING, driver); + if (r < 0) + return table_log_add_error(r); + } + if (vendor) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Vendor:", + TABLE_STRING, vendor); + if (r < 0) + return table_log_add_error(r); + } + if (model) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Model:", + TABLE_STRING, model); + if (r < 0) + return table_log_add_error(r); + } + + if (info->has_mac_address) { + _cleanup_free_ char *description = NULL; + + if (info->hw_address.length == ETH_ALEN) + (void) ieee_oui(hwdb, &info->hw_address.addr.ether, &description); + + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "HW Address:"); + if (r < 0) + return table_log_add_error(r); + r = table_add_cell_stringf(table, NULL, "%s%s%s%s", + HW_ADDR_TO_STR(&info->hw_address), + description ? " (" : "", + strempty(description), + description ? ")" : ""); + if (r < 0) + return table_log_add_error(r); + } + + if (info->has_permanent_mac_address) { + _cleanup_free_ char *description = NULL; + char ea[ETHER_ADDR_TO_STRING_MAX]; + + (void) ieee_oui(hwdb, &info->permanent_mac_address, &description); + + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "HW Permanent Address:"); + if (r < 0) + return table_log_add_error(r); + r = table_add_cell_stringf(table, NULL, "%s%s%s%s", + ether_addr_to_string(&info->permanent_mac_address, ea), + description ? " (" : "", + strempty(description), + description ? ")" : ""); + if (r < 0) + return table_log_add_error(r); + } + + if (info->mtu > 0) { + char min_str[DECIMAL_STR_MAX(uint32_t)], max_str[DECIMAL_STR_MAX(uint32_t)]; + + xsprintf(min_str, "%" PRIu32, info->min_mtu); + xsprintf(max_str, "%" PRIu32, info->max_mtu); + + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "MTU:"); + if (r < 0) + return table_log_add_error(r); + r = table_add_cell_stringf(table, NULL, "%" PRIu32 "%s%s%s%s%s%s%s", + info->mtu, + info->min_mtu > 0 || info->max_mtu > 0 ? " (" : "", + info->min_mtu > 0 ? "min: " : "", + info->min_mtu > 0 ? min_str : "", + info->min_mtu > 0 && info->max_mtu > 0 ? ", " : "", + info->max_mtu > 0 ? "max: " : "", + info->max_mtu > 0 ? max_str : "", + info->min_mtu > 0 || info->max_mtu > 0 ? ")" : ""); + if (r < 0) + return table_log_add_error(r); + } + + if (info->qdisc) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "QDisc:", + TABLE_STRING, info->qdisc); + if (r < 0) + return table_log_add_error(r); + } + + if (info->master > 0) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Master:", + TABLE_IFINDEX, info->master); + if (r < 0) + return table_log_add_error(r); + } + + if (info->has_ipv6_address_generation_mode) { + static const struct { + const char *mode; + } mode_table[] = { + { "eui64" }, + { "none" }, + { "stable-privacy" }, + { "random" }, + }; + + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "IPv6 Address Generation Mode:", + TABLE_STRING, mode_table[info->addr_gen_mode]); + if (r < 0) + return table_log_add_error(r); + } + + if (streq_ptr(info->netdev_kind, "bridge")) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Forward Delay:", + TABLE_TIMESPAN_MSEC, jiffies_to_usec(info->forward_delay), + TABLE_EMPTY, + TABLE_STRING, "Hello Time:", + TABLE_TIMESPAN_MSEC, jiffies_to_usec(info->hello_time), + TABLE_EMPTY, + TABLE_STRING, "Max Age:", + TABLE_TIMESPAN_MSEC, jiffies_to_usec(info->max_age), + TABLE_EMPTY, + TABLE_STRING, "Ageing Time:", + TABLE_TIMESPAN_MSEC, jiffies_to_usec(info->ageing_time), + TABLE_EMPTY, + TABLE_STRING, "Priority:", + TABLE_UINT16, info->priority, + TABLE_EMPTY, + TABLE_STRING, "STP:", + TABLE_BOOLEAN, info->stp_state > 0, + TABLE_EMPTY, + TABLE_STRING, "Multicast IGMP Version:", + TABLE_UINT8, info->mcast_igmp_version, + TABLE_EMPTY, + TABLE_STRING, "Cost:", + TABLE_UINT32, info->cost); + if (r < 0) + return table_log_add_error(r); + + if (info->port_state <= BR_STATE_BLOCKING) + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Port State:", + TABLE_STRING, bridge_state_to_string(info->port_state)); + } else if (streq_ptr(info->netdev_kind, "bond")) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Mode:", + TABLE_STRING, bond_mode_to_string(info->mode), + TABLE_EMPTY, + TABLE_STRING, "Miimon:", + TABLE_TIMESPAN_MSEC, jiffies_to_usec(info->miimon), + TABLE_EMPTY, + TABLE_STRING, "Updelay:", + TABLE_TIMESPAN_MSEC, jiffies_to_usec(info->updelay), + TABLE_EMPTY, + TABLE_STRING, "Downdelay:", + TABLE_TIMESPAN_MSEC, jiffies_to_usec(info->downdelay)); + if (r < 0) + return table_log_add_error(r); + + } else if (streq_ptr(info->netdev_kind, "vxlan")) { + char ttl[CONST_MAX(STRLEN("auto") + 1, DECIMAL_STR_MAX(uint8_t))]; + + if (info->vxlan_info.vni > 0) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "VNI:", + TABLE_UINT32, info->vxlan_info.vni); + if (r < 0) + return table_log_add_error(r); + } + + if (IN_SET(info->vxlan_info.group_family, AF_INET, AF_INET6)) { + const char *p; + + r = in_addr_is_multicast(info->vxlan_info.group_family, &info->vxlan_info.group); + if (r <= 0) + p = "Remote:"; + else + p = "Group:"; + + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, p, + info->vxlan_info.group_family == AF_INET ? TABLE_IN_ADDR : TABLE_IN6_ADDR, + &info->vxlan_info.group); + if (r < 0) + return table_log_add_error(r); + } + + if (IN_SET(info->vxlan_info.local_family, AF_INET, AF_INET6)) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Local:", + info->vxlan_info.local_family == AF_INET ? TABLE_IN_ADDR : TABLE_IN6_ADDR, + &info->vxlan_info.local); + if (r < 0) + return table_log_add_error(r); + } + + if (info->vxlan_info.dest_port > 0) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Destination Port:", + TABLE_UINT16, be16toh(info->vxlan_info.dest_port)); + if (r < 0) + return table_log_add_error(r); + } + + if (info->vxlan_info.link > 0) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Underlying Device:", + TABLE_IFINDEX, info->vxlan_info.link); + if (r < 0) + return table_log_add_error(r); + } + + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Learning:", + TABLE_BOOLEAN, info->vxlan_info.learning); + if (r < 0) + return table_log_add_error(r); + + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "RSC:", + TABLE_BOOLEAN, info->vxlan_info.rsc); + if (r < 0) + return table_log_add_error(r); + + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "L3MISS:", + TABLE_BOOLEAN, info->vxlan_info.l3miss); + if (r < 0) + return table_log_add_error(r); + + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "L2MISS:", + TABLE_BOOLEAN, info->vxlan_info.l2miss); + if (r < 0) + return table_log_add_error(r); + + if (info->vxlan_info.tos > 1) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "TOS:", + TABLE_UINT8, info->vxlan_info.tos); + if (r < 0) + return table_log_add_error(r); + } + + if (info->vxlan_info.ttl > 0) + xsprintf(ttl, "%" PRIu8, info->vxlan_info.ttl); + else + strcpy(ttl, "auto"); + + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "TTL:", + TABLE_STRING, ttl); + if (r < 0) + return table_log_add_error(r); + } else if (streq_ptr(info->netdev_kind, "vlan") && info->vlan_id > 0) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "VLan Id:", + TABLE_UINT16, info->vlan_id); + if (r < 0) + return table_log_add_error(r); + } else if (STRPTR_IN_SET(info->netdev_kind, "ipip", "sit", "gre", "gretap", "erspan", "vti")) { + if (!in_addr_is_null(AF_INET, &info->local)) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Local:", + TABLE_IN_ADDR, &info->local); + if (r < 0) + return table_log_add_error(r); + } + + if (!in_addr_is_null(AF_INET, &info->remote)) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Remote:", + TABLE_IN_ADDR, &info->remote); + if (r < 0) + return table_log_add_error(r); + } + } else if (STRPTR_IN_SET(info->netdev_kind, "ip6gre", "ip6gretap", "ip6erspan", "vti6")) { + if (!in_addr_is_null(AF_INET6, &info->local)) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Local:", + TABLE_IN6_ADDR, &info->local); + if (r < 0) + return table_log_add_error(r); + } + + if (!in_addr_is_null(AF_INET6, &info->remote)) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Remote:", + TABLE_IN6_ADDR, &info->remote); + if (r < 0) + return table_log_add_error(r); + } + } else if (streq_ptr(info->netdev_kind, "geneve")) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "VNI:", + TABLE_UINT32, info->vni); + if (r < 0) + return table_log_add_error(r); + + if (info->has_tunnel_ipv4 && !in_addr_is_null(AF_INET, &info->remote)) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Remote:", + TABLE_IN_ADDR, &info->remote); + if (r < 0) + return table_log_add_error(r); + } else if (!in_addr_is_null(AF_INET6, &info->remote)) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Remote:", + TABLE_IN6_ADDR, &info->remote); + if (r < 0) + return table_log_add_error(r); + } + + if (info->ttl > 0) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "TTL:", + TABLE_UINT8, info->ttl); + if (r < 0) + return table_log_add_error(r); + } + + if (info->tos > 0) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "TOS:", + TABLE_UINT8, info->tos); + if (r < 0) + return table_log_add_error(r); + } + + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Port:", + TABLE_UINT16, info->tunnel_port); + if (r < 0) + return table_log_add_error(r); + + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Inherit:", + TABLE_STRING, geneve_df_to_string(info->inherit)); + if (r < 0) + return table_log_add_error(r); + + if (info->df > 0) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "IPDoNotFragment:", + TABLE_UINT8, info->df); + if (r < 0) + return table_log_add_error(r); + } + + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "UDPChecksum:", + TABLE_BOOLEAN, info->csum); + if (r < 0) + return table_log_add_error(r); + + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "UDP6ZeroChecksumTx:", + TABLE_BOOLEAN, info->csum6_tx); + if (r < 0) + return table_log_add_error(r); + + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "UDP6ZeroChecksumRx:", + TABLE_BOOLEAN, info->csum6_rx); + if (r < 0) + return table_log_add_error(r); + + if (info->label > 0) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "FlowLabel:", + TABLE_UINT32, info->label); + if (r < 0) + return table_log_add_error(r); + } + } else if (STRPTR_IN_SET(info->netdev_kind, "macvlan", "macvtap")) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Mode:", + TABLE_STRING, macvlan_mode_to_string(info->macvlan_mode)); + if (r < 0) + return table_log_add_error(r); + } else if (streq_ptr(info->netdev_kind, "ipvlan")) { + _cleanup_free_ char *p = NULL, *s = NULL; + + if (info->ipvlan_flags & IPVLAN_F_PRIVATE) + p = strdup("private"); + else if (info->ipvlan_flags & IPVLAN_F_VEPA) + p = strdup("vepa"); + else + p = strdup("bridge"); + if (!p) + log_oom(); + + s = strjoin(ipvlan_mode_to_string(info->ipvlan_mode), " (", p, ")"); + if (!s) + return log_oom(); + + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Mode:", + TABLE_STRING, s); + if (r < 0) + return table_log_add_error(r); + } + + if (info->has_wlan_link_info) { + _cleanup_free_ char *esc = NULL; + char buf[ETHER_ADDR_TO_STRING_MAX]; + + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "WiFi access point:"); + if (r < 0) + return table_log_add_error(r); + + if (info->ssid) + esc = cescape(info->ssid); + + r = table_add_cell_stringf(table, NULL, "%s (%s)", + strnull(esc), + ether_addr_to_string(&info->bssid, buf)); + if (r < 0) + return table_log_add_error(r); + } + + if (info->has_bitrates) { + char tx[FORMAT_BYTES_MAX], rx[FORMAT_BYTES_MAX]; + + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Bit Rate (Tx/Rx):"); + if (r < 0) + return table_log_add_error(r); + r = table_add_cell_stringf(table, NULL, "%sbps/%sbps", + format_bytes_full(tx, sizeof tx, info->tx_bitrate, 0), + format_bytes_full(rx, sizeof rx, info->rx_bitrate, 0)); + if (r < 0) + return table_log_add_error(r); + } + + if (info->has_tx_queues || info->has_rx_queues) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Queue Length (Tx/Rx):"); + if (r < 0) + return table_log_add_error(r); + r = table_add_cell_stringf(table, NULL, "%" PRIu32 "/%" PRIu32, info->tx_queues, info->rx_queues); + if (r < 0) + return table_log_add_error(r); + } + + if (info->has_ethtool_link_info) { + const char *duplex = duplex_to_string(info->duplex); + const char *port = port_to_string(info->port); + + if (IN_SET(info->autonegotiation, AUTONEG_DISABLE, AUTONEG_ENABLE)) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Auto negotiation:", + TABLE_BOOLEAN, info->autonegotiation == AUTONEG_ENABLE); + if (r < 0) + return table_log_add_error(r); + } + + if (info->speed > 0) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Speed:", + TABLE_BPS, info->speed); + if (r < 0) + return table_log_add_error(r); + } + + if (duplex) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Duplex:", + TABLE_STRING, duplex); + if (r < 0) + return table_log_add_error(r); + } + + if (port) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Port:", + TABLE_STRING, port); + if (r < 0) + return table_log_add_error(r); + } + } + + r = dump_addresses(rtnl, lease, table, info->ifindex); + if (r < 0) + return r; + r = dump_gateways(rtnl, hwdb, table, info->ifindex); + if (r < 0) + return r; + r = dump_list(table, "DNS:", dns); + if (r < 0) + return r; + r = dump_list(table, "Search Domains:", search_domains); + if (r < 0) + return r; + r = dump_list(table, "Route Domains:", route_domains); + if (r < 0) + return r; + r = dump_list(table, "NTP:", ntp); + if (r < 0) + return r; + r = dump_list(table, "SIP:", sip); + if (r < 0) + return r; + r = dump_ifindexes(table, "Carrier Bound To:", carrier_bound_to); + if (r < 0) + return r; + r = dump_ifindexes(table, "Carrier Bound By:", carrier_bound_by); + if (r < 0) + return r; + + if (lease) { + const void *client_id; + size_t client_id_len; + const char *tz; + + r = sd_dhcp_lease_get_timezone(lease, &tz); + if (r >= 0) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "Time Zone:", + TABLE_STRING, tz); + if (r < 0) + return table_log_add_error(r); + } + + r = sd_dhcp_lease_get_client_id(lease, &client_id, &client_id_len); + if (r >= 0) { + _cleanup_free_ char *id = NULL; + + r = sd_dhcp_client_id_to_string(client_id, client_id_len, &id); + if (r >= 0) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "DHCP4 Client ID:", + TABLE_STRING, id); + if (r < 0) + return table_log_add_error(r); + } + } + } + + r = sd_network_link_get_dhcp6_client_iaid_string(info->ifindex, &iaid); + if (r >= 0) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "DHCP6 Client IAID:", + TABLE_STRING, iaid); + if (r < 0) + return table_log_add_error(r); + } + + r = sd_network_link_get_dhcp6_client_duid_string(info->ifindex, &duid); + if (r >= 0) { + r = table_add_many(table, + TABLE_EMPTY, + TABLE_STRING, "DHCP6 Client DUID:", + TABLE_STRING, duid); + if (r < 0) + return table_log_add_error(r); + } + + r = dump_lldp_neighbors(table, "Connected To:", info->ifindex); + if (r < 0) + return r; + + r = dump_dhcp_leases(table, "Offered DHCP leases:", bus, info); + if (r < 0) + return r; + + r = dump_statistics(table, info); + if (r < 0) + return r; + + r = table_print(table, NULL); + if (r < 0) + return table_log_print_error(r); + + return show_logs(info); +} + +static int system_status(sd_netlink *rtnl, sd_hwdb *hwdb) { + _cleanup_free_ char *operational_state = NULL; + _cleanup_strv_free_ char **dns = NULL, **ntp = NULL, **search_domains = NULL, **route_domains = NULL; + const char *on_color_operational, *off_color_operational; + _cleanup_(table_unrefp) Table *table = NULL; + TableCell *cell; + int r; + + assert(rtnl); + + (void) sd_network_get_operational_state(&operational_state); + operational_state_to_color(NULL, operational_state, &on_color_operational, &off_color_operational); + + table = table_new("dot", "key", "value"); + if (!table) + return log_oom(); + + if (arg_full) + table_set_width(table, 0); + + assert_se(cell = table_get_cell(table, 0, 0)); + (void) table_set_ellipsize_percent(table, cell, 100); + + assert_se(cell = table_get_cell(table, 0, 1)); + (void) table_set_align_percent(table, cell, 100); + (void) table_set_ellipsize_percent(table, cell, 100); + + table_set_header(table, false); + + r = table_add_many(table, + TABLE_STRING, special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE), + TABLE_SET_COLOR, on_color_operational, + TABLE_STRING, "State:", + TABLE_STRING, strna(operational_state), + TABLE_SET_COLOR, on_color_operational); + if (r < 0) + return table_log_add_error(r); + + r = dump_addresses(rtnl, NULL, table, 0); + if (r < 0) + return r; + r = dump_gateways(rtnl, hwdb, table, 0); + if (r < 0) + return r; + + (void) sd_network_get_dns(&dns); + r = dump_list(table, "DNS:", dns); + if (r < 0) + return r; + + (void) sd_network_get_search_domains(&search_domains); + r = dump_list(table, "Search Domains:", search_domains); + if (r < 0) + return r; + + (void) sd_network_get_route_domains(&route_domains); + r = dump_list(table, "Route Domains:", route_domains); + if (r < 0) + return r; + + (void) sd_network_get_ntp(&ntp); + r = dump_list(table, "NTP:", ntp); + if (r < 0) + return r; + + r = table_print(table, NULL); + if (r < 0) + return table_log_print_error(r); + + return show_logs(NULL); +} + +static int link_status(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + _cleanup_(link_info_array_freep) LinkInfo *links = NULL; + int r, c, i; + + (void) pager_open(arg_pager_flags); + + r = sd_bus_open_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to connect system bus: %m"); + + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + r = sd_hwdb_new(&hwdb); + if (r < 0) + log_debug_errno(r, "Failed to open hardware database: %m"); + + if (arg_all) + c = acquire_link_info(bus, rtnl, NULL, &links); + else if (argc <= 1) + return system_status(rtnl, hwdb); + else + c = acquire_link_info(bus, rtnl, argv + 1, &links); + if (c < 0) + return c; + + for (i = 0; i < c; i++) { + if (i > 0) + fputc('\n', stdout); + + link_status_one(bus, rtnl, hwdb, links + i); + } + + return 0; +} + +static char *lldp_capabilities_to_string(uint16_t x) { + static const char characters[] = { + 'o', 'p', 'b', 'w', 'r', 't', 'd', 'a', 'c', 's', 'm', + }; + char *ret; + unsigned i; + + ret = new(char, ELEMENTSOF(characters) + 1); + if (!ret) + return NULL; + + for (i = 0; i < ELEMENTSOF(characters); i++) + ret[i] = (x & (1U << i)) ? characters[i] : '.'; + + ret[i] = 0; + return ret; +} + +static void lldp_capabilities_legend(uint16_t x) { + unsigned w, i, cols = columns(); + static const char* const table[] = { + "o - Other", + "p - Repeater", + "b - Bridge", + "w - WLAN Access Point", + "r - Router", + "t - Telephone", + "d - DOCSIS cable device", + "a - Station", + "c - Customer VLAN", + "s - Service VLAN", + "m - Two-port MAC Relay (TPMR)", + }; + + if (x == 0) + return; + + printf("\nCapability Flags:\n"); + for (w = 0, i = 0; i < ELEMENTSOF(table); i++) + if (x & (1U << i) || arg_all) { + bool newline; + + newline = w + strlen(table[i]) + (w == 0 ? 0 : 2) > cols; + if (newline) + w = 0; + w += printf("%s%s%s", newline ? "\n" : "", w == 0 ? "" : "; ", table[i]); + } + puts(""); +} + +static int link_lldp_status(int argc, char *argv[], void *userdata) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(link_info_array_freep) LinkInfo *links = NULL; + _cleanup_(table_unrefp) Table *table = NULL; + int i, r, c, m = 0; + uint16_t all = 0; + TableCell *cell; + + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + c = acquire_link_info(NULL, rtnl, argc > 1 ? argv + 1 : NULL, &links); + if (c < 0) + return c; + + (void) pager_open(arg_pager_flags); + + table = table_new("link", + "chassis id", + "system name", + "caps", + "port id", + "port description"); + if (!table) + return log_oom(); + + if (arg_full) + table_set_width(table, 0); + + table_set_header(table, arg_legend); + + assert_se(cell = table_get_cell(table, 0, 0)); + table_set_minimum_width(table, cell, 16); + + assert_se(cell = table_get_cell(table, 0, 1)); + table_set_minimum_width(table, cell, 17); + + assert_se(cell = table_get_cell(table, 0, 2)); + table_set_minimum_width(table, cell, 16); + + assert_se(cell = table_get_cell(table, 0, 3)); + table_set_minimum_width(table, cell, 11); + + assert_se(cell = table_get_cell(table, 0, 4)); + table_set_minimum_width(table, cell, 17); + + assert_se(cell = table_get_cell(table, 0, 5)); + table_set_minimum_width(table, cell, 16); + + for (i = 0; i < c; i++) { + _cleanup_fclose_ FILE *f = NULL; + + r = open_lldp_neighbors(links[i].ifindex, &f); + if (r == -ENOENT) + continue; + if (r < 0) { + log_warning_errno(r, "Failed to open LLDP data for %i, ignoring: %m", links[i].ifindex); + continue; + } + + for (;;) { + _cleanup_free_ char *cid = NULL, *pid = NULL, *sname = NULL, *pdesc = NULL, *capabilities = NULL; + const char *chassis_id = NULL, *port_id = NULL, *system_name = NULL, *port_description = NULL; + _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL; + uint16_t cc; + + r = next_lldp_neighbor(f, &n); + if (r < 0) { + log_warning_errno(r, "Failed to read neighbor data: %m"); + break; + } + if (r == 0) + break; + + (void) sd_lldp_neighbor_get_chassis_id_as_string(n, &chassis_id); + (void) sd_lldp_neighbor_get_port_id_as_string(n, &port_id); + (void) sd_lldp_neighbor_get_system_name(n, &system_name); + (void) sd_lldp_neighbor_get_port_description(n, &port_description); + + if (chassis_id) { + cid = ellipsize(chassis_id, 17, 100); + if (cid) + chassis_id = cid; + } + + if (port_id) { + pid = ellipsize(port_id, 17, 100); + if (pid) + port_id = pid; + } + + if (system_name) { + sname = ellipsize(system_name, 16, 100); + if (sname) + system_name = sname; + } + + if (port_description) { + pdesc = ellipsize(port_description, 16, 100); + if (pdesc) + port_description = pdesc; + } + + if (sd_lldp_neighbor_get_enabled_capabilities(n, &cc) >= 0) { + capabilities = lldp_capabilities_to_string(cc); + all |= cc; + } + + r = table_add_many(table, + TABLE_STRING, links[i].name, + TABLE_STRING, strna(chassis_id), + TABLE_STRING, strna(system_name), + TABLE_STRING, strna(capabilities), + TABLE_STRING, strna(port_id), + TABLE_STRING, strna(port_description)); + if (r < 0) + return table_log_add_error(r); + + m++; + } + } + + r = table_print(table, NULL); + if (r < 0) + return table_log_print_error(r); + + if (arg_legend) { + lldp_capabilities_legend(all); + printf("\n%i neighbors listed.\n", m); + } + + return 0; +} + +static int link_delete_send_message(sd_netlink *rtnl, int index) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(rtnl); + + r = sd_rtnl_message_new_link(rtnl, &req, RTM_DELLINK, index); + if (r < 0) + return rtnl_log_create_error(r); + + r = sd_netlink_call(rtnl, req, 0, NULL); + if (r < 0) + return r; + + return 0; +} + +static int link_up_down_send_message(sd_netlink *rtnl, char *command, int index) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(rtnl); + + r = sd_rtnl_message_new_link(rtnl, &req, RTM_SETLINK, index); + if (r < 0) + return rtnl_log_create_error(r); + + if (streq(command, "up")) + r = sd_rtnl_message_link_set_flags(req, IFF_UP, IFF_UP); + else + r = sd_rtnl_message_link_set_flags(req, 0, IFF_UP); + if (r < 0) + return log_error_errno(r, "Could not set link flags: %m"); + + r = sd_netlink_call(rtnl, req, 0, NULL); + if (r < 0) + return r; + + return 0; +} + +static int link_up_down(int argc, char *argv[], void *userdata) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_set_free_ Set *indexes = NULL; + int index, r, i; + void *p; + + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + indexes = set_new(NULL); + if (!indexes) + return log_oom(); + + for (i = 1; i < argc; i++) { + index = resolve_interface_or_warn(&rtnl, argv[i]); + if (index < 0) + return index; + + r = set_put(indexes, INT_TO_PTR(index)); + if (r < 0) + return log_oom(); + } + + SET_FOREACH(p, indexes) { + index = PTR_TO_INT(p); + r = link_up_down_send_message(rtnl, argv[0], index); + if (r < 0) { + char ifname[IF_NAMESIZE + 1]; + + return log_error_errno(r, "Failed to %s interface %s: %m", + argv[1], format_ifname_full(index, ifname, FORMAT_IFNAME_IFINDEX)); + } + } + + return r; +} + +static int link_delete(int argc, char *argv[], void *userdata) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_set_free_ Set *indexes = NULL; + int index, r, i; + void *p; + + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + indexes = set_new(NULL); + if (!indexes) + return log_oom(); + + for (i = 1; i < argc; i++) { + index = resolve_interface_or_warn(&rtnl, argv[i]); + if (index < 0) + return index; + + r = set_put(indexes, INT_TO_PTR(index)); + if (r < 0) + return log_oom(); + } + + SET_FOREACH(p, indexes) { + index = PTR_TO_INT(p); + r = link_delete_send_message(rtnl, index); + if (r < 0) { + char ifname[IF_NAMESIZE + 1]; + + return log_error_errno(r, "Failed to delete interface %s: %m", + format_ifname_full(index, ifname, FORMAT_IFNAME_IFINDEX)); + } + } + + return r; +} + +static int link_renew_one(sd_bus *bus, int index, const char *name) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + r = bus_call_method(bus, bus_network_mgr, "RenewLink", &error, NULL, "i", index); + if (r < 0) + return log_error_errno(r, "Failed to renew dynamic configuration of interface %s: %s", + name, bus_error_message(&error, r)); + + return 0; +} + +static int link_renew(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + int index, i, k = 0, r; + + r = sd_bus_open_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to connect system bus: %m"); + + for (i = 1; i < argc; i++) { + index = resolve_interface_or_warn(&rtnl, argv[i]); + if (index < 0) + return index; + + r = link_renew_one(bus, index, argv[i]); + if (r < 0 && k >= 0) + k = r; + } + + return k; +} + +static int link_force_renew_one(sd_bus *bus, int index, const char *name) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + r = bus_call_method(bus, bus_network_mgr, "ForceRenewLink", &error, NULL, "i", index); + if (r < 0) + return log_error_errno(r, "Failed to force renew dynamic configuration of interface %s: %s", + name, bus_error_message(&error, r)); + + return 0; +} + +static int link_force_renew(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + int index, i, k = 0, r; + + r = sd_bus_open_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to connect system bus: %m"); + + for (i = 1; i < argc; i++) { + index = resolve_interface_or_warn(&rtnl, argv[i]); + if (index < 0) + return index; + + r = link_force_renew_one(bus, index, argv[i]); + if (r < 0 && k >= 0) + k = r; + } + + return k; +} + +static int verb_reload(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + r = sd_bus_open_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to connect system bus: %m"); + + r = bus_call_method(bus, bus_network_mgr, "Reload", &error, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to reload network settings: %m"); + + return 0; +} + +static int verb_reconfigure(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_set_free_ Set *indexes = NULL; + int index, i, r; + void *p; + + r = sd_bus_open_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to connect system bus: %m"); + + indexes = set_new(NULL); + if (!indexes) + return log_oom(); + + for (i = 1; i < argc; i++) { + index = resolve_interface_or_warn(&rtnl, argv[i]); + if (index < 0) + return index; + + r = set_put(indexes, INT_TO_PTR(index)); + if (r < 0) + return log_oom(); + } + + SET_FOREACH(p, indexes) { + index = PTR_TO_INT(p); + r = bus_call_method(bus, bus_network_mgr, "ReconfigureLink", &error, NULL, "i", index); + if (r < 0) { + char ifname[IF_NAMESIZE + 1]; + + return log_error_errno(r, "Failed to reconfigure network interface %s: %m", + format_ifname_full(index, ifname, FORMAT_IFNAME_IFINDEX)); + } + } + + return 0; +} + +static int help(void) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("networkctl", "1", &link); + if (r < 0) + return log_oom(); + + printf("%s [OPTIONS...] COMMAND\n\n" + "%sQuery and control the networking subsystem.%s\n" + "\nCommands:\n" + " list [PATTERN...] List links\n" + " status [PATTERN...] Show link status\n" + " lldp [PATTERN...] Show LLDP neighbors\n" + " label Show current address label entries in the kernel\n" + " delete DEVICES... Delete virtual netdevs\n" + " up DEVICES... Bring devices up\n" + " down DEVICES... Bring devices down\n" + " renew DEVICES... Renew dynamic configurations\n" + " forcerenew DEVICES... Trigger DHCP reconfiguration of all connected clients\n" + " reconfigure DEVICES... Reconfigure interfaces\n" + " reload Reload .network and .netdev files\n" + "\nOptions:\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " --no-legend Do not show the headers and footers\n" + " -a --all Show status for all links\n" + " -s --stats Show detailed link statics\n" + " -l --full Do not ellipsize output\n" + " -n --lines=INTEGER Number of journal entries to show\n" + "\nSee the %s for details.\n" + , program_invocation_short_name + , ansi_highlight() + , ansi_normal() + , link + ); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER, + ARG_NO_LEGEND, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, + { "all", no_argument, NULL, 'a' }, + { "stats", no_argument, NULL, 's' }, + { "full", no_argument, NULL, 'l' }, + { "lines", required_argument, NULL, 'n' }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hasln:", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + return help(); + + case ARG_VERSION: + return version(); + + case ARG_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; + + case ARG_NO_LEGEND: + arg_legend = false; + break; + + case 'a': + arg_all = true; + break; + + case 's': + arg_stats = true; + break; + + case 'l': + arg_full = true; + break; + + case 'n': + if (safe_atou(optarg, &arg_lines) < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse lines '%s'", optarg); + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + } + + return 1; +} + +static int networkctl_main(int argc, char *argv[]) { + static const Verb verbs[] = { + { "list", VERB_ANY, VERB_ANY, VERB_DEFAULT, list_links }, + { "status", VERB_ANY, VERB_ANY, 0, link_status }, + { "lldp", VERB_ANY, VERB_ANY, 0, link_lldp_status }, + { "label", 1, 1, 0, list_address_labels }, + { "delete", 2, VERB_ANY, 0, link_delete }, + { "up", 2, VERB_ANY, 0, link_up_down }, + { "down", 2, VERB_ANY, 0, link_up_down }, + { "renew", 2, VERB_ANY, 0, link_renew }, + { "forcerenew", 2, VERB_ANY, 0, link_force_renew }, + { "reconfigure", 2, VERB_ANY, 0, verb_reconfigure }, + { "reload", 1, 1, 0, verb_reload }, + {} + }; + + return dispatch_verb(argc, argv, verbs, NULL); +} + +static void warn_networkd_missing(void) { + + if (access("/run/systemd/netif/state", F_OK) >= 0) + return; + + fprintf(stderr, "WARNING: systemd-networkd is not running, output will be incomplete.\n\n"); +} + +static int run(int argc, char* argv[]) { + int r; + + log_setup_cli(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + warn_networkd_missing(); + + return networkctl_main(argc, argv); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/network/networkd-address-label.c b/src/network/networkd-address-label.c new file mode 100644 index 0000000..f933a1d --- /dev/null +++ b/src/network/networkd-address-label.c @@ -0,0 +1,242 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <net/if.h> +#include <linux/if_addrlabel.h> + +#include "alloc-util.h" +#include "netlink-util.h" +#include "networkd-address-label.h" +#include "networkd-link.h" +#include "networkd-manager.h" +#include "networkd-network.h" +#include "parse-util.h" + +AddressLabel *address_label_free(AddressLabel *label) { + if (!label) + return NULL; + + if (label->network) { + assert(label->section); + hashmap_remove(label->network->address_labels_by_section, label->section); + } + + network_config_section_free(label->section); + return mfree(label); +} + +DEFINE_NETWORK_SECTION_FUNCTIONS(AddressLabel, address_label_free); + +static int address_label_new_static(Network *network, const char *filename, unsigned section_line, AddressLabel **ret) { + _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; + _cleanup_(address_label_freep) AddressLabel *label = NULL; + int r; + + assert(network); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = network_config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + label = hashmap_get(network->address_labels_by_section, n); + if (label) { + *ret = TAKE_PTR(label); + return 0; + } + + label = new(AddressLabel, 1); + if (!label) + return -ENOMEM; + + *label = (AddressLabel) { + .network = network, + .section = TAKE_PTR(n), + }; + + r = hashmap_ensure_allocated(&network->address_labels_by_section, &network_config_hash_ops); + if (r < 0) + return r; + + r = hashmap_put(network->address_labels_by_section, label->section, label); + if (r < 0) + return r; + + *ret = TAKE_PTR(label); + return 0; +} + +static int address_label_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(rtnl); + assert(m); + assert(link); + assert(link->ifname); + assert(link->address_label_messages > 0); + + link->address_label_messages--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EEXIST) { + log_link_message_warning_errno(link, m, r, "Could not set address label"); + link_enter_failed(link); + return 1; + } else if (r >= 0) + (void) manager_rtnl_process_address(rtnl, m, link->manager); + + if (link->address_label_messages == 0) + log_link_debug(link, "Addresses label set"); + + return 1; +} + +static int address_label_configure(AddressLabel *label, Link *link) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(label); + assert(link); + assert(link->ifindex > 0); + assert(link->manager); + assert(link->manager->rtnl); + + r = sd_rtnl_message_new_addrlabel(link->manager->rtnl, &req, RTM_NEWADDRLABEL, + link->ifindex, AF_INET6); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate RTM_NEWADDR message: %m"); + + r = sd_rtnl_message_addrlabel_set_prefixlen(req, label->prefixlen); + if (r < 0) + return log_link_error_errno(link, r, "Could not set prefixlen: %m"); + + r = sd_netlink_message_append_u32(req, IFAL_LABEL, label->label); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFAL_LABEL attribute: %m"); + + r = sd_netlink_message_append_in6_addr(req, IFA_ADDRESS, &label->in_addr.in6); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFA_ADDRESS attribute: %m"); + + r = netlink_call_async(link->manager->rtnl, NULL, req, + address_label_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + return 0; +} + +int link_set_address_labels(Link *link) { + AddressLabel *label; + int r; + + assert(link); + assert(link->network); + + HASHMAP_FOREACH(label, link->network->address_labels_by_section) { + r = address_label_configure(label, link); + if (r < 0) + return log_link_warning_errno(link, r, "Could not set address label: %m"); + + link->address_label_messages++; + } + + return 0; +} + +void network_drop_invalid_address_labels(Network *network) { + AddressLabel *label; + + assert(network); + + HASHMAP_FOREACH(label, network->address_labels_by_section) + if (section_is_invalid(label->section)) + address_label_free(label); +} + +int config_parse_address_label_prefix(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(address_label_free_or_set_invalidp) AddressLabel *n = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = address_label_new_static(network, filename, section_line, &n); + if (r < 0) + return log_oom(); + + r = in_addr_prefix_from_string(rvalue, AF_INET6, &n->in_addr, &n->prefixlen); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Address label is invalid, ignoring assignment: %s", rvalue); + return 0; + } + + n = NULL; + + return 0; +} + +int config_parse_address_label( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(address_label_free_or_set_invalidp) AddressLabel *n = NULL; + Network *network = userdata; + uint32_t k; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = address_label_new_static(network, filename, section_line, &n); + if (r < 0) + return log_oom(); + + r = safe_atou32(rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse address label, ignoring: %s", rvalue); + return 0; + } + + if (k == 0xffffffffUL) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Address label is invalid, ignoring: %s", rvalue); + return 0; + } + + n->label = k; + n = NULL; + + return 0; +} diff --git a/src/network/networkd-address-label.h b/src/network/networkd-address-label.h new file mode 100644 index 0000000..11fdd9a --- /dev/null +++ b/src/network/networkd-address-label.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <inttypes.h> + +#include "conf-parser.h" +#include "in-addr-util.h" +#include "networkd-util.h" + +typedef struct Network Network; +typedef struct Link Link; + +typedef struct AddressLabel { + Network *network; + NetworkConfigSection *section; + + unsigned char prefixlen; + uint32_t label; + union in_addr_union in_addr; +} AddressLabel; + +AddressLabel *address_label_free(AddressLabel *label); + +void network_drop_invalid_address_labels(Network *network); + +int link_set_address_labels(Link *link); + +CONFIG_PARSER_PROTOTYPE(config_parse_address_label); +CONFIG_PARSER_PROTOTYPE(config_parse_address_label_prefix); diff --git a/src/network/networkd-address-pool.c b/src/network/networkd-address-pool.c new file mode 100644 index 0000000..7e27db6 --- /dev/null +++ b/src/network/networkd-address-pool.c @@ -0,0 +1,190 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "networkd-address-pool.h" +#include "networkd-address.h" +#include "networkd-manager.h" +#include "set.h" +#include "string-util.h" + +#define RANDOM_PREFIX_TRIAL_MAX 1024 + +static int address_pool_new( + Manager *m, + int family, + const union in_addr_union *u, + unsigned prefixlen) { + + _cleanup_free_ AddressPool *p = NULL; + int r; + + assert(m); + assert(u); + + p = new(AddressPool, 1); + if (!p) + return -ENOMEM; + + *p = (AddressPool) { + .manager = m, + .family = family, + .prefixlen = prefixlen, + .in_addr = *u, + }; + + r = ordered_set_ensure_put(&m->address_pools, NULL, p); + if (r < 0) + return r; + + TAKE_PTR(p); + return 0; +} + +static int address_pool_new_from_string( + Manager *m, + int family, + const char *p, + unsigned prefixlen) { + + union in_addr_union u; + int r; + + assert(m); + assert(p); + + r = in_addr_from_string(family, p, &u); + if (r < 0) + return r; + + return address_pool_new(m, family, &u, prefixlen); +} + +int address_pool_setup_default(Manager *m) { + int r; + + assert(m); + + /* Add in the well-known private address ranges. */ + r = address_pool_new_from_string(m, AF_INET6, "fd00::", 8); + if (r < 0) + return r; + + r = address_pool_new_from_string(m, AF_INET, "192.168.0.0", 16); + if (r < 0) + return r; + + r = address_pool_new_from_string(m, AF_INET, "172.16.0.0", 12); + if (r < 0) + return r; + + r = address_pool_new_from_string(m, AF_INET, "10.0.0.0", 8); + if (r < 0) + return r; + + return 0; +} + +static bool address_pool_prefix_is_taken( + AddressPool *p, + const union in_addr_union *u, + unsigned prefixlen) { + + Link *l; + Network *n; + + assert(p); + assert(u); + + HASHMAP_FOREACH(l, p->manager->links) { + Address *a; + + /* Don't clash with assigned addresses */ + SET_FOREACH(a, l->addresses) { + if (a->family != p->family) + continue; + + if (in_addr_prefix_intersect(p->family, u, prefixlen, &a->in_addr, a->prefixlen)) + return true; + } + + /* Don't clash with addresses already pulled from the pool, but not assigned yet */ + SET_FOREACH(a, l->pool_addresses) { + if (a->family != p->family) + continue; + + if (in_addr_prefix_intersect(p->family, u, prefixlen, &a->in_addr, a->prefixlen)) + return true; + } + } + + /* And don't clash with configured but un-assigned addresses either */ + ORDERED_HASHMAP_FOREACH(n, p->manager->networks) { + Address *a; + + ORDERED_HASHMAP_FOREACH(a, n->addresses_by_section) { + if (a->family != p->family) + continue; + + if (in_addr_prefix_intersect(p->family, u, prefixlen, &a->in_addr, a->prefixlen)) + return true; + } + } + + return false; +} + +static int address_pool_acquire_one(AddressPool *p, int family, unsigned prefixlen, union in_addr_union *found) { + union in_addr_union u; + unsigned i; + int r; + + assert(p); + assert(prefixlen > 0); + assert(found); + + if (p->family != family) + return 0; + + if (p->prefixlen >= prefixlen) + return 0; + + u = p->in_addr; + + for (i = 0; i < RANDOM_PREFIX_TRIAL_MAX; i++) { + r = in_addr_random_prefix(p->family, &u, p->prefixlen, prefixlen); + if (r <= 0) + return r; + + if (!address_pool_prefix_is_taken(p, &u, prefixlen)) { + if (DEBUG_LOGGING) { + _cleanup_free_ char *s = NULL; + + (void) in_addr_to_string(p->family, &u, &s); + log_debug("Found range %s/%u", strna(s), prefixlen); + } + + *found = u; + return 1; + } + } + + return 0; +} + +int address_pool_acquire(Manager *m, int family, unsigned prefixlen, union in_addr_union *found) { + AddressPool *p; + int r; + + assert(m); + assert(IN_SET(family, AF_INET, AF_INET6)); + assert(prefixlen > 0); + assert(found); + + ORDERED_SET_FOREACH(p, m->address_pools) { + r = address_pool_acquire_one(p, family, prefixlen, found); + if (r != 0) + return r; + } + + return 0; +} diff --git a/src/network/networkd-address-pool.h b/src/network/networkd-address-pool.h new file mode 100644 index 0000000..93bdec8 --- /dev/null +++ b/src/network/networkd-address-pool.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "in-addr-util.h" + +typedef struct Manager Manager; + +typedef struct AddressPool { + Manager *manager; + + int family; + unsigned prefixlen; + union in_addr_union in_addr; +} AddressPool; + +int address_pool_setup_default(Manager *m); +int address_pool_acquire(Manager *m, int family, unsigned prefixlen, union in_addr_union *found); diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c new file mode 100644 index 0000000..961b248 --- /dev/null +++ b/src/network/networkd-address.c @@ -0,0 +1,1922 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <net/if.h> +#include <net/if_arp.h> + +#include "alloc-util.h" +#include "firewall-util.h" +#include "memory-util.h" +#include "netlink-util.h" +#include "networkd-address-pool.h" +#include "networkd-address.h" +#include "networkd-manager.h" +#include "networkd-network.h" +#include "parse-util.h" +#include "string-util.h" +#include "strv.h" + +#define ADDRESSES_PER_LINK_MAX 2048U +#define STATIC_ADDRESSES_PER_NETWORK_MAX 1024U + +int generate_ipv6_eui_64_address(const Link *link, struct in6_addr *ret) { + assert(link); + assert(ret); + + if (link->iftype == ARPHRD_INFINIBAND) { + /* see RFC4391 section 8 */ + memcpy(&ret->s6_addr[8], &link->hw_addr.addr.infiniband[12], 8); + ret->s6_addr[8] ^= 1 << 1; + + return 0; + } + + /* see RFC4291 section 2.5.1 */ + ret->s6_addr[8] = link->hw_addr.addr.ether.ether_addr_octet[0]; + ret->s6_addr[8] ^= 1 << 1; + ret->s6_addr[9] = link->hw_addr.addr.ether.ether_addr_octet[1]; + ret->s6_addr[10] = link->hw_addr.addr.ether.ether_addr_octet[2]; + ret->s6_addr[11] = 0xff; + ret->s6_addr[12] = 0xfe; + ret->s6_addr[13] = link->hw_addr.addr.ether.ether_addr_octet[3]; + ret->s6_addr[14] = link->hw_addr.addr.ether.ether_addr_octet[4]; + ret->s6_addr[15] = link->hw_addr.addr.ether.ether_addr_octet[5]; + + return 0; +} + +int address_new(Address **ret) { + _cleanup_(address_freep) Address *address = NULL; + + address = new(Address, 1); + if (!address) + return -ENOMEM; + + *address = (Address) { + .family = AF_UNSPEC, + .scope = RT_SCOPE_UNIVERSE, + .cinfo.ifa_prefered = CACHE_INFO_INFINITY_LIFE_TIME, + .cinfo.ifa_valid = CACHE_INFO_INFINITY_LIFE_TIME, + .duplicate_address_detection = ADDRESS_FAMILY_IPV6, + }; + + *ret = TAKE_PTR(address); + + return 0; +} + +static int address_new_static(Network *network, const char *filename, unsigned section_line, Address **ret) { + _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; + _cleanup_(address_freep) Address *address = NULL; + int r; + + assert(network); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = network_config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + address = ordered_hashmap_get(network->addresses_by_section, n); + if (address) { + *ret = TAKE_PTR(address); + return 0; + } + + if (ordered_hashmap_size(network->addresses_by_section) >= STATIC_ADDRESSES_PER_NETWORK_MAX) + return -E2BIG; + + r = address_new(&address); + if (r < 0) + return r; + + address->network = network; + address->section = TAKE_PTR(n); + + r = ordered_hashmap_ensure_allocated(&network->addresses_by_section, &network_config_hash_ops); + if (r < 0) + return r; + + r = ordered_hashmap_put(network->addresses_by_section, address->section, address); + if (r < 0) + return r; + + *ret = TAKE_PTR(address); + return 0; +} + +Address *address_free(Address *address) { + if (!address) + return NULL; + + if (address->network) { + assert(address->section); + ordered_hashmap_remove(address->network->addresses_by_section, address->section); + } + + if (address->link) { + NDiscAddress *n; + + set_remove(address->link->addresses, address); + set_remove(address->link->addresses_foreign, address); + set_remove(address->link->static_addresses, address); + if (address->link->dhcp_address == address) + address->link->dhcp_address = NULL; + if (address->link->dhcp_address_old == address) + address->link->dhcp_address_old = NULL; + set_remove(address->link->dhcp6_addresses, address); + set_remove(address->link->dhcp6_addresses_old, address); + set_remove(address->link->dhcp6_pd_addresses, address); + set_remove(address->link->dhcp6_pd_addresses_old, address); + SET_FOREACH(n, address->link->ndisc_addresses) + if (n->address == address) + free(set_remove(address->link->ndisc_addresses, n)); + + if (in_addr_equal(AF_INET6, &address->in_addr, (const union in_addr_union *) &address->link->ipv6ll_address)) + memzero(&address->link->ipv6ll_address, sizeof(struct in6_addr)); + } + + sd_ipv4acd_unref(address->acd); + + network_config_section_free(address->section); + free(address->label); + return mfree(address); +} + +static bool address_may_have_broadcast(const Address *a) { + assert(a); + + /* A /31 or /32 IPv4 address does not have a broadcast address. + * See https://tools.ietf.org/html/rfc3021 */ + + return a->family == AF_INET && in4_addr_is_null(&a->in_addr_peer.in) && a->prefixlen <= 30; +} + +static uint32_t address_prefix(const Address *a) { + assert(a); + + /* make sure we don't try to shift by 32. + * See ISO/IEC 9899:TC3 § 6.5.7.3. */ + if (a->prefixlen == 0) + return 0; + + if (a->in_addr_peer.in.s_addr != 0) + return be32toh(a->in_addr_peer.in.s_addr) >> (32 - a->prefixlen); + else + return be32toh(a->in_addr.in.s_addr) >> (32 - a->prefixlen); +} + +void address_hash_func(const Address *a, struct siphash *state) { + assert(a); + + siphash24_compress(&a->family, sizeof(a->family), state); + + switch (a->family) { + case AF_INET: + siphash24_compress(&a->prefixlen, sizeof(a->prefixlen), state); + + uint32_t prefix = address_prefix(a); + siphash24_compress(&prefix, sizeof(prefix), state); + + _fallthrough_; + case AF_INET6: + siphash24_compress(&a->in_addr, FAMILY_ADDRESS_SIZE(a->family), state); + break; + default: + /* treat any other address family as AF_UNSPEC */ + break; + } +} + +int address_compare_func(const Address *a1, const Address *a2) { + int r; + + r = CMP(a1->family, a2->family); + if (r != 0) + return r; + + switch (a1->family) { + case AF_INET: + /* See kernel's find_matching_ifa() in net/ipv4/devinet.c */ + r = CMP(a1->prefixlen, a2->prefixlen); + if (r != 0) + return r; + + r = CMP(address_prefix(a1), address_prefix(a2)); + if (r != 0) + return r; + + _fallthrough_; + case AF_INET6: + /* See kernel's ipv6_get_ifaddr() in net/ipv6/addrconf.c */ + return memcmp(&a1->in_addr, &a2->in_addr, FAMILY_ADDRESS_SIZE(a1->family)); + default: + /* treat any other address family as AF_UNSPEC */ + return 0; + } +} + +DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(address_hash_ops, Address, address_hash_func, address_compare_func, address_free); + +bool address_equal(const Address *a1, const Address *a2) { + if (a1 == a2) + return true; + + if (!a1 || !a2) + return false; + + return address_compare_func(a1, a2) == 0; +} + +static int address_copy(Address *dest, const Address *src) { + int r; + + assert(dest); + assert(src); + + if (src->family == AF_INET) { + r = free_and_strdup(&dest->label, src->label); + if (r < 0) + return r; + } + + dest->family = src->family; + dest->prefixlen = src->prefixlen; + dest->scope = src->scope; + dest->flags = src->flags; + dest->cinfo = src->cinfo; + dest->in_addr = src->in_addr; + dest->in_addr_peer = src->in_addr_peer; + if (address_may_have_broadcast(src)) + dest->broadcast = src->broadcast; + dest->duplicate_address_detection = src->duplicate_address_detection; + + return 0; +} + +static int address_set_masquerade(Address *address, bool add) { + union in_addr_union masked; + int r; + + assert(address); + assert(address->link); + + if (!address->link->network) + return 0; + + if (!address->link->network->ip_masquerade) + return 0; + + if (address->family != AF_INET) + return 0; + + if (address->scope >= RT_SCOPE_LINK) + return 0; + + if (address->ip_masquerade_done == add) + return 0; + + masked = address->in_addr; + r = in_addr_mask(address->family, &masked, address->prefixlen); + if (r < 0) + return r; + + r = fw_add_masquerade(add, AF_INET, 0, &masked, address->prefixlen, NULL, NULL, 0); + if (r < 0) + return r; + + address->ip_masquerade_done = add; + + return 0; +} + +static int address_add_internal(Link *link, Set **addresses, const Address *in, Address **ret) { + _cleanup_(address_freep) Address *address = NULL; + int r; + + assert(link); + assert(addresses); + assert(in); + + r = address_new(&address); + if (r < 0) + return r; + + r = address_copy(address, in); + if (r < 0) + return r; + + /* Consider address tentative until we get the real flags from the kernel */ + address->flags = IFA_F_TENTATIVE; + + r = set_ensure_put(addresses, &address_hash_ops, address); + if (r < 0) + return r; + if (r == 0) + return -EEXIST; + + address->link = link; + + if (ret) + *ret = address; + TAKE_PTR(address); + return 0; +} + +static int address_add_foreign(Link *link, const Address *in, Address **ret) { + return address_add_internal(link, &link->addresses_foreign, in, ret); +} + +static int address_add(Link *link, const Address *in, Address **ret) { + Address *address; + int r; + + assert(link); + assert(in); + + r = address_get(link, in, &address); + if (r == -ENOENT) { + /* Address does not exist, create a new one */ + r = address_add_internal(link, &link->addresses, in, &address); + if (r < 0) + return r; + } else if (r == 0) { + /* Take over a foreign address */ + r = set_ensure_put(&link->addresses, &address_hash_ops, address); + if (r < 0) + return r; + + set_remove(link->addresses_foreign, address); + } else if (r == 1) { + /* Already exists, do nothing */ + ; + } else + return r; + + if (ret) + *ret = address; + + return 0; +} + +static int address_update(Address *address, const Address *src) { + bool ready; + int r; + + assert(address); + assert(address->link); + assert(src); + + ready = address_is_ready(address); + + address->flags = src->flags; + address->scope = src->scope; + address->cinfo = src->cinfo; + + if (IN_SET(address->link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 0; + + link_update_operstate(address->link, true); + link_check_ready(address->link); + + if (!ready && address_is_ready(address)) { + if (address->callback) { + r = address->callback(address); + if (r < 0) + return r; + } + + if (address->family == AF_INET6 && + in_addr_is_link_local(AF_INET6, &address->in_addr) > 0 && + IN6_IS_ADDR_UNSPECIFIED(&address->link->ipv6ll_address) > 0) { + + r = link_ipv6ll_gained(address->link, &address->in_addr.in6); + if (r < 0) + return r; + } + } + + return 0; +} + +static int address_drop(Address *address) { + Link *link; + bool ready; + int r; + + assert(address); + + ready = address_is_ready(address); + link = address->link; + + r = address_set_masquerade(address, false); + if (r < 0) + log_link_warning_errno(link, r, "Failed to disable IP masquerading, ignoring: %m"); + + address_free(address); + + link_update_operstate(link, true); + + if (link && !ready) + link_check_ready(link); + + return 0; +} + +int address_get(Link *link, const Address *in, Address **ret) { + Address *existing; + + assert(link); + assert(in); + + existing = set_get(link->addresses, in); + if (existing) { + if (ret) + *ret = existing; + return 1; + } + + existing = set_get(link->addresses_foreign, in); + if (existing) { + if (ret) + *ret = existing; + return 0; + } + + return -ENOENT; +} + +static bool address_exists_internal(Set *addresses, int family, const union in_addr_union *in_addr) { + Address *address; + + SET_FOREACH(address, addresses) { + if (address->family != family) + continue; + if (in_addr_equal(address->family, &address->in_addr, in_addr)) + return true; + } + + return false; +} + +bool address_exists(Link *link, int family, const union in_addr_union *in_addr) { + assert(link); + assert(IN_SET(family, AF_INET, AF_INET6)); + assert(in_addr); + + if (address_exists_internal(link->addresses, family, in_addr)) + return true; + if (address_exists_internal(link->addresses_foreign, family, in_addr)) + return true; + return false; +} + +static int address_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(m); + assert(link); + assert(link->ifname); + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EADDRNOTAVAIL) + log_link_message_warning_errno(link, m, r, "Could not drop address"); + else if (r >= 0) + (void) manager_rtnl_process_address(rtnl, m, link->manager); + + return 1; +} + +int address_remove( + const Address *address, + Link *link, + link_netlink_message_handler_t callback) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(address); + assert(IN_SET(address->family, AF_INET, AF_INET6)); + assert(link); + assert(link->ifindex > 0); + assert(link->manager); + assert(link->manager->rtnl); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *b = NULL; + + (void) in_addr_to_string(address->family, &address->in_addr, &b); + log_link_debug(link, "Removing address %s", strna(b)); + } + + r = sd_rtnl_message_new_addr(link->manager->rtnl, &req, RTM_DELADDR, + link->ifindex, address->family); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate RTM_DELADDR message: %m"); + + r = sd_rtnl_message_addr_set_prefixlen(req, address->prefixlen); + if (r < 0) + return log_link_error_errno(link, r, "Could not set prefixlen: %m"); + + r = netlink_message_append_in_addr_union(req, IFA_LOCAL, address->family, &address->in_addr); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFA_LOCAL attribute: %m"); + + r = netlink_call_async(link->manager->rtnl, NULL, req, + callback ?: address_remove_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + return 0; +} + +static bool link_is_static_address_configured(const Link *link, const Address *address) { + Address *net_address; + + assert(link); + assert(address); + + if (!link->network) + return false; + + ORDERED_HASHMAP_FOREACH(net_address, link->network->addresses_by_section) + if (address_equal(net_address, address)) + return true; + + return false; +} + +static bool link_address_is_dynamic(const Link *link, const Address *address) { + Route *route; + + assert(link); + assert(address); + + if (address->cinfo.ifa_prefered != CACHE_INFO_INFINITY_LIFE_TIME) + return true; + + /* Even when the address is leased from a DHCP server, networkd assign the address + * without lifetime when KeepConfiguration=dhcp. So, let's check that we have + * corresponding routes with RTPROT_DHCP. */ + SET_FOREACH(route, link->routes_foreign) { + if (route->protocol != RTPROT_DHCP) + continue; + + if (address->family != route->family) + continue; + + if (in_addr_equal(address->family, &address->in_addr, &route->prefsrc)) + return true; + } + + return false; +} + +static int link_enumerate_ipv6_tentative_addresses(Link *link) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + sd_netlink_message *addr; + int r; + + assert(link); + assert(link->manager); + assert(link->manager->rtnl); + + r = sd_rtnl_message_new_addr(link->manager->rtnl, &req, RTM_GETADDR, 0, AF_INET6); + if (r < 0) + return r; + + r = sd_netlink_call(link->manager->rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (addr = reply; addr; addr = sd_netlink_message_next(addr)) { + unsigned char flags; + int ifindex; + + r = sd_rtnl_message_addr_get_ifindex(addr, &ifindex); + if (r < 0) { + log_link_warning_errno(link, r, "rtnl: invalid ifindex, ignoring: %m"); + continue; + } else if (link->ifindex != ifindex) + continue; + + r = sd_rtnl_message_addr_get_flags(addr, &flags); + if (r < 0) { + log_link_warning_errno(link, r, "rtnl: received address message with invalid flags, ignoring: %m"); + continue; + } else if (!(flags & IFA_F_TENTATIVE)) + continue; + + log_link_debug(link, "Found tentative ipv6 link-local address"); + (void) manager_rtnl_process_address(link->manager->rtnl, addr, link->manager); + } + + return 0; +} + +int link_drop_foreign_addresses(Link *link) { + Address *address; + int k, r = 0; + + assert(link); + + /* The kernel doesn't notify us about tentative addresses; + * so if ipv6ll is disabled, we need to enumerate them now so we can drop them below */ + if (!link_ipv6ll_enabled(link)) { + r = link_enumerate_ipv6_tentative_addresses(link); + if (r < 0) + return r; + } + + SET_FOREACH(address, link->addresses_foreign) { + /* we consider IPv6LL addresses to be managed by the kernel */ + if (address->family == AF_INET6 && in_addr_is_link_local(AF_INET6, &address->in_addr) == 1 && link_ipv6ll_enabled(link)) + continue; + + if (link_address_is_dynamic(link, address)) { + if (link->network && FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP)) + continue; + } else if (link->network && FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_STATIC)) + continue; + + if (link_is_static_address_configured(link, address)) { + k = address_add(link, address, NULL); + if (k < 0) { + log_link_error_errno(link, k, "Failed to add address: %m"); + if (r >= 0) + r = k; + } + } else { + k = address_remove(address, link, NULL); + if (k < 0 && r >= 0) + r = k; + } + } + + return r; +} + +static int remove_static_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(m); + assert(link); + assert(link->ifname); + assert(link->address_remove_messages > 0); + + link->address_remove_messages--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EADDRNOTAVAIL) + log_link_message_warning_errno(link, m, r, "Could not drop address"); + else if (r >= 0) + (void) manager_rtnl_process_address(rtnl, m, link->manager); + + if (link->address_remove_messages == 0 && link->request_static_addresses) { + link_set_state(link, LINK_STATE_CONFIGURING); + r = link_set_addresses(link); + if (r < 0) + link_enter_failed(link); + } + + return 1; +} + +int link_drop_addresses(Link *link) { + Address *address, *pool_address; + int k, r = 0; + + assert(link); + + SET_FOREACH(address, link->addresses) { + /* we consider IPv6LL addresses to be managed by the kernel */ + if (address->family == AF_INET6 && in_addr_is_link_local(AF_INET6, &address->in_addr) == 1 && link_ipv6ll_enabled(link)) + continue; + + k = address_remove(address, link, remove_static_address_handler); + if (k < 0 && r >= 0) { + r = k; + continue; + } + + link->address_remove_messages++; + + SET_FOREACH(pool_address, link->pool_addresses) + if (address_equal(address, pool_address)) + address_free(set_remove(link->pool_addresses, pool_address)); + } + + return r; +} + +static int address_acquire(Link *link, const Address *original, Address **ret) { + union in_addr_union in_addr = IN_ADDR_NULL; + struct in_addr broadcast = {}; + _cleanup_(address_freep) Address *na = NULL; + int r; + + assert(link); + assert(original); + assert(ret); + + /* Something useful was configured? just use it */ + r = in_addr_is_null(original->family, &original->in_addr); + if (r < 0) + return r; + if (r == 0) { + *ret = NULL; + return 0; + } + + /* The address is configured to be 0.0.0.0 or [::] by the user? + * Then let's acquire something more useful from the pool. */ + r = address_pool_acquire(link->manager, original->family, original->prefixlen, &in_addr); + if (r < 0) + return r; + if (r == 0) + return -EBUSY; + + if (original->family == AF_INET) { + /* Pick first address in range for ourselves ... */ + in_addr.in.s_addr = in_addr.in.s_addr | htobe32(1); + + /* .. and use last as broadcast address */ + if (original->prefixlen > 30) + broadcast.s_addr = 0; + else + broadcast.s_addr = in_addr.in.s_addr | htobe32(0xFFFFFFFFUL >> original->prefixlen); + } else if (original->family == AF_INET6) + in_addr.in6.s6_addr[15] |= 1; + + r = address_new(&na); + if (r < 0) + return r; + + r = address_copy(na, original); + if (r < 0) + return r; + + na->broadcast = broadcast; + na->in_addr = in_addr; + + r = set_ensure_put(&link->pool_addresses, &address_hash_ops, na); + if (r < 0) + return r; + if (r == 0) + return -EEXIST; + + *ret = TAKE_PTR(na); + return 1; +} + +static int ipv4_dad_configure(Address *address); + +int address_configure( + const Address *address, + Link *link, + link_netlink_message_handler_t callback, + bool update, + Address **ret) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + Address *acquired_address, *a; + uint32_t flags; + int r; + + assert(address); + assert(IN_SET(address->family, AF_INET, AF_INET6)); + assert(link); + assert(link->ifindex > 0); + assert(link->manager); + assert(link->manager->rtnl); + assert(callback); + + /* If this is a new address, then refuse adding more than the limit */ + if (address_get(link, address, NULL) <= 0 && + set_size(link->addresses) >= ADDRESSES_PER_LINK_MAX) + return log_link_error_errno(link, SYNTHETIC_ERRNO(E2BIG), + "Too many addresses are configured, refusing: %m"); + + r = address_acquire(link, address, &acquired_address); + if (r < 0) + return log_link_error_errno(link, r, "Failed to acquire an address from pool: %m"); + if (acquired_address) + address = acquired_address; + + if (DEBUG_LOGGING) { + _cleanup_free_ char *str = NULL; + + (void) in_addr_to_string(address->family, &address->in_addr, &str); + log_link_debug(link, "%s address: %s", update ? "Updating" : "Configuring", strna(str)); + } + + if (update) + r = sd_rtnl_message_new_addr_update(link->manager->rtnl, &req, + link->ifindex, address->family); + else + r = sd_rtnl_message_new_addr(link->manager->rtnl, &req, RTM_NEWADDR, + link->ifindex, address->family); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate RTM_NEWADDR message: %m"); + + r = sd_rtnl_message_addr_set_prefixlen(req, address->prefixlen); + if (r < 0) + return log_link_error_errno(link, r, "Could not set prefixlen: %m"); + + flags = address->flags | IFA_F_PERMANENT; + r = sd_rtnl_message_addr_set_flags(req, flags & 0xff); + if (r < 0) + return log_link_error_errno(link, r, "Could not set flags: %m"); + + if (flags & ~0xff) { + r = sd_netlink_message_append_u32(req, IFA_FLAGS, flags); + if (r < 0) + return log_link_error_errno(link, r, "Could not set extended flags: %m"); + } + + r = sd_rtnl_message_addr_set_scope(req, address->scope); + if (r < 0) + return log_link_error_errno(link, r, "Could not set scope: %m"); + + r = netlink_message_append_in_addr_union(req, IFA_LOCAL, address->family, &address->in_addr); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFA_LOCAL attribute: %m"); + + if (in_addr_is_null(address->family, &address->in_addr_peer) == 0) { + r = netlink_message_append_in_addr_union(req, IFA_ADDRESS, address->family, &address->in_addr_peer); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFA_ADDRESS attribute: %m"); + } else if (address_may_have_broadcast(address)) { + r = sd_netlink_message_append_in_addr(req, IFA_BROADCAST, &address->broadcast); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFA_BROADCAST attribute: %m"); + } + + if (address->family == AF_INET && address->label) { + r = sd_netlink_message_append_string(req, IFA_LABEL, address->label); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFA_LABEL attribute: %m"); + } + + r = sd_netlink_message_append_cache_info(req, IFA_CACHEINFO, &address->cinfo); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFA_CACHEINFO attribute: %m"); + + r = address_add(link, address, &a); + if (r < 0) + return log_link_error_errno(link, r, "Could not add address: %m"); + + r = address_set_masquerade(a, true); + if (r < 0) + log_link_warning_errno(link, r, "Could not enable IP masquerading, ignoring: %m"); + + r = netlink_call_async(link->manager->rtnl, NULL, req, callback, link_netlink_destroy_callback, link); + if (r < 0) { + (void) address_set_masquerade(a, false); + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + } + + link_ref(link); + + if (FLAGS_SET(address->duplicate_address_detection, ADDRESS_FAMILY_IPV4)) { + r = ipv4_dad_configure(a); + if (r < 0) + log_link_warning_errno(link, r, "Failed to start IPv4ACD client, ignoring: %m"); + } + + if (ret) + *ret = a; + + return 1; +} + +static int static_address_ready_callback(Address *address) { + Address *a; + Link *link; + + assert(address); + assert(address->link); + + link = address->link; + + if (!link->addresses_configured) + return 0; + + SET_FOREACH(a, link->static_addresses) + if (!address_is_ready(a)) { + _cleanup_free_ char *str = NULL; + + (void) in_addr_to_string(a->family, &a->in_addr, &str); + log_link_debug(link, "an address %s/%u is not ready", strnull(str), a->prefixlen); + return 0; + } + + /* This should not be called again */ + SET_FOREACH(a, link->static_addresses) + a->callback = NULL; + + link->addresses_ready = true; + + return link_set_routes(link); +} + +static int address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(rtnl); + assert(m); + assert(link); + assert(link->ifname); + assert(link->address_messages > 0); + + link->address_messages--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EEXIST) { + log_link_message_warning_errno(link, m, r, "Could not set address"); + link_enter_failed(link); + return 1; + } else if (r >= 0) + (void) manager_rtnl_process_address(rtnl, m, link->manager); + + if (link->address_messages == 0) { + Address *a; + + log_link_debug(link, "Addresses set"); + link->addresses_configured = true; + + /* When all static addresses are already ready, then static_address_ready_callback() + * will not be called automatically. So, call it here. */ + a = set_first(link->static_addresses); + if (!a) { + log_link_debug(link, "No static address is stored. Already removed?"); + return 1; + } + + r = static_address_ready_callback(a); + if (r < 0) + link_enter_failed(link); + } + + return 1; +} + +static int static_address_configure(const Address *address, Link *link, bool update) { + Address *ret; + int r; + + assert(address); + assert(link); + + r = address_configure(address, link, address_handler, update, &ret); + if (r < 0) + return log_link_warning_errno(link, r, "Could not configure static address: %m"); + + link->address_messages++; + + r = set_ensure_put(&link->static_addresses, &address_hash_ops, ret); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to store static address: %m"); + + ret->callback = static_address_ready_callback; + + return 0; +} + +int link_set_addresses(Link *link) { + Address *ad; + Prefix *p; + int r; + + assert(link); + assert(link->network); + + if (link->address_remove_messages != 0) { + log_link_debug(link, "Removing old addresses, new addresses will be configured later."); + link->request_static_addresses = true; + return 0; + } + + ORDERED_HASHMAP_FOREACH(ad, link->network->addresses_by_section) { + bool update; + + update = address_get(link, ad, NULL) > 0; + r = static_address_configure(ad, link, update); + if (r < 0) + return r; + } + + HASHMAP_FOREACH(p, link->network->prefixes_by_section) { + _cleanup_(address_freep) Address *address = NULL; + + if (!p->assign) + continue; + + r = address_new(&address); + if (r < 0) + return log_oom(); + + r = sd_radv_prefix_get_prefix(p->radv_prefix, &address->in_addr.in6, &address->prefixlen); + if (r < 0) + return log_link_warning_errno(link, r, "Could not get RA prefix: %m"); + + r = generate_ipv6_eui_64_address(link, &address->in_addr.in6); + if (r < 0) + return log_link_warning_errno(link, r, "Could not generate EUI64 address: %m"); + + address->family = AF_INET6; + r = static_address_configure(address, link, true); + if (r < 0) + return r; + } + + if (link->address_messages == 0) { + link->addresses_configured = true; + link->addresses_ready = true; + r = link_set_routes(link); + if (r < 0) + return r; + } else { + log_link_debug(link, "Setting addresses"); + link_set_state(link, LINK_STATE_CONFIGURING); + } + + return 0; +} + +int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) { + _cleanup_(address_freep) Address *tmp = NULL; + _cleanup_free_ char *buf = NULL, *buf_peer = NULL; + Link *link = NULL; + uint16_t type; + unsigned char flags; + Address *address = NULL; + char valid_buf[FORMAT_TIMESPAN_MAX]; + const char *valid_str = NULL; + int ifindex, r; + bool has_peer = false; + + assert(rtnl); + assert(message); + assert(m); + + if (sd_netlink_message_is_error(message)) { + r = sd_netlink_message_get_errno(message); + if (r < 0) + log_message_warning_errno(message, r, "rtnl: failed to receive address message, ignoring"); + + return 0; + } + + r = sd_netlink_message_get_type(message, &type); + if (r < 0) { + log_warning_errno(r, "rtnl: could not get message type, ignoring: %m"); + return 0; + } else if (!IN_SET(type, RTM_NEWADDR, RTM_DELADDR)) { + log_warning("rtnl: received unexpected message type %u when processing address, ignoring.", type); + return 0; + } + + r = sd_rtnl_message_addr_get_ifindex(message, &ifindex); + if (r < 0) { + log_warning_errno(r, "rtnl: could not get ifindex from message, ignoring: %m"); + return 0; + } else if (ifindex <= 0) { + log_warning("rtnl: received address message with invalid ifindex %d, ignoring.", ifindex); + return 0; + } + + r = link_get(m, ifindex, &link); + if (r < 0 || !link) { + /* when enumerating we might be out of sync, but we will get the address again, so just + * ignore it */ + if (!m->enumerating) + log_warning("rtnl: received address for link '%d' we don't know about, ignoring.", ifindex); + return 0; + } + + r = address_new(&tmp); + if (r < 0) + return log_oom(); + + r = sd_rtnl_message_addr_get_family(message, &tmp->family); + if (r < 0) { + log_link_warning(link, "rtnl: received address message without family, ignoring."); + return 0; + } else if (!IN_SET(tmp->family, AF_INET, AF_INET6)) { + log_link_debug(link, "rtnl: received address message with invalid family '%i', ignoring.", tmp->family); + return 0; + } + + r = sd_rtnl_message_addr_get_prefixlen(message, &tmp->prefixlen); + if (r < 0) { + log_link_warning_errno(link, r, "rtnl: received address message without prefixlen, ignoring: %m"); + return 0; + } + + r = sd_rtnl_message_addr_get_scope(message, &tmp->scope); + if (r < 0) { + log_link_warning_errno(link, r, "rtnl: received address message without scope, ignoring: %m"); + return 0; + } + + r = sd_rtnl_message_addr_get_flags(message, &flags); + if (r < 0) { + log_link_warning_errno(link, r, "rtnl: received address message without flags, ignoring: %m"); + return 0; + } + tmp->flags = flags; + + switch (tmp->family) { + case AF_INET: + r = sd_netlink_message_read_in_addr(message, IFA_LOCAL, &tmp->in_addr.in); + if (r < 0) { + log_link_warning_errno(link, r, "rtnl: received address message without valid address, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_read_in_addr(message, IFA_ADDRESS, &tmp->in_addr_peer.in); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: could not get peer address from address message, ignoring: %m"); + return 0; + } else if (r >= 0) { + if (in4_addr_equal(&tmp->in_addr.in, &tmp->in_addr_peer.in)) + tmp->in_addr_peer = IN_ADDR_NULL; + else + has_peer = true; + } + + r = sd_netlink_message_read_in_addr(message, IFA_BROADCAST, &tmp->broadcast); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: could not get broadcast from address message, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_read_string_strdup(message, IFA_LABEL, &tmp->label); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: could not get label from address message, ignoring: %m"); + return 0; + } else if (r >= 0 && streq_ptr(tmp->label, link->ifname)) + tmp->label = mfree(tmp->label); + + break; + + case AF_INET6: + r = sd_netlink_message_read_in6_addr(message, IFA_LOCAL, &tmp->in_addr.in6); + if (r >= 0) { + /* Have peer address. */ + r = sd_netlink_message_read_in6_addr(message, IFA_ADDRESS, &tmp->in_addr_peer.in6); + if (r < 0) { + log_link_warning_errno(link, r, "rtnl: could not get peer address from address message, ignoring: %m"); + return 0; + } + has_peer = true; + } else if (r == -ENODATA) { + /* Does not have peer address. */ + r = sd_netlink_message_read_in6_addr(message, IFA_ADDRESS, &tmp->in_addr.in6); + if (r < 0) { + log_link_warning_errno(link, r, "rtnl: received address message without valid address, ignoring: %m"); + return 0; + } + } else { + log_link_warning_errno(link, r, "rtnl: could not get local address from address message, ignoring: %m"); + return 0; + } + + break; + + default: + assert_not_reached("Received unsupported address family"); + } + + (void) in_addr_to_string(tmp->family, &tmp->in_addr, &buf); + (void) in_addr_to_string(tmp->family, &tmp->in_addr_peer, &buf_peer); + + r = sd_netlink_message_read_cache_info(message, IFA_CACHEINFO, &tmp->cinfo); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: cannot get IFA_CACHEINFO attribute, ignoring: %m"); + return 0; + } else if (r >= 0 && tmp->cinfo.ifa_valid != CACHE_INFO_INFINITY_LIFE_TIME) + valid_str = format_timespan(valid_buf, FORMAT_TIMESPAN_MAX, + tmp->cinfo.ifa_valid * USEC_PER_SEC, + USEC_PER_SEC); + + (void) address_get(link, tmp, &address); + + switch (type) { + case RTM_NEWADDR: + if (address) + log_link_debug(link, "Remembering updated address: %s%s%s/%u (valid %s%s)", + strnull(buf), has_peer ? " peer " : "", + has_peer ? strnull(buf_peer) : "", tmp->prefixlen, + valid_str ? "for " : "forever", strempty(valid_str)); + else { + /* An address appeared that we did not request */ + r = address_add_foreign(link, tmp, &address); + if (r < 0) { + log_link_warning_errno(link, r, "Failed to remember foreign address %s/%u, ignoring: %m", + strnull(buf), tmp->prefixlen); + return 0; + } else + log_link_debug(link, "Remembering foreign address: %s%s%s/%u (valid %s%s)", + strnull(buf), has_peer ? " peer " : "", + has_peer ? strnull(buf_peer) : "", tmp->prefixlen, + valid_str ? "for " : "forever", strempty(valid_str)); + } + + /* address_update() logs internally, so we don't need to here. */ + r = address_update(address, tmp); + if (r < 0) + link_enter_failed(link); + + break; + + case RTM_DELADDR: + if (address) { + log_link_debug(link, "Forgetting address: %s%s%s/%u (valid %s%s)", + strnull(buf), has_peer ? " peer " : "", + has_peer ? strnull(buf_peer) : "", tmp->prefixlen, + valid_str ? "for " : "forever", strempty(valid_str)); + (void) address_drop(address); + } else + log_link_debug(link, "Kernel removed an address we don't remember: %s%s%s/%u (valid %s%s), ignoring.", + strnull(buf), has_peer ? " peer " : "", + has_peer ? strnull(buf_peer) : "", tmp->prefixlen, + valid_str ? "for " : "forever", strempty(valid_str)); + + break; + + default: + assert_not_reached("Received invalid RTNL message type"); + } + + return 1; +} + +int link_serialize_addresses(Link *link, FILE *f) { + bool space = false; + Address *a; + + assert(link); + + fputs("ADDRESSES=", f); + SET_FOREACH(a, link->addresses) { + _cleanup_free_ char *address_str = NULL; + + if (in_addr_to_string(a->family, &a->in_addr, &address_str) < 0) + continue; + + fprintf(f, "%s%s/%u", space ? " " : "", address_str, a->prefixlen); + space = true; + } + fputc('\n', f); + + return 0; +} + +int link_deserialize_addresses(Link *link, const char *addresses) { + int r; + + assert(link); + + for (const char *p = addresses;; ) { + _cleanup_(address_freep) Address *tmp = NULL; + _cleanup_free_ char *address_str = NULL; + + r = extract_first_word(&p, &address_str, NULL, 0); + if (r < 0) + return log_link_debug_errno(link, r, "Failed to parse ADDRESSES=: %m"); + if (r == 0) + return 0; + + r = address_new(&tmp); + if (r < 0) + return log_oom(); + + r = in_addr_prefix_from_string_auto(address_str, &tmp->family, &tmp->in_addr, &tmp->prefixlen); + if (r < 0) { + log_link_debug_errno(link, r, "Failed to parse address, ignoring: %s", address_str); + continue; + } + + r = address_add(link, tmp, NULL); + if (r < 0) + log_link_debug_errno(link, r, "Failed to add address %s, ignoring: %m", address_str); + } + + return 0; +} + +static void static_address_on_acd(sd_ipv4acd *acd, int event, void *userdata) { + _cleanup_free_ char *pretty = NULL; + Address *address; + Link *link; + int r; + + assert(acd); + assert(userdata); + + address = (Address *) userdata; + link = address->link; + + (void) in_addr_to_string(address->family, &address->in_addr, &pretty); + switch (event) { + case SD_IPV4ACD_EVENT_STOP: + log_link_debug(link, "Stopping ACD client..."); + return; + + case SD_IPV4ACD_EVENT_BIND: + log_link_debug(link, "Successfully claimed address %s", strna(pretty)); + link_check_ready(link); + break; + + case SD_IPV4ACD_EVENT_CONFLICT: + log_link_warning(link, "DAD conflict. Dropping address %s", strna(pretty)); + r = address_remove(address, link, NULL); + if (r < 0) + log_link_error_errno(link, r, "Failed to drop DAD conflicted address %s", strna(pretty));; + + link_check_ready(link); + break; + + default: + assert_not_reached("Invalid IPv4ACD event."); + } + + (void) sd_ipv4acd_stop(acd); + + return; +} + +static int ipv4_dad_configure(Address *address) { + int r; + + assert(address); + assert(address->link); + + if (address->family != AF_INET) + return 0; + + if (DEBUG_LOGGING) { + _cleanup_free_ char *pretty = NULL; + + (void) in_addr_to_string(address->family, &address->in_addr, &pretty); + log_link_debug(address->link, "Starting IPv4ACD client. Probing address %s", strna(pretty)); + } + + if (!address->acd) { + r = sd_ipv4acd_new(&address->acd); + if (r < 0) + return r; + + r = sd_ipv4acd_attach_event(address->acd, address->link->manager->event, 0); + if (r < 0) + return r; + } + + r = sd_ipv4acd_set_ifindex(address->acd, address->link->ifindex); + if (r < 0) + return r; + + r = sd_ipv4acd_set_mac(address->acd, &address->link->hw_addr.addr.ether); + if (r < 0) + return r; + + r = sd_ipv4acd_set_address(address->acd, &address->in_addr.in); + if (r < 0) + return r; + + r = sd_ipv4acd_set_callback(address->acd, static_address_on_acd, address); + if (r < 0) + return r; + + return sd_ipv4acd_start(address->acd, true); +} + +static int ipv4_dad_update_mac_one(Address *address) { + bool running; + int r; + + assert(address); + + if (!address->acd) + return 0; + + running = sd_ipv4acd_is_running(address->acd); + + r = sd_ipv4acd_stop(address->acd); + if (r < 0) + return r; + + r = sd_ipv4acd_set_mac(address->acd, &address->link->hw_addr.addr.ether); + if (r < 0) + return r; + + if (running) { + r = sd_ipv4acd_start(address->acd, true); + if (r < 0) + return r; + } + + return 0; +} + +int ipv4_dad_update_mac(Link *link) { + Address *address; + int k, r = 0; + + assert(link); + + SET_FOREACH(address, link->addresses) { + k = ipv4_dad_update_mac_one(address); + if (k < 0 && r >= 0) + r = k; + } + + return r; +} + +int ipv4_dad_stop(Link *link) { + Address *address; + int k, r = 0; + + assert(link); + + SET_FOREACH(address, link->addresses) { + k = sd_ipv4acd_stop(address->acd); + if (k < 0 && r >= 0) + r = k; + } + + return r; +} + +void ipv4_dad_unref(Link *link) { + Address *address; + + assert(link); + + SET_FOREACH(address, link->addresses) + address->acd = sd_ipv4acd_unref(address->acd); +} + +int config_parse_broadcast( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = address_new_static(network, filename, section_line, &n); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate new address, ignoring assignment: %m"); + return 0; + } + + if (n->family == AF_INET6) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Broadcast is not valid for IPv6 addresses, ignoring assignment: %s", rvalue); + return 0; + } + + r = in_addr_from_string(AF_INET, rvalue, (union in_addr_union*) &n->broadcast); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Broadcast is invalid, ignoring assignment: %s", rvalue); + return 0; + } + + n->family = AF_INET; + n = NULL; + + return 0; +} + +int config_parse_address( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + union in_addr_union buffer; + unsigned char prefixlen; + int r, f; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + if (streq(section, "Network")) + /* we are not in an Address section, so use line number instead. */ + r = address_new_static(network, filename, line, &n); + else + r = address_new_static(network, filename, section_line, &n); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate new address, ignoring assignment: %m"); + return 0; + } + + /* Address=address/prefixlen */ + r = in_addr_prefix_from_string_auto_internal(rvalue, PREFIXLEN_REFUSE, &f, &buffer, &prefixlen); + if (r == -ENOANO) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "An address '%s' is specified without prefix length. " + "The behavior of parsing addresses without prefix length will be changed in the future release. " + "Please specify prefix length explicitly.", rvalue); + + r = in_addr_prefix_from_string_auto_internal(rvalue, PREFIXLEN_LEGACY, &f, &buffer, &prefixlen); + } + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid address '%s', ignoring assignment: %m", rvalue); + return 0; + } + + if (n->family != AF_UNSPEC && f != n->family) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Address is incompatible, ignoring assignment: %s", rvalue); + return 0; + } + + if (in_addr_is_null(f, &buffer)) { + /* Will use address from address pool. Note that for ipv6 case, prefix of the address + * pool is 8, but 40 bit is used by the global ID and 16 bit by the subnet ID. So, + * let's limit the prefix length to 64 or larger. See RFC4193. */ + if ((f == AF_INET && prefixlen < 8) || + (f == AF_INET6 && prefixlen < 64)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Null address with invalid prefixlen='%u', ignoring assignment: %s", + prefixlen, rvalue); + return 0; + } + } + + n->family = f; + n->prefixlen = prefixlen; + + if (streq(lvalue, "Address")) + n->in_addr = buffer; + else + n->in_addr_peer = buffer; + + n = NULL; + + return 0; +} + +int config_parse_label( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = address_new_static(network, filename, section_line, &n); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate new address, ignoring assignment: %m"); + return 0; + } + + if (!address_label_valid(rvalue)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Interface label is too long or invalid, ignoring assignment: %s", rvalue); + return 0; + } + + r = free_and_strdup(&n->label, rvalue); + if (r < 0) + return log_oom(); + + n = NULL; + return 0; +} + +int config_parse_lifetime( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + uint32_t k; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = address_new_static(network, filename, section_line, &n); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate new address, ignoring assignment: %m"); + return 0; + } + + /* We accept only "forever", "infinity", empty, or "0". */ + if (STR_IN_SET(rvalue, "forever", "infinity", "")) + k = CACHE_INFO_INFINITY_LIFE_TIME; + else if (streq(rvalue, "0")) + k = 0; + else { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid PreferredLifetime= value, ignoring: %s", rvalue); + return 0; + } + + n->cinfo.ifa_prefered = k; + TAKE_PTR(n); + + return 0; +} + +int config_parse_address_flags( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = address_new_static(network, filename, section_line, &n); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate new address, ignoring assignment: %m"); + return 0; + } + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse %s=, ignoring: %s", lvalue, rvalue); + return 0; + } + + if (streq(lvalue, "AddPrefixRoute")) + r = !r; + + SET_FLAG(n->flags, ltype, r); + + n = NULL; + return 0; +} + +int config_parse_address_scope( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = address_new_static(network, filename, section_line, &n); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate new address, ignoring assignment: %m"); + return 0; + } + + if (streq(rvalue, "host")) + n->scope = RT_SCOPE_HOST; + else if (streq(rvalue, "link")) + n->scope = RT_SCOPE_LINK; + else if (streq(rvalue, "global")) + n->scope = RT_SCOPE_UNIVERSE; + else { + r = safe_atou8(rvalue , &n->scope); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Could not parse address scope \"%s\", ignoring assignment: %m", rvalue); + return 0; + } + } + + n->scope_set = true; + n = NULL; + return 0; +} + +int config_parse_duplicate_address_detection( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + AddressFamily a; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = address_new_static(network, filename, section_line, &n); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate new address, ignoring assignment: %m"); + return 0; + } + + r = parse_boolean(rvalue); + if (r >= 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "For historical reasons, %s=%s means %s=%s. " + "Please use 'both', 'ipv4', 'ipv6' or 'none' instead.", + lvalue, rvalue, lvalue, r ? "none" : "both"); + n->duplicate_address_detection = r ? ADDRESS_FAMILY_NO : ADDRESS_FAMILY_YES; + n = NULL; + return 0; + } + + a = duplicate_address_detection_address_family_from_string(rvalue); + if (a < 0) { + log_syntax(unit, LOG_WARNING, filename, line, SYNTHETIC_ERRNO(EINVAL), + "Failed to parse %s=, ignoring: %s", lvalue, rvalue); + return 0; + } + + n->duplicate_address_detection = a; + n = NULL; + return 0; +} + +bool address_is_ready(const Address *a) { + assert(a); + + return !(a->flags & IFA_F_TENTATIVE); +} + +static int address_section_verify(Address *address) { + if (section_is_invalid(address->section)) + return -EINVAL; + + if (address->family == AF_UNSPEC) { + assert(address->section); + + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Address section without Address= field configured. " + "Ignoring [Address] section from line %u.", + address->section->filename, address->section->line); + } + + if (address_may_have_broadcast(address)) { + if (address->broadcast.s_addr == 0) + address->broadcast.s_addr = address->in_addr.in.s_addr | htobe32(0xfffffffflu >> address->prefixlen); + } else if (address->broadcast.s_addr != 0) { + log_warning("%s: broadcast address is set for IPv6 address or IPv4 address with prefixlength larger than 30. " + "Ignoring Broadcast= setting in the [Address] section from line %u.", + address->section->filename, address->section->line); + + address->broadcast.s_addr = 0; + } + + if (address->family == AF_INET6 && address->label) { + log_warning("%s: address label is set for IPv6 address in the [Address] section from line %u. " + "Ignoring Label= setting.", + address->section->filename, address->section->line); + + address->label = mfree(address->label); + } + + if (in_addr_is_localhost(address->family, &address->in_addr) > 0 && + (address->family == AF_INET || !address->scope_set)) { + /* For IPv4, scope must be always RT_SCOPE_HOST. + * For IPv6, use RT_SCOPE_HOST only when it is not explicitly specified. */ + + if (address->scope_set && address->scope != RT_SCOPE_HOST) + log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: non-host scope is set in the [Address] section from line %u. " + "Ignoring Scope= setting.", + address->section->filename, address->section->line); + + address->scope = RT_SCOPE_HOST; + } + + if (!FLAGS_SET(address->duplicate_address_detection, ADDRESS_FAMILY_IPV6)) + address->flags |= IFA_F_NODAD; + + return 0; +} + +void network_drop_invalid_addresses(Network *network) { + Address *address; + + assert(network); + + ORDERED_HASHMAP_FOREACH(address, network->addresses_by_section) + if (address_section_verify(address) < 0) + address_free(address); +} diff --git a/src/network/networkd-address.h b/src/network/networkd-address.h new file mode 100644 index 0000000..56e81da --- /dev/null +++ b/src/network/networkd-address.h @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <inttypes.h> +#include <stdbool.h> +#include <stdio.h> + +#include "sd-ipv4acd.h" + +#include "conf-parser.h" +#include "in-addr-util.h" +#include "networkd-link.h" +#include "networkd-util.h" + +#define CACHE_INFO_INFINITY_LIFE_TIME 0xFFFFFFFFU + +typedef struct Manager Manager; +typedef struct Network Network; +typedef int (*address_ready_callback_t)(Address *address); + +typedef struct Address { + Network *network; + NetworkConfigSection *section; + + Link *link; + + int family; + unsigned char prefixlen; + unsigned char scope; + uint32_t flags; + char *label; + + struct in_addr broadcast; + struct ifa_cacheinfo cinfo; + + union in_addr_union in_addr; + union in_addr_union in_addr_peer; + + bool scope_set:1; + bool ip_masquerade_done:1; + AddressFamily duplicate_address_detection; + + /* Called when address become ready */ + address_ready_callback_t callback; + + sd_ipv4acd *acd; +} Address; + +int address_new(Address **ret); +Address *address_free(Address *address); +int address_get(Link *link, const Address *in, Address **ret); +bool address_exists(Link *link, int family, const union in_addr_union *in_addr); +int address_configure(const Address *address, Link *link, link_netlink_message_handler_t callback, bool update, Address **ret); +int address_remove(const Address *address, Link *link, link_netlink_message_handler_t callback); +bool address_equal(const Address *a1, const Address *a2); +bool address_is_ready(const Address *a); + +int generate_ipv6_eui_64_address(const Link *link, struct in6_addr *ret); + +DEFINE_NETWORK_SECTION_FUNCTIONS(Address, address_free); + +int link_set_addresses(Link *link); +int link_drop_addresses(Link *link); +int link_drop_foreign_addresses(Link *link); +int link_serialize_addresses(Link *link, FILE *f); +int link_deserialize_addresses(Link *link, const char *addresses); + +void ipv4_dad_unref(Link *link); +int ipv4_dad_stop(Link *link); +int ipv4_dad_update_mac(Link *link); + +int manager_rtnl_process_address(sd_netlink *nl, sd_netlink_message *message, Manager *m); + +void network_drop_invalid_addresses(Network *network); + +void address_hash_func(const Address *a, struct siphash *state); +int address_compare_func(const Address *a1, const Address *a2); +extern const struct hash_ops address_hash_ops; + +CONFIG_PARSER_PROTOTYPE(config_parse_address); +CONFIG_PARSER_PROTOTYPE(config_parse_broadcast); +CONFIG_PARSER_PROTOTYPE(config_parse_label); +CONFIG_PARSER_PROTOTYPE(config_parse_lifetime); +CONFIG_PARSER_PROTOTYPE(config_parse_address_flags); +CONFIG_PARSER_PROTOTYPE(config_parse_address_scope); +CONFIG_PARSER_PROTOTYPE(config_parse_duplicate_address_detection); + +#define IPV4_ADDRESS_FMT_STR "%u.%u.%u.%u" +#define IPV4_ADDRESS_FMT_VAL(address) \ + be32toh((address).s_addr) >> 24, \ + (be32toh((address).s_addr) >> 16) & 0xFFu, \ + (be32toh((address).s_addr) >> 8) & 0xFFu, \ + be32toh((address).s_addr) & 0xFFu diff --git a/src/network/networkd-brvlan.c b/src/network/networkd-brvlan.c new file mode 100644 index 0000000..e53c73c --- /dev/null +++ b/src/network/networkd-brvlan.c @@ -0,0 +1,283 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2016 BISDN GmbH. All rights reserved. +***/ + +#include <netinet/in.h> +#include <linux/if_bridge.h> +#include <stdbool.h> + +#include "alloc-util.h" +#include "conf-parser.h" +#include "netlink-util.h" +#include "networkd-brvlan.h" +#include "networkd-link.h" +#include "networkd-manager.h" +#include "networkd-network.h" +#include "parse-util.h" +#include "vlan-util.h" + +static bool is_bit_set(unsigned bit, uint32_t scope) { + assert(bit < sizeof(scope)*8); + return scope & (UINT32_C(1) << bit); +} + +static void set_bit(unsigned nr, uint32_t *addr) { + if (nr < BRIDGE_VLAN_BITMAP_MAX) + addr[nr / 32] |= (UINT32_C(1) << (nr % 32)); +} + +static int find_next_bit(int i, uint32_t x) { + int j; + + if (i >= 32) + return -1; + + /* find first bit */ + if (i < 0) + return BUILTIN_FFS_U32(x); + + /* mask off prior finds to get next */ + j = __builtin_ffs(x >> i); + return j ? j + i : 0; +} + +static int append_vlan_info_data(Link *const link, sd_netlink_message *req, uint16_t pvid, const uint32_t *br_vid_bitmap, const uint32_t *br_untagged_bitmap) { + struct bridge_vlan_info br_vlan; + int i, j, k, r, cnt; + uint16_t begin, end; + bool done, untagged = false; + + assert(link); + assert(req); + assert(br_vid_bitmap); + assert(br_untagged_bitmap); + + cnt = 0; + + begin = end = UINT16_MAX; + for (k = 0; k < BRIDGE_VLAN_BITMAP_LEN; k++) { + unsigned base_bit; + uint32_t vid_map = br_vid_bitmap[k]; + uint32_t untagged_map = br_untagged_bitmap[k]; + + base_bit = k * 32; + i = -1; + done = false; + do { + j = find_next_bit(i, vid_map); + if (j > 0) { + /* first hit of any bit */ + if (begin == UINT16_MAX && end == UINT16_MAX) { + begin = end = j - 1 + base_bit; + untagged = is_bit_set(j - 1, untagged_map); + goto next; + } + + /* this bit is a continuation of prior bits */ + if (j - 2 + base_bit == end && untagged == is_bit_set(j - 1, untagged_map) && (uint16_t)j - 1 + base_bit != pvid && (uint16_t)begin != pvid) { + end++; + goto next; + } + } else + done = true; + + if (begin != UINT16_MAX) { + cnt++; + if (done && k < BRIDGE_VLAN_BITMAP_LEN - 1) + break; + + br_vlan.flags = 0; + if (untagged) + br_vlan.flags |= BRIDGE_VLAN_INFO_UNTAGGED; + + if (begin == end) { + br_vlan.vid = begin; + + if (begin == pvid) + br_vlan.flags |= BRIDGE_VLAN_INFO_PVID; + + r = sd_netlink_message_append_data(req, IFLA_BRIDGE_VLAN_INFO, &br_vlan, sizeof(br_vlan)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_BRIDGE_VLAN_INFO attribute: %m"); + } else { + br_vlan.vid = begin; + br_vlan.flags |= BRIDGE_VLAN_INFO_RANGE_BEGIN; + + r = sd_netlink_message_append_data(req, IFLA_BRIDGE_VLAN_INFO, &br_vlan, sizeof(br_vlan)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_BRIDGE_VLAN_INFO attribute: %m"); + + br_vlan.vid = end; + br_vlan.flags &= ~BRIDGE_VLAN_INFO_RANGE_BEGIN; + br_vlan.flags |= BRIDGE_VLAN_INFO_RANGE_END; + + r = sd_netlink_message_append_data(req, IFLA_BRIDGE_VLAN_INFO, &br_vlan, sizeof(br_vlan)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_BRIDGE_VLAN_INFO attribute: %m"); + } + + if (done) + break; + } + if (j > 0) { + begin = end = j - 1 + base_bit; + untagged = is_bit_set(j - 1, untagged_map); + } + + next: + i = j; + } while (!done); + } + + assert(cnt > 0); + return cnt; +} + +static int set_brvlan_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EEXIST) + log_link_message_warning_errno(link, m, r, "Could not add VLAN to bridge port"); + + return 1; +} + +int link_set_bridge_vlan(Link *link) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(link); + assert(link->manager); + assert(link->network); + + if (!link->network->use_br_vlan) + return 0; + + if (!link->network->bridge && !streq_ptr(link->kind, "bridge")) + return 0; + + /* pvid might not be in br_vid_bitmap yet */ + if (link->network->pvid) + set_bit(link->network->pvid, link->network->br_vid_bitmap); + + /* create new RTM message */ + r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m"); + + r = sd_rtnl_message_link_set_family(req, AF_BRIDGE); + if (r < 0) + return log_link_error_errno(link, r, "Could not set message family: %m"); + + r = sd_netlink_message_open_container(req, IFLA_AF_SPEC); + if (r < 0) + return log_link_error_errno(link, r, "Could not open IFLA_AF_SPEC container: %m"); + + /* master needs flag self */ + if (!link->network->bridge) { + uint16_t flags = BRIDGE_FLAGS_SELF; + r = sd_netlink_message_append_data(req, IFLA_BRIDGE_FLAGS, &flags, sizeof(flags)); + if (r < 0) + return log_link_error_errno(link, r, "Could not open IFLA_BRIDGE_FLAGS: %m"); + } + + /* add vlan info */ + r = append_vlan_info_data(link, req, link->network->pvid, link->network->br_vid_bitmap, link->network->br_untagged_bitmap); + if (r < 0) + return log_link_error_errno(link, r, "Could not append VLANs: %m"); + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not close IFLA_AF_SPEC container: %m"); + + /* send message to the kernel */ + r = netlink_call_async(link->manager->rtnl, NULL, req, set_brvlan_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + return 0; +} + +int config_parse_brvlan_pvid(const char *unit, const char *filename, + unsigned line, const char *section, + unsigned section_line, const char *lvalue, + int ltype, const char *rvalue, void *data, + void *userdata) { + Network *network = userdata; + uint16_t pvid; + int r; + + r = parse_vlanid(rvalue, &pvid); + if (r < 0) + return r; + + network->pvid = pvid; + network->use_br_vlan = true; + + return 0; +} + +int config_parse_brvlan_vlan(const char *unit, const char *filename, + unsigned line, const char *section, + unsigned section_line, const char *lvalue, + int ltype, const char *rvalue, void *data, + void *userdata) { + Network *network = userdata; + uint16_t vid, vid_end; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = parse_vid_range(rvalue, &vid, &vid_end); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse VLAN, ignoring: %s", rvalue); + return 0; + } + + for (; vid <= vid_end; vid++) + set_bit(vid, network->br_vid_bitmap); + + network->use_br_vlan = true; + return 0; +} + +int config_parse_brvlan_untagged(const char *unit, const char *filename, + unsigned line, const char *section, + unsigned section_line, const char *lvalue, + int ltype, const char *rvalue, void *data, + void *userdata) { + Network *network = userdata; + int r; + uint16_t vid, vid_end; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = parse_vid_range(rvalue, &vid, &vid_end); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Could not parse VLAN: %s", rvalue); + return 0; + } + + for (; vid <= vid_end; vid++) { + set_bit(vid, network->br_vid_bitmap); + set_bit(vid, network->br_untagged_bitmap); + } + + network->use_br_vlan = true; + return 0; +} diff --git a/src/network/networkd-brvlan.h b/src/network/networkd-brvlan.h new file mode 100644 index 0000000..938b790 --- /dev/null +++ b/src/network/networkd-brvlan.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/*** + Copyright © 2016 BISDN GmbH. All rights reserved. +***/ + +#include "conf-parser.h" + +#define BRIDGE_VLAN_BITMAP_MAX 4096 +#define BRIDGE_VLAN_BITMAP_LEN (BRIDGE_VLAN_BITMAP_MAX / 32) + +typedef struct Link Link; + +int link_set_bridge_vlan(Link *link); + +CONFIG_PARSER_PROTOTYPE(config_parse_brvlan_pvid); +CONFIG_PARSER_PROTOTYPE(config_parse_brvlan_vlan); +CONFIG_PARSER_PROTOTYPE(config_parse_brvlan_untagged); diff --git a/src/network/networkd-can.c b/src/network/networkd-can.c new file mode 100644 index 0000000..7e31d2f --- /dev/null +++ b/src/network/networkd-can.c @@ -0,0 +1,315 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <net/if.h> +#include <linux/can/netlink.h> + +#include "netlink-util.h" +#include "networkd-can.h" +#include "networkd-link.h" +#include "networkd-manager.h" +#include "parse-util.h" +#include "string-util.h" + +#define CAN_TERMINATION_OHM_VALUE 120 + +int config_parse_can_bitrate( + const char* unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + uint32_t *br = data; + uint64_t sz; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = parse_size(rvalue, 1000, &sz); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse can bitrate '%s', ignoring: %m", rvalue); + return 0; + } + + /* Linux uses __u32 for bitrates, so the value should not exceed that. */ + if (sz <= 0 || sz > UINT32_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Bit rate out of permitted range 1...4294967295"); + return 0; + } + + *br = (uint32_t) sz; + + return 0; +} + +static int link_up_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0) + /* we warn but don't fail the link, as it may be brought up later */ + log_link_message_warning_errno(link, m, r, "Could not bring up interface"); + + return 1; +} + +static int link_up_can(Link *link) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(link); + + log_link_debug(link, "Bringing CAN link up"); + + r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m"); + + r = sd_rtnl_message_link_set_flags(req, IFF_UP, IFF_UP); + if (r < 0) + return log_link_error_errno(link, r, "Could not set link flags: %m"); + + r = netlink_call_async(link->manager->rtnl, NULL, req, link_up_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + return 0; +} + +static int link_set_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + + log_link_debug(link, "Set link"); + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EEXIST) { + log_link_message_warning_errno(link, m, r, "Failed to configure CAN link"); + link_enter_failed(link); + } + + return 1; +} + +static int link_set_can(Link *link) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + struct can_ctrlmode cm = {}; + int r; + + assert(link); + assert(link->network); + assert(link->manager); + assert(link->manager->rtnl); + + log_link_debug(link, "Configuring CAN link."); + + r = sd_rtnl_message_new_link(link->manager->rtnl, &m, RTM_NEWLINK, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Failed to allocate netlink message: %m"); + + r = sd_netlink_message_set_flags(m, NLM_F_REQUEST | NLM_F_ACK); + if (r < 0) + return log_link_error_errno(link, r, "Could not set netlink flags: %m"); + + r = sd_netlink_message_open_container(m, IFLA_LINKINFO); + if (r < 0) + return log_link_error_errno(link, r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, link->kind); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_INFO_DATA attribute: %m"); + + if (link->network->can_bitrate > 0 || link->network->can_sample_point > 0) { + struct can_bittiming bt = { + .bitrate = link->network->can_bitrate, + .sample_point = link->network->can_sample_point, + }; + + log_link_debug(link, "Setting bitrate = %d bit/s", bt.bitrate); + if (link->network->can_sample_point > 0) + log_link_debug(link, "Setting sample point = %d.%d%%", bt.sample_point / 10, bt.sample_point % 10); + else + log_link_debug(link, "Using default sample point"); + + r = sd_netlink_message_append_data(m, IFLA_CAN_BITTIMING, &bt, sizeof(bt)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_CAN_BITTIMING attribute: %m"); + } + + if (link->network->can_data_bitrate > 0 || link->network->can_data_sample_point > 0) { + struct can_bittiming bt = { + .bitrate = link->network->can_data_bitrate, + .sample_point = link->network->can_data_sample_point, + }; + + log_link_debug(link, "Setting data bitrate = %d bit/s", bt.bitrate); + if (link->network->can_data_sample_point > 0) + log_link_debug(link, "Setting data sample point = %d.%d%%", bt.sample_point / 10, bt.sample_point % 10); + else + log_link_debug(link, "Using default data sample point"); + + r = sd_netlink_message_append_data(m, IFLA_CAN_DATA_BITTIMING, &bt, sizeof(bt)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_CAN_DATA_BITTIMING attribute: %m"); + } + + if (link->network->can_fd_mode >= 0) { + cm.mask |= CAN_CTRLMODE_FD; + SET_FLAG(cm.flags, CAN_CTRLMODE_FD, link->network->can_fd_mode > 0); + log_link_debug(link, "%sabling FD mode", link->network->can_fd_mode > 0 ? "En" : "Dis"); + } + + if (link->network->can_non_iso >= 0) { + cm.mask |= CAN_CTRLMODE_FD_NON_ISO; + SET_FLAG(cm.flags, CAN_CTRLMODE_FD_NON_ISO, link->network->can_non_iso > 0); + log_link_debug(link, "%sabling FD non-ISO mode", link->network->can_non_iso > 0 ? "En" : "Dis"); + } + + if (link->network->can_restart_us > 0) { + char time_string[FORMAT_TIMESPAN_MAX]; + uint64_t restart_ms; + + if (link->network->can_restart_us == USEC_INFINITY) + restart_ms = 0; + else + restart_ms = DIV_ROUND_UP(link->network->can_restart_us, USEC_PER_MSEC); + + format_timespan(time_string, FORMAT_TIMESPAN_MAX, restart_ms * 1000, MSEC_PER_SEC); + + if (restart_ms > UINT32_MAX) { + log_link_error(link, "restart timeout (%s) too big.", time_string); + return -ERANGE; + } + + log_link_debug(link, "Setting restart = %s", time_string); + + r = sd_netlink_message_append_u32(m, IFLA_CAN_RESTART_MS, restart_ms); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_CAN_RESTART_MS attribute: %m"); + } + + if (link->network->can_triple_sampling >= 0) { + cm.mask |= CAN_CTRLMODE_3_SAMPLES; + SET_FLAG(cm.flags, CAN_CTRLMODE_3_SAMPLES, link->network->can_triple_sampling); + log_link_debug(link, "%sabling triple-sampling", link->network->can_triple_sampling ? "En" : "Dis"); + } + + if (link->network->can_listen_only >= 0) { + cm.mask |= CAN_CTRLMODE_LISTENONLY; + SET_FLAG(cm.flags, CAN_CTRLMODE_LISTENONLY, link->network->can_listen_only); + log_link_debug(link, "%sabling listen-only mode", link->network->can_listen_only ? "En" : "Dis"); + } + + if (cm.mask != 0) { + r = sd_netlink_message_append_data(m, IFLA_CAN_CTRLMODE, &cm, sizeof(cm)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_CAN_CTRLMODE attribute: %m"); + } + + if (link->network->can_termination >= 0) { + + log_link_debug(link, "%sabling can-termination", link->network->can_termination ? "En" : "Dis"); + + r = sd_netlink_message_append_u16(m, IFLA_CAN_TERMINATION, + link->network->can_termination ? CAN_TERMINATION_OHM_VALUE : 0); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_CAN_TERMINATION attribute: %m"); + + } + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_link_error_errno(link, r, "Failed to close netlink container: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_link_error_errno(link, r, "Failed to close netlink container: %m"); + + r = netlink_call_async(link->manager->rtnl, NULL, m, link_set_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + if (!(link->flags & IFF_UP)) + return link_up_can(link); + + return 0; +} + +static int link_down_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0) { + log_link_message_warning_errno(link, m, r, "Could not bring down interface"); + link_enter_failed(link); + return 1; + } + + r = link_set_can(link); + if (r < 0) + link_enter_failed(link); + + return 1; +} + +int link_configure_can(Link *link) { + int r; + + link_set_state(link, LINK_STATE_CONFIGURING); + + if (streq_ptr(link->kind, "can")) { + /* The CAN interface must be down to configure bitrate, etc... */ + if ((link->flags & IFF_UP)) { + r = link_down(link, link_down_handler); + if (r < 0) { + link_enter_failed(link); + return r; + } + } else { + r = link_set_can(link); + if (r < 0) { + link_enter_failed(link); + return r; + } + } + + return 0; + } + + if (!(link->flags & IFF_UP)) { + r = link_up_can(link); + if (r < 0) { + link_enter_failed(link); + return r; + } + } + + return 0; +} diff --git a/src/network/networkd-can.h b/src/network/networkd-can.h new file mode 100644 index 0000000..7a2705b --- /dev/null +++ b/src/network/networkd-can.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "conf-parser.h" + +typedef struct Link Link; + +int link_configure_can(Link *link); + +CONFIG_PARSER_PROTOTYPE(config_parse_can_bitrate); diff --git a/src/network/networkd-conf.c b/src/network/networkd-conf.c new file mode 100644 index 0000000..bf51624 --- /dev/null +++ b/src/network/networkd-conf.c @@ -0,0 +1,191 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2014 Vinay Kulkarni <kulkarniv@vmware.com> + ***/ + +#include <ctype.h> +#include <netinet/ip.h> + +#include "conf-parser.h" +#include "def.h" +#include "dhcp-identifier.h" +#include "extract-word.h" +#include "hexdecoct.h" +#include "networkd-conf.h" +#include "networkd-manager.h" +#include "networkd-network.h" +#include "networkd-speed-meter.h" +#include "networkd-dhcp4.h" +#include "string-table.h" + +int manager_parse_config_file(Manager *m) { + int r; + + assert(m); + + r = config_parse_many_nulstr( + PKGSYSCONFDIR "/networkd.conf", + CONF_PATHS_NULSTR("systemd/networkd.conf.d"), + "Network\0" + "DHCP\0", + config_item_perf_lookup, networkd_gperf_lookup, + CONFIG_PARSE_WARN, + m, + NULL); + if (r < 0) + return r; + + if (m->use_speed_meter && m->speed_meter_interval_usec < SPEED_METER_MINIMUM_TIME_INTERVAL) { + char buf[FORMAT_TIMESPAN_MAX]; + + log_warning("SpeedMeterIntervalSec= is too small, using %s.", + format_timespan(buf, sizeof buf, SPEED_METER_MINIMUM_TIME_INTERVAL, USEC_PER_SEC)); + m->speed_meter_interval_usec = SPEED_METER_MINIMUM_TIME_INTERVAL; + } + + return 0; +} + +static const char* const duid_type_table[_DUID_TYPE_MAX] = { + [DUID_TYPE_LLT] = "link-layer-time", + [DUID_TYPE_EN] = "vendor", + [DUID_TYPE_LL] = "link-layer", + [DUID_TYPE_UUID] = "uuid", +}; +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(duid_type, DUIDType); + +int config_parse_duid_type( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_free_ char *type_string = NULL; + const char *p = rvalue; + DUID *duid = data; + DUIDType type; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(duid); + + r = extract_first_word(&p, &type_string, ":", 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid syntax, ignoring: %s", rvalue); + return 0; + } + if (r == 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Failed to extract DUID type from '%s', ignoring.", rvalue); + return 0; + } + + type = duid_type_from_string(type_string); + if (type < 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Failed to parse DUID type '%s', ignoring.", type_string); + return 0; + } + + if (!isempty(p)) { + usec_t u; + + if (type != DUID_TYPE_LLT) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid syntax, ignoring: %s", rvalue); + return 0; + } + + r = parse_timestamp(p, &u); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse timestamp, ignoring: %s", p); + return 0; + } + + duid->llt_time = u; + } + + duid->type = type; + + return 0; +} + +int config_parse_duid_rawdata( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + DUID *ret = data; + uint8_t raw_data[MAX_DUID_LEN]; + unsigned count = 0; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(ret); + + /* RawData contains DUID in format "NN:NN:NN..." */ + for (const char *p = rvalue;;) { + int n1, n2, len, r; + uint32_t byte; + _cleanup_free_ char *cbyte = NULL; + + r = extract_first_word(&p, &cbyte, ":", 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to read DUID, ignoring assignment: %s.", rvalue); + return 0; + } + if (r == 0) + break; + + if (count >= MAX_DUID_LEN) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Max DUID length exceeded, ignoring assignment: %s.", rvalue); + return 0; + } + + len = strlen(cbyte); + if (!IN_SET(len, 1, 2)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid length - DUID byte: %s, ignoring assignment: %s.", cbyte, rvalue); + return 0; + } + n1 = unhexchar(cbyte[0]); + if (len == 2) + n2 = unhexchar(cbyte[1]); + else + n2 = 0; + + if (n1 < 0 || n2 < 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid DUID byte: %s. Ignoring assignment: %s.", cbyte, rvalue); + return 0; + } + + byte = ((uint8_t) n1 << (4 * (len-1))) | (uint8_t) n2; + raw_data[count++] = byte; + } + + assert_cc(sizeof(raw_data) == sizeof(ret->raw_data)); + memcpy(ret->raw_data, raw_data, count); + ret->raw_data_len = count; + return 0; +} diff --git a/src/network/networkd-conf.h b/src/network/networkd-conf.h new file mode 100644 index 0000000..b485e9e --- /dev/null +++ b/src/network/networkd-conf.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/*** + Copyright © 2014 Vinay Kulkarni <kulkarniv@vmware.com> +***/ + +#include "conf-parser.h" + +typedef struct Manager Manager; + +int manager_parse_config_file(Manager *m); + +const struct ConfigPerfItem* networkd_gperf_lookup(const char *key, GPERF_LEN_TYPE length); + +CONFIG_PARSER_PROTOTYPE(config_parse_duid_type); +CONFIG_PARSER_PROTOTYPE(config_parse_duid_rawdata); diff --git a/src/network/networkd-dhcp-common.c b/src/network/networkd-dhcp-common.c new file mode 100644 index 0000000..9f58121 --- /dev/null +++ b/src/network/networkd-dhcp-common.c @@ -0,0 +1,935 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <netinet/in.h> +#include <linux/if_arp.h> + +#include "dhcp-internal.h" +#include "dhcp6-internal.h" +#include "escape.h" +#include "in-addr-util.h" +#include "networkd-dhcp-common.h" +#include "networkd-link.h" +#include "networkd-manager.h" +#include "networkd-network.h" +#include "parse-util.h" +#include "socket-util.h" +#include "string-table.h" +#include "strv.h" + +bool link_dhcp_enabled(Link *link, int family) { + assert(link); + assert(IN_SET(family, AF_INET, AF_INET6)); + + if (family == AF_INET6 && !socket_ipv6_is_supported()) + return false; + + if (link->flags & IFF_LOOPBACK) + return false; + + if (link->iftype == ARPHRD_CAN) + return false; + + if (!link->network) + return false; + + return link->network->dhcp & (family == AF_INET ? ADDRESS_FAMILY_IPV4 : ADDRESS_FAMILY_IPV6); +} + +void network_adjust_dhcp(Network *network) { + assert(network); + assert(network->dhcp >= 0); + + if (network->dhcp == ADDRESS_FAMILY_NO) + return; + + /* Bonding slave does not support addressing. */ + if (network->bond) { + log_warning("%s: Cannot enable DHCP= when Bond= is specified, disabling DHCP=.", + network->filename); + network->dhcp = ADDRESS_FAMILY_NO; + return; + } + + if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6) && + FLAGS_SET(network->dhcp, ADDRESS_FAMILY_IPV6)) { + log_warning("%s: DHCPv6 client is enabled but IPv6 link local addressing is disabled. " + "Disabling DHCPv6 client.", network->filename); + SET_FLAG(network->dhcp, ADDRESS_FAMILY_IPV6, false); + } +} + +static struct DUID fallback_duid = { .type = DUID_TYPE_EN }; +DUID* link_get_duid(Link *link) { + if (link->network->duid.type != _DUID_TYPE_INVALID) + return &link->network->duid; + else if (link->hw_addr.length == 0 && + (link->manager->duid.type == DUID_TYPE_LLT || + link->manager->duid.type == DUID_TYPE_LL)) + /* Fallback to DUID that works without mac addresses. + * This is useful for tunnel devices without mac address. */ + return &fallback_duid; + else + return &link->manager->duid; +} + +static int duid_set_uuid(DUID *duid, sd_id128_t uuid) { + assert(duid); + + if (duid->raw_data_len > 0) + return 0; + + if (duid->type != DUID_TYPE_UUID) + return -EINVAL; + + memcpy(&duid->raw_data, &uuid, sizeof(sd_id128_t)); + duid->raw_data_len = sizeof(sd_id128_t); + + return 1; +} + +static int get_product_uuid_handler(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + Manager *manager = userdata; + const sd_bus_error *e; + const void *a; + size_t sz; + DUID *duid; + Link *link; + int r; + + assert(m); + assert(manager); + + e = sd_bus_message_get_error(m); + if (e) { + log_error_errno(sd_bus_error_get_errno(e), + "Could not get product UUID. Falling back to use machine-app-specific ID as DUID-UUID: %s", + e->message); + goto configure; + } + + r = sd_bus_message_read_array(m, 'y', &a, &sz); + if (r < 0) + goto configure; + + if (sz != sizeof(sd_id128_t)) { + log_error("Invalid product UUID. Falling back to use machine-app-specific ID as DUID-UUID."); + goto configure; + } + + memcpy(&manager->product_uuid, a, sz); + while ((duid = set_steal_first(manager->duids_requesting_uuid))) + (void) duid_set_uuid(duid, manager->product_uuid); + + manager->duids_requesting_uuid = set_free(manager->duids_requesting_uuid); + +configure: + while ((link = set_steal_first(manager->links_requesting_uuid))) { + link_unref(link); + + r = link_configure(link); + if (r < 0) + link_enter_failed(link); + } + + manager->links_requesting_uuid = set_free(manager->links_requesting_uuid); + + /* To avoid calling GetProductUUID() bus method so frequently, set the flag below + * even if the method fails. */ + manager->has_product_uuid = true; + + return 1; +} + +int manager_request_product_uuid(Manager *m, Link *link) { + int r; + + assert(m); + + if (m->has_product_uuid) + return 0; + + log_debug("Requesting product UUID"); + + if (link) { + DUID *duid; + + assert_se(duid = link_get_duid(link)); + + r = set_ensure_put(&m->links_requesting_uuid, NULL, link); + if (r < 0) + return log_oom(); + if (r > 0) + link_ref(link); + + r = set_ensure_put(&m->duids_requesting_uuid, NULL, duid); + if (r < 0) + return log_oom(); + } + + if (!m->bus || sd_bus_is_ready(m->bus) <= 0) { + log_debug("Not connected to system bus, requesting product UUID later."); + return 0; + } + + r = sd_bus_call_method_async( + m->bus, + NULL, + "org.freedesktop.hostname1", + "/org/freedesktop/hostname1", + "org.freedesktop.hostname1", + "GetProductUUID", + get_product_uuid_handler, + m, + "b", + false); + if (r < 0) + return log_warning_errno(r, "Failed to get product UUID: %m"); + + return 0; +} + +static bool link_requires_uuid(Link *link) { + const DUID *duid; + + assert(link); + assert(link->manager); + assert(link->network); + + duid = link_get_duid(link); + if (duid->type != DUID_TYPE_UUID || duid->raw_data_len != 0) + return false; + + if (link_dhcp4_enabled(link) && IN_SET(link->network->dhcp_client_identifier, DHCP_CLIENT_ID_DUID, DHCP_CLIENT_ID_DUID_ONLY)) + return true; + + if (link_dhcp6_enabled(link) || link_ipv6_accept_ra_enabled(link)) + return true; + + return false; +} + +int link_configure_duid(Link *link) { + Manager *m; + DUID *duid; + int r; + + assert(link); + assert(link->manager); + assert(link->network); + + m = link->manager; + duid = link_get_duid(link); + + if (!link_requires_uuid(link)) + return 1; + + if (m->has_product_uuid) { + (void) duid_set_uuid(duid, m->product_uuid); + return 1; + } + + if (!m->links_requesting_uuid) { + r = manager_request_product_uuid(m, link); + if (r < 0) { + if (r == -ENOMEM) + return r; + + log_link_warning_errno(link, r, + "Failed to get product UUID. Falling back to use machine-app-specific ID as DUID-UUID: %m"); + return 1; + } + } else { + r = set_put(m->links_requesting_uuid, link); + if (r < 0) + return log_oom(); + if (r > 0) + link_ref(link); + + r = set_put(m->duids_requesting_uuid, duid); + if (r < 0) + return log_oom(); + } + + return 0; +} + +int config_parse_dhcp( + const char* unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + AddressFamily *dhcp = data, s; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + /* Note that this is mostly like + * config_parse_address_family(), except that it + * understands some old names for the enum values */ + + s = address_family_from_string(rvalue); + if (s < 0) { + + /* Previously, we had a slightly different enum here, + * support its values for compatibility. */ + + if (streq(rvalue, "none")) + s = ADDRESS_FAMILY_NO; + else if (streq(rvalue, "v4")) + s = ADDRESS_FAMILY_IPV4; + else if (streq(rvalue, "v6")) + s = ADDRESS_FAMILY_IPV6; + else if (streq(rvalue, "both")) + s = ADDRESS_FAMILY_YES; + else { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Failed to parse DHCP option, ignoring: %s", rvalue); + return 0; + } + + log_syntax(unit, LOG_WARNING, filename, line, 0, + "DHCP=%s is deprecated, please use DHCP=%s instead.", + rvalue, address_family_to_string(s)); + } + + *dhcp = s; + return 0; +} + +int config_parse_dhcp_route_metric( + const char* unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = data; + uint32_t metric; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = safe_atou32(rvalue, &metric); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse RouteMetric=%s, ignoring assignment: %m", rvalue); + return 0; + } + + if (streq_ptr(section, "DHCPv4")) { + network->dhcp_route_metric = metric; + network->dhcp_route_metric_set = true; + } else if (streq_ptr(section, "DHCPv6")) { + network->dhcp6_route_metric = metric; + network->dhcp6_route_metric_set = true; + } else { /* [DHCP] section */ + if (!network->dhcp_route_metric_set) + network->dhcp_route_metric = metric; + if (!network->dhcp6_route_metric_set) + network->dhcp6_route_metric = metric; + } + + return 0; +} + +int config_parse_dhcp_use_dns( + const char* unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse UseDNS=%s, ignoring assignment: %m", rvalue); + return 0; + } + + if (streq_ptr(section, "DHCPv4")) { + network->dhcp_use_dns = r; + network->dhcp_use_dns_set = true; + } else if (streq_ptr(section, "DHCPv6")) { + network->dhcp6_use_dns = r; + network->dhcp6_use_dns_set = true; + } else { /* [DHCP] section */ + if (!network->dhcp_use_dns_set) + network->dhcp_use_dns = r; + if (!network->dhcp6_use_dns_set) + network->dhcp6_use_dns = r; + } + + return 0; +} + +int config_parse_dhcp_use_ntp( + const char* unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse UseNTP=%s, ignoring assignment: %m", rvalue); + return 0; + } + + if (streq_ptr(section, "DHCPv4")) { + network->dhcp_use_ntp = r; + network->dhcp_use_ntp_set = true; + } else if (streq_ptr(section, "DHCPv6")) { + network->dhcp6_use_ntp = r; + network->dhcp6_use_ntp_set = true; + } else { /* [DHCP] section */ + if (!network->dhcp_use_ntp_set) + network->dhcp_use_ntp = r; + if (!network->dhcp6_use_ntp_set) + network->dhcp6_use_ntp = r; + } + + return 0; +} + +int config_parse_section_route_table( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = data; + uint32_t rt; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = safe_atou32(rvalue, &rt); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse RouteTable=%s, ignoring assignment: %m", rvalue); + return 0; + } + + if (STRPTR_IN_SET(section, "DHCP", "DHCPv4")) { + network->dhcp_route_table = rt; + network->dhcp_route_table_set = true; + } else { /* section is IPv6AcceptRA */ + network->ipv6_accept_ra_route_table = rt; + network->ipv6_accept_ra_route_table_set = true; + } + + return 0; +} + +int config_parse_iaid(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + Network *network = data; + uint32_t iaid; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(network); + + r = safe_atou32(rvalue, &iaid); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Unable to read IAID, ignoring assignment: %s", rvalue); + return 0; + } + + network->iaid = iaid; + network->iaid_set = true; + + return 0; +} + +int config_parse_dhcp_user_class( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + char ***l = data; + int r; + + assert(l); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *l = strv_free(*l); + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *w = NULL; + size_t len; + + r = extract_first_word(&p, &w, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to split user classes option, ignoring: %s", rvalue); + return 0; + } + if (r == 0) + return 0; + + len = strlen(w); + if (ltype == AF_INET) { + if (len > UINT8_MAX || len == 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "%s length is not in the range 1-255, ignoring.", w); + continue; + } + } else { + if (len > UINT16_MAX || len == 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "%s length is not in the range 1-65535, ignoring.", w); + continue; + } + } + + r = strv_consume(l, TAKE_PTR(w)); + if (r < 0) + return log_oom(); + } +} + +int config_parse_dhcp_vendor_class( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + char ***l = data; + int r; + + assert(l); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *l = strv_free(*l); + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *w = NULL; + + r = extract_first_word(&p, &w, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to split vendor classes option, ignoring: %s", rvalue); + return 0; + } + if (r == 0) + return 0; + + if (strlen(w) > UINT8_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "%s length is not in the range 1-255, ignoring.", w); + continue; + } + + r = strv_push(l, w); + if (r < 0) + return log_oom(); + + w = NULL; + } +} + +int config_parse_dhcp_send_option( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(sd_dhcp_option_unrefp) sd_dhcp_option *opt4 = NULL, *old4 = NULL; + _cleanup_(sd_dhcp6_option_unrefp) sd_dhcp6_option *opt6 = NULL, *old6 = NULL; + uint32_t uint32_data, enterprise_identifier = 0; + _cleanup_free_ char *word = NULL, *q = NULL; + OrderedHashmap **options = data; + uint16_t u16, uint16_data; + union in_addr_union addr; + DHCPOptionDataType type; + uint8_t u8, uint8_data; + const void *udata; + const char *p; + ssize_t sz; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + *options = ordered_hashmap_free(*options); + return 0; + } + + p = rvalue; + if (ltype == AF_INET6 && streq(lvalue, "SendVendorOption")) { + r = extract_first_word(&p, &word, ":", 0); + if (r == -ENOMEM) + return log_oom(); + if (r <= 0 || isempty(p)) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid DHCP option, ignoring assignment: %s", rvalue); + return 0; + } + + r = safe_atou32(word, &enterprise_identifier); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse DHCPv6 enterprise identifier data, ignoring assignment: %s", p); + return 0; + } + word = mfree(word); + } + + r = extract_first_word(&p, &word, ":", 0); + if (r == -ENOMEM) + return log_oom(); + if (r <= 0 || isempty(p)) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid DHCP option, ignoring assignment: %s", rvalue); + return 0; + } + + if (ltype == AF_INET6) { + r = safe_atou16(word, &u16); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid DHCP option, ignoring assignment: %s", rvalue); + return 0; + } + if (u16 < 1 || u16 >= UINT16_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid DHCP option, valid range is 1-65535, ignoring assignment: %s", rvalue); + return 0; + } + } else { + r = safe_atou8(word, &u8); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid DHCP option, ignoring assignment: %s", rvalue); + return 0; + } + if (u8 < 1 || u8 >= UINT8_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid DHCP option, valid range is 1-254, ignoring assignment: %s", rvalue); + return 0; + } + } + + word = mfree(word); + r = extract_first_word(&p, &word, ":", 0); + if (r == -ENOMEM) + return log_oom(); + if (r <= 0 || isempty(p)) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid DHCP option, ignoring assignment: %s", rvalue); + return 0; + } + + type = dhcp_option_data_type_from_string(word); + if (type < 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid DHCP option data type, ignoring assignment: %s", p); + return 0; + } + + switch(type) { + case DHCP_OPTION_DATA_UINT8:{ + r = safe_atou8(p, &uint8_data); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse DHCP uint8 data, ignoring assignment: %s", p); + return 0; + } + + udata = &uint8_data; + sz = sizeof(uint8_t); + break; + } + case DHCP_OPTION_DATA_UINT16:{ + r = safe_atou16(p, &uint16_data); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse DHCP uint16 data, ignoring assignment: %s", p); + return 0; + } + + udata = &uint16_data; + sz = sizeof(uint16_t); + break; + } + case DHCP_OPTION_DATA_UINT32: { + r = safe_atou32(p, &uint32_data); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse DHCP uint32 data, ignoring assignment: %s", p); + return 0; + } + + udata = &uint32_data; + sz = sizeof(uint32_t); + + break; + } + case DHCP_OPTION_DATA_IPV4ADDRESS: { + r = in_addr_from_string(AF_INET, p, &addr); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse DHCP ipv4address data, ignoring assignment: %s", p); + return 0; + } + + udata = &addr.in; + sz = sizeof(addr.in.s_addr); + break; + } + case DHCP_OPTION_DATA_IPV6ADDRESS: { + r = in_addr_from_string(AF_INET6, p, &addr); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse DHCP ipv6address data, ignoring assignment: %s", p); + return 0; + } + + udata = &addr.in6; + sz = sizeof(addr.in6.s6_addr); + break; + } + case DHCP_OPTION_DATA_STRING: + sz = cunescape(p, UNESCAPE_ACCEPT_NUL, &q); + if (sz < 0) + log_syntax(unit, LOG_WARNING, filename, line, sz, + "Failed to decode DHCP option data, ignoring assignment: %s", p); + + udata = q; + break; + default: + return -EINVAL; + } + + if (ltype == AF_INET6) { + r = sd_dhcp6_option_new(u16, udata, sz, enterprise_identifier, &opt6); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue); + return 0; + } + + r = ordered_hashmap_ensure_allocated(options, &dhcp6_option_hash_ops); + if (r < 0) + return log_oom(); + + /* Overwrite existing option */ + old6 = ordered_hashmap_get(*options, UINT_TO_PTR(u16)); + r = ordered_hashmap_replace(*options, UINT_TO_PTR(u16), opt6); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue); + return 0; + } + TAKE_PTR(opt6); + } else { + r = sd_dhcp_option_new(u8, udata, sz, &opt4); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue); + return 0; + } + + r = ordered_hashmap_ensure_allocated(options, &dhcp_option_hash_ops); + if (r < 0) + return log_oom(); + + /* Overwrite existing option */ + old4 = ordered_hashmap_get(*options, UINT_TO_PTR(u8)); + r = ordered_hashmap_replace(*options, UINT_TO_PTR(u8), opt4); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue); + return 0; + } + TAKE_PTR(opt4); + } + return 0; +} + +int config_parse_dhcp_request_options( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = data; + const char *p; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + if (ltype == AF_INET) + network->dhcp_request_options = set_free(network->dhcp_request_options); + else + network->dhcp6_request_options = set_free(network->dhcp6_request_options); + + return 0; + } + + for (p = rvalue;;) { + _cleanup_free_ char *n = NULL; + uint32_t i; + + r = extract_first_word(&p, &n, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse DHCP request option, ignoring assignment: %s", + rvalue); + return 0; + } + if (r == 0) + return 0; + + r = safe_atou32(n, &i); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "DHCP request option is invalid, ignoring assignment: %s", n); + continue; + } + + if (i < 1 || i >= UINT8_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "DHCP request option is invalid, valid range is 1-254, ignoring assignment: %s", n); + continue; + } + + r = set_ensure_put(ltype == AF_INET ? &network->dhcp_request_options : &network->dhcp6_request_options, + NULL, UINT32_TO_PTR(i)); + if (r < 0) + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to store DHCP request option '%s', ignoring assignment: %m", n); + } +} + +DEFINE_CONFIG_PARSE_ENUM(config_parse_dhcp_use_domains, dhcp_use_domains, DHCPUseDomains, + "Failed to parse DHCP use domains setting"); + +static const char* const dhcp_use_domains_table[_DHCP_USE_DOMAINS_MAX] = { + [DHCP_USE_DOMAINS_NO] = "no", + [DHCP_USE_DOMAINS_ROUTE] = "route", + [DHCP_USE_DOMAINS_YES] = "yes", +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dhcp_use_domains, DHCPUseDomains, DHCP_USE_DOMAINS_YES); + +static const char * const dhcp_option_data_type_table[_DHCP_OPTION_DATA_MAX] = { + [DHCP_OPTION_DATA_UINT8] = "uint8", + [DHCP_OPTION_DATA_UINT16] = "uint16", + [DHCP_OPTION_DATA_UINT32] = "uint32", + [DHCP_OPTION_DATA_STRING] = "string", + [DHCP_OPTION_DATA_IPV4ADDRESS] = "ipv4address", + [DHCP_OPTION_DATA_IPV6ADDRESS] = "ipv6address", +}; + +DEFINE_STRING_TABLE_LOOKUP(dhcp_option_data_type, DHCPOptionDataType); diff --git a/src/network/networkd-dhcp-common.h b/src/network/networkd-dhcp-common.h new file mode 100644 index 0000000..78c149e --- /dev/null +++ b/src/network/networkd-dhcp-common.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "conf-parser.h" +#include "dhcp-identifier.h" +#include "time-util.h" + +#define DHCP_ROUTE_METRIC 1024 + +typedef struct Link Link; +typedef struct Manager Manager; +typedef struct Network Network; + +typedef enum DHCPUseDomains { + DHCP_USE_DOMAINS_NO, + DHCP_USE_DOMAINS_YES, + DHCP_USE_DOMAINS_ROUTE, + _DHCP_USE_DOMAINS_MAX, + _DHCP_USE_DOMAINS_INVALID = -1, +} DHCPUseDomains; + +typedef enum DHCPOptionDataType { + DHCP_OPTION_DATA_UINT8, + DHCP_OPTION_DATA_UINT16, + DHCP_OPTION_DATA_UINT32, + DHCP_OPTION_DATA_STRING, + DHCP_OPTION_DATA_IPV4ADDRESS, + DHCP_OPTION_DATA_IPV6ADDRESS, + _DHCP_OPTION_DATA_MAX, + _DHCP_OPTION_DATA_INVALID, +} DHCPOptionDataType; + +typedef struct DUID { + /* Value of Type in [DHCP] section */ + DUIDType type; + + uint8_t raw_data_len; + uint8_t raw_data[MAX_DUID_LEN]; + usec_t llt_time; +} DUID; + +bool link_dhcp_enabled(Link *link, int family); +static inline bool link_dhcp4_enabled(Link *link) { + return link_dhcp_enabled(link, AF_INET); +} +static inline bool link_dhcp6_enabled(Link *link) { + return link_dhcp_enabled(link, AF_INET6); +} + +void network_adjust_dhcp(Network *network); + +DUID* link_get_duid(Link *link); +int link_configure_duid(Link *link); +int manager_request_product_uuid(Manager *m, Link *link); + +const char* dhcp_use_domains_to_string(DHCPUseDomains p) _const_; +DHCPUseDomains dhcp_use_domains_from_string(const char *s) _pure_; + +const char *dhcp_option_data_type_to_string(DHCPOptionDataType d) _const_; +DHCPOptionDataType dhcp_option_data_type_from_string(const char *d) _pure_; + +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_route_metric); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_use_dns); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_use_domains); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_use_ntp); +CONFIG_PARSER_PROTOTYPE(config_parse_iaid); +CONFIG_PARSER_PROTOTYPE(config_parse_section_route_table); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_user_class); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_vendor_class); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_send_option); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_request_options); diff --git a/src/network/networkd-dhcp-server-bus.c b/src/network/networkd-dhcp-server-bus.c new file mode 100644 index 0000000..32f4bae --- /dev/null +++ b/src/network/networkd-dhcp-server-bus.c @@ -0,0 +1,110 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-dhcp-server.h" + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-util.h" +#include "dhcp-server-internal.h" +#include "networkd-dhcp-server-bus.h" +#include "networkd-link-bus.h" +#include "networkd-manager.h" +#include "strv.h" + +static int property_get_leases( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + Link *l = userdata; + sd_dhcp_server *s; + DHCPLease *lease; + int r; + + assert(reply); + assert(l); + + s = l->dhcp_server; + if (!s) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Link %s has no DHCP server.", l->ifname); + + r = sd_bus_message_open_container(reply, 'a', "(uayayayayt)"); + if (r < 0) + return r; + + HASHMAP_FOREACH(lease, s->leases_by_client_id) { + r = sd_bus_message_open_container(reply, 'r', "uayayayayt"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "u", (uint32_t)AF_INET); + if (r < 0) + return r; + + r = sd_bus_message_append_array(reply, 'y', lease->client_id.data, lease->client_id.length); + if (r < 0) + return r; + + r = sd_bus_message_append_array(reply, 'y', &lease->address, sizeof(lease->address)); + if (r < 0) + return r; + + r = sd_bus_message_append_array(reply, 'y', &lease->gateway, sizeof(lease->gateway)); + if (r < 0) + return r; + + r = sd_bus_message_append_array(reply, 'y', &lease->chaddr, sizeof(lease->chaddr)); + if (r < 0) + return r; + + r = sd_bus_message_append_basic(reply, 't', &lease->expiration); + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int dhcp_server_emit_changed(Link *link, const char *property, ...) { + _cleanup_free_ char *path = NULL; + char **l; + + assert(link); + + path = link_bus_path(link); + if (!path) + return log_oom(); + + l = strv_from_stdarg_alloca(property); + + return sd_bus_emit_properties_changed_strv( + link->manager->bus, + path, + "org.freedesktop.network1.DHCPServer", + l); +} + +void dhcp_server_callback(sd_dhcp_server *s, uint64_t event, void *data) { + Link *l = data; + + assert(l); + + if (event & SD_DHCP_SERVER_EVENT_LEASE_CHANGED) + (void) dhcp_server_emit_changed(l, "Leases", NULL); +} + + +const sd_bus_vtable dhcp_server_vtable[] = { + SD_BUS_VTABLE_START(0), + + SD_BUS_PROPERTY("Leases", "a(uayayayayt)", property_get_leases, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + + SD_BUS_VTABLE_END +}; diff --git a/src/network/networkd-dhcp-server-bus.h b/src/network/networkd-dhcp-server-bus.h new file mode 100644 index 0000000..7191478 --- /dev/null +++ b/src/network/networkd-dhcp-server-bus.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-bus.h" +#include "networkd-link.h" + +extern const sd_bus_vtable dhcp_server_vtable[]; + +void dhcp_server_callback(sd_dhcp_server *server, uint64_t event, void *data); diff --git a/src/network/networkd-dhcp-server.c b/src/network/networkd-dhcp-server.c new file mode 100644 index 0000000..cf279c6 --- /dev/null +++ b/src/network/networkd-dhcp-server.c @@ -0,0 +1,439 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <netinet/in.h> +#include <linux/if_arp.h> +#include <linux/if.h> + +#include "sd-dhcp-server.h" + +#include "fd-util.h" +#include "fileio.h" +#include "networkd-address.h" +#include "networkd-dhcp-server.h" +#include "networkd-link.h" +#include "networkd-manager.h" +#include "networkd-network.h" +#include "parse-util.h" +#include "socket-netlink.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" + +static bool link_dhcp4_server_enabled(Link *link) { + assert(link); + + if (link->flags & IFF_LOOPBACK) + return false; + + if (!link->network) + return false; + + if (link->network->bond) + return false; + + if (link->iftype == ARPHRD_CAN) + return false; + + return link->network->dhcp_server; +} + +static Address* link_find_dhcp_server_address(Link *link) { + Address *address; + + assert(link); + assert(link->network); + + /* The first statically configured address if there is any */ + ORDERED_HASHMAP_FOREACH(address, link->network->addresses_by_section) + if (address->family == AF_INET && + !in_addr_is_null(address->family, &address->in_addr)) + return address; + + /* If that didn't work, find a suitable address we got from the pool */ + SET_FOREACH(address, link->pool_addresses) + if (address->family == AF_INET) + return address; + + return NULL; +} + +static int link_push_uplink_to_dhcp_server( + Link *link, + sd_dhcp_lease_server_type what, + sd_dhcp_server *s) { + + _cleanup_free_ struct in_addr *addresses = NULL; + size_t n_addresses = 0, n_allocated = 0; + bool use_dhcp_lease_data = true; + + assert(link); + + if (!link->network) + return 0; + assert(link->network); + + log_link_debug(link, "Copying %s from link", dhcp_lease_server_type_to_string(what)); + + switch (what) { + + case SD_DHCP_LEASE_DNS: + /* For DNS we have a special case. We the data configured explicitly locally along with the + * data from the DHCP lease. */ + + for (unsigned i = 0; i < link->network->n_dns; i++) { + struct in_addr ia; + + /* Only look for IPv4 addresses */ + if (link->network->dns[i]->family != AF_INET) + continue; + + ia = link->network->dns[i]->address.in; + + /* Never propagate obviously borked data */ + if (in4_addr_is_null(&ia) || in4_addr_is_localhost(&ia)) + continue; + + if (!GREEDY_REALLOC(addresses, n_allocated, n_addresses + 1)) + return log_oom(); + + addresses[n_addresses++] = ia; + } + + use_dhcp_lease_data = link->network->dhcp_use_dns; + break; + + case SD_DHCP_LEASE_NTP: { + char **i; + + /* For NTP things are similar, but for NTP hostnames can be configured too, which we cannot + * propagate via DHCP. Hence let's only propagate those which are IP addresses. */ + + STRV_FOREACH(i, link->network->ntp) { + union in_addr_union ia; + + if (in_addr_from_string(AF_INET, *i, &ia) < 0) + continue; + + /* Never propagate obviously borked data */ + if (in4_addr_is_null(&ia.in) || in4_addr_is_localhost(&ia.in)) + continue; + + if (!GREEDY_REALLOC(addresses, n_allocated, n_addresses + 1)) + return log_oom(); + + addresses[n_addresses++] = ia.in; + } + + use_dhcp_lease_data = link->network->dhcp_use_ntp; + break; + } + + case SD_DHCP_LEASE_SIP: + + /* For SIP we don't allow explicit, local configuration, but there's control whether to use the data */ + use_dhcp_lease_data = link->network->dhcp_use_sip; + break; + + case SD_DHCP_LEASE_POP3: + case SD_DHCP_LEASE_SMTP: + case SD_DHCP_LEASE_LPR: + /* For the other server types we currently do not allow local configuration of server data, + * since there are typically no local consumers of the data. */ + break; + + default: + assert_not_reached("Unexpected server type"); + } + + if (use_dhcp_lease_data && link->dhcp_lease) { + const struct in_addr *da; + + int n = sd_dhcp_lease_get_servers(link->dhcp_lease, what, &da); + if (n > 0) { + if (!GREEDY_REALLOC(addresses, n_allocated, n_addresses + n)) + return log_oom(); + + for (int j = 0; j < n; j++) + if (in4_addr_is_non_local(&da[j])) + addresses[n_addresses++] = da[j]; + } + } + + if (n_addresses <= 0) + return 0; + + return sd_dhcp_server_set_servers(s, what, addresses, n_addresses); +} + +static int dhcp4_server_parse_dns_server_string_and_warn(Link *l, const char *string, struct in_addr **addresses, size_t *n_allocated, size_t *n_addresses) { + for (;;) { + _cleanup_free_ char *word = NULL, *server_name = NULL; + union in_addr_union address; + int family, r, ifindex = 0; + + r = extract_first_word(&string, &word, NULL, 0); + if (r < 0) + return r; + if (r == 0) + break; + + r = in_addr_ifindex_name_from_string_auto(word, &family, &address, &ifindex, &server_name); + if (r < 0) { + log_warning_errno(r, "Failed to parse DNS server address '%s', ignoring: %m", word); + continue; + } + + /* Only look for IPv4 addresses */ + if (family != AF_INET) + continue; + + /* Never propagate obviously borked data */ + if (in4_addr_is_null(&address.in) || in4_addr_is_localhost(&address.in)) + continue; + + if (!GREEDY_REALLOC(*addresses, *n_allocated, *n_addresses + 1)) + return log_oom(); + + (*addresses)[(*n_addresses)++] = address.in; + } + + return 0; +} + +static int dhcp4_server_set_dns_from_resolve_conf(Link *link) { + _cleanup_free_ struct in_addr *addresses = NULL; + size_t n_addresses = 0, n_allocated = 0; + _cleanup_fclose_ FILE *f = NULL; + int n = 0, r; + + f = fopen(PRIVATE_UPLINK_RESOLV_CONF, "re"); + if (!f) { + if (errno == ENOENT) + return 0; + + return log_warning_errno(errno, "Failed to open " PRIVATE_UPLINK_RESOLV_CONF ": %m"); + } + + for (;;) { + _cleanup_free_ char *line = NULL; + const char *a; + char *l; + + r = read_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return log_error_errno(r, "Failed to read " PRIVATE_UPLINK_RESOLV_CONF ": %m"); + if (r == 0) + break; + + n++; + + l = strstrip(line); + if (IN_SET(*l, '#', ';', 0)) + continue; + + a = first_word(l, "nameserver"); + if (!a) + continue; + + r = dhcp4_server_parse_dns_server_string_and_warn(link, a, &addresses, &n_allocated, &n_addresses); + if (r < 0) + log_warning_errno(r, "Failed to parse DNS server address '%s', ignoring.", a); + } + + if (n_addresses <= 0) + return 0; + + return sd_dhcp_server_set_dns(link->dhcp_server, addresses, n_addresses); +} + +int dhcp4_server_configure(Link *link) { + bool acquired_uplink = false; + sd_dhcp_option *p; + Link *uplink = NULL; + Address *address; + int r; + + assert(link); + + if (!link_dhcp4_server_enabled(link)) + return 0; + + if (!(link->flags & IFF_UP)) + return 0; + + if (!link->dhcp_server) { + r = sd_dhcp_server_new(&link->dhcp_server, link->ifindex); + if (r < 0) + return r; + + r = sd_dhcp_server_attach_event(link->dhcp_server, link->manager->event, 0); + if (r < 0) + return r; + } + + address = link_find_dhcp_server_address(link); + if (!address) + return log_link_error_errno(link, SYNTHETIC_ERRNO(EBUSY), + "Failed to find suitable address for DHCPv4 server instance."); + + /* use the server address' subnet as the pool */ + r = sd_dhcp_server_configure_pool(link->dhcp_server, &address->in_addr.in, address->prefixlen, + link->network->dhcp_server_pool_offset, link->network->dhcp_server_pool_size); + if (r < 0) + return log_link_error_errno(link, r, "Failed to configure address pool for DHCPv4 server instance: %m"); + + /* TODO: + r = sd_dhcp_server_set_router(link->dhcp_server, &main_address->in_addr.in); + if (r < 0) + return r; + */ + + if (link->network->dhcp_server_max_lease_time_usec > 0) { + r = sd_dhcp_server_set_max_lease_time(link->dhcp_server, + DIV_ROUND_UP(link->network->dhcp_server_max_lease_time_usec, USEC_PER_SEC)); + if (r < 0) + return log_link_error_errno(link, r, "Failed to set maximum lease time for DHCPv4 server instance: %m"); + } + + if (link->network->dhcp_server_default_lease_time_usec > 0) { + r = sd_dhcp_server_set_default_lease_time(link->dhcp_server, + DIV_ROUND_UP(link->network->dhcp_server_default_lease_time_usec, USEC_PER_SEC)); + if (r < 0) + return log_link_error_errno(link, r, "Failed to set default lease time for DHCPv4 server instance: %m"); + } + + for (sd_dhcp_lease_server_type type = 0; type < _SD_DHCP_LEASE_SERVER_TYPE_MAX; type ++) { + + if (!link->network->dhcp_server_emit[type].emit) + continue; + + if (link->network->dhcp_server_emit[type].n_addresses > 0) + /* Explicitly specified servers to emit */ + r = sd_dhcp_server_set_servers( + link->dhcp_server, + type, + link->network->dhcp_server_emit[type].addresses, + link->network->dhcp_server_emit[type].n_addresses); + else { + /* Emission is requested, but nothing explicitly configured. Let's find a suitable upling */ + if (!acquired_uplink) { + uplink = manager_find_uplink(link->manager, link); + acquired_uplink = true; + } + + if (uplink && uplink->network) + r = link_push_uplink_to_dhcp_server(uplink, type, link->dhcp_server); + else if (type == SD_DHCP_LEASE_DNS) + r = dhcp4_server_set_dns_from_resolve_conf(link); + else { + log_link_debug(link, + "Not emitting %s on link, couldn't find suitable uplink.", + dhcp_lease_server_type_to_string(type)); + continue; + } + } + + if (r < 0) + log_link_warning_errno(link, r, + "Failed to set %s for DHCP server, ignoring: %m", + dhcp_lease_server_type_to_string(type)); + } + + r = sd_dhcp_server_set_emit_router(link->dhcp_server, link->network->dhcp_server_emit_router); + if (r < 0) + return log_link_error_errno(link, r, "Failed to set router emission for DHCP server: %m"); + + if (link->network->dhcp_server_emit_timezone) { + _cleanup_free_ char *buffer = NULL; + const char *tz; + + if (link->network->dhcp_server_timezone) + tz = link->network->dhcp_server_timezone; + else { + r = get_timezone(&buffer); + if (r < 0) + return log_link_error_errno(link, r, "Failed to determine timezone: %m"); + + tz = buffer; + } + + r = sd_dhcp_server_set_timezone(link->dhcp_server, tz); + if (r < 0) + return log_link_error_errno(link, r, "Failed to set timezone for DHCP server: %m"); + } + + ORDERED_HASHMAP_FOREACH(p, link->network->dhcp_server_send_options) { + r = sd_dhcp_server_add_option(link->dhcp_server, p); + if (r == -EEXIST) + continue; + if (r < 0) + return log_link_error_errno(link, r, "Failed to set DHCPv4 option: %m"); + } + + ORDERED_HASHMAP_FOREACH(p, link->network->dhcp_server_send_vendor_options) { + r = sd_dhcp_server_add_vendor_option(link->dhcp_server, p); + if (r == -EEXIST) + continue; + if (r < 0) + return log_link_error_errno(link, r, "Failed to set DHCPv4 option: %m"); + } + + if (!sd_dhcp_server_is_running(link->dhcp_server)) { + r = sd_dhcp_server_start(link->dhcp_server); + if (r < 0) + return log_link_error_errno(link, r, "Could not start DHCPv4 server instance: %m"); + + log_link_debug(link, "Offering DHCPv4 leases"); + } + + return 0; +} + +int config_parse_dhcp_server_emit( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + NetworkDHCPServerEmitAddress *emit = data; + + assert(emit); + assert(rvalue); + + for (const char *p = rvalue;;) { + _cleanup_free_ char *w = NULL; + union in_addr_union a; + int r; + + r = extract_first_word(&p, &w, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to extract word, ignoring: %s", rvalue); + return 0; + } + if (r == 0) + return 0; + + r = in_addr_from_string(AF_INET, w, &a); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse %s= address '%s', ignoring: %m", lvalue, w); + continue; + } + + struct in_addr *m = reallocarray(emit->addresses, emit->n_addresses + 1, sizeof(struct in_addr)); + if (!m) + return log_oom(); + + emit->addresses = m; + emit->addresses[emit->n_addresses++] = a.in; + } +} diff --git a/src/network/networkd-dhcp-server.h b/src/network/networkd-dhcp-server.h new file mode 100644 index 0000000..4bd5120 --- /dev/null +++ b/src/network/networkd-dhcp-server.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "conf-parser.h" +#include "networkd-link.h" +#include "networkd-util.h" + +typedef struct Link Link; + +int dhcp4_server_configure(Link *link); + +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_emit); diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c new file mode 100644 index 0000000..f3c1e5f --- /dev/null +++ b/src/network/networkd-dhcp4.c @@ -0,0 +1,1761 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <netinet/in.h> +#include <netinet/ip.h> +#include <linux/if.h> +#include <linux/if_arp.h> + +#include "escape.h" +#include "alloc-util.h" +#include "dhcp-client-internal.h" +#include "hostname-util.h" +#include "parse-util.h" +#include "network-internal.h" +#include "networkd-address.h" +#include "networkd-dhcp4.h" +#include "networkd-link.h" +#include "networkd-manager.h" +#include "networkd-network.h" +#include "string-table.h" +#include "string-util.h" +#include "sysctl-util.h" +#include "web-util.h" + +static int dhcp4_update_address(Link *link, bool announce); +static int dhcp4_remove_all(Link *link); + +static int dhcp4_release_old_lease(Link *link) { + Route *route; + int k, r = 0; + + assert(link); + + if (!link->dhcp_address_old && set_isempty(link->dhcp_routes_old)) + return 0; + + log_link_debug(link, "Removing old DHCPv4 address and routes."); + + link_dirty(link); + + SET_FOREACH(route, link->dhcp_routes_old) { + k = route_remove(route, NULL, link, NULL); + if (k < 0) + r = k; + } + + if (link->dhcp_address_old) { + k = address_remove(link->dhcp_address_old, link, NULL); + if (k < 0) + r = k; + } + + return r; +} + +static void dhcp4_check_ready(Link *link) { + int r; + + if (link->network->dhcp_send_decline && !link->dhcp4_address_bind) + return; + + if (link->dhcp4_messages > 0) + return; + + link->dhcp4_configured = true; + + /* New address and routes are configured now. Let's release old lease. */ + r = dhcp4_release_old_lease(link); + if (r < 0) { + link_enter_failed(link); + return; + } + + link_check_ready(link); +} + +static int dhcp4_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + assert(link->dhcp4_messages > 0); + + link->dhcp4_messages--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r == -ENETUNREACH && !link->dhcp4_route_retrying) { + + /* It seems kernel does not support that the prefix route cannot be configured with + * route table. Let's once drop the config and reconfigure them later. */ + + log_link_message_debug_errno(link, m, r, "Could not set DHCPv4 route, retrying later"); + link->dhcp4_route_failed = true; + link->manager->dhcp4_prefix_root_cannot_set_table = true; + } else if (r < 0 && r != -EEXIST) { + log_link_message_warning_errno(link, m, r, "Could not set DHCPv4 route"); + link_enter_failed(link); + return 1; + } + + if (link->dhcp4_messages == 0 && link->dhcp4_route_failed) { + link->dhcp4_route_failed = false; + link->dhcp4_route_retrying = true; + + r = dhcp4_remove_all(link); + if (r < 0) + link_enter_failed(link); + return 1; + } + + dhcp4_check_ready(link); + + return 1; +} + +static int route_scope_from_address(const Route *route, const struct in_addr *self_addr) { + assert(route); + assert(self_addr); + + if (in4_addr_is_localhost(&route->dst.in) || + (!in4_addr_is_null(self_addr) && in4_addr_equal(&route->dst.in, self_addr))) + return RT_SCOPE_HOST; + else if (in4_addr_is_null(&route->gw.in)) + return RT_SCOPE_LINK; + else + return RT_SCOPE_UNIVERSE; +} + +static bool link_prefixroute(Link *link) { + return !link->network->dhcp_route_table_set || + link->network->dhcp_route_table == RT_TABLE_MAIN || + link->manager->dhcp4_prefix_root_cannot_set_table; +} + +static int dhcp_route_configure(Route *route, Link *link) { + Route *ret; + int r; + + assert(route); + assert(link); + + r = route_configure(route, link, dhcp4_route_handler, &ret); + if (r < 0) + return log_link_error_errno(link, r, "Failed to set DHCPv4 route: %m"); + + link->dhcp4_messages++; + + r = set_ensure_put(&link->dhcp_routes, &route_hash_ops, ret); + if (r < 0) + return log_link_error_errno(link, r, "Failed to store DHCPv4 route: %m"); + + (void) set_remove(link->dhcp_routes_old, ret); + + return 0; +} + +static int link_set_dns_routes(Link *link, const struct in_addr *address) { + const struct in_addr *dns; + uint32_t table; + int i, n, r; + + assert(link); + assert(link->dhcp_lease); + assert(link->network); + + if (!link->network->dhcp_use_dns || + !link->network->dhcp_routes_to_dns) + return 0; + + n = sd_dhcp_lease_get_dns(link->dhcp_lease, &dns); + if (IN_SET(n, 0, -ENODATA)) + return 0; + if (n < 0) + return log_link_warning_errno(link, n, "DHCP error: could not get DNS servers: %m"); + + table = link_get_dhcp_route_table(link); + + for (i = 0; i < n; i ++) { + _cleanup_(route_freep) Route *route = NULL; + + r = route_new(&route); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate route: %m"); + + /* Set routes to DNS servers. */ + + route->family = AF_INET; + route->dst.in = dns[i]; + route->dst_prefixlen = 32; + route->prefsrc.in = *address; + route->scope = RT_SCOPE_LINK; + route->protocol = RTPROT_DHCP; + route->priority = link->network->dhcp_route_metric; + route->table = table; + + r = dhcp_route_configure(route, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not set route to DNS server: %m"); + } + + return 0; +} + +static int dhcp_prefix_route_from_lease( + const sd_dhcp_lease *lease, + uint32_t table, + const struct in_addr *address, + Route **ret_route) { + + Route *route; + struct in_addr netmask; + int r; + + r = sd_dhcp_lease_get_netmask((sd_dhcp_lease*) lease, &netmask); + if (r < 0) + return r; + + r = route_new(&route); + if (r < 0) + return r; + + route->family = AF_INET; + route->dst.in.s_addr = address->s_addr & netmask.s_addr; + route->dst_prefixlen = in4_addr_netmask_to_prefixlen(&netmask); + route->prefsrc.in = *address; + route->scope = RT_SCOPE_LINK; + route->protocol = RTPROT_DHCP; + route->table = table; + *ret_route = route; + return 0; +} + +static int link_set_dhcp_routes(Link *link) { + _cleanup_free_ sd_dhcp_route **static_routes = NULL; + bool classless_route = false, static_route = false; + struct in_addr address; + uint32_t table; + Route *rt; + int r, n; + + assert(link); + + if (!link->dhcp_lease) /* link went down while we configured the IP addresses? */ + return 0; + + if (!link->network) /* link went down while we configured the IP addresses? */ + return 0; + + if (!link_has_carrier(link) && !link->network->configure_without_carrier) + /* During configuring addresses, the link lost its carrier. As networkd is dropping + * the addresses now, let's not configure the routes either. */ + return 0; + + while ((rt = set_steal_first(link->dhcp_routes))) { + r = set_ensure_put(&link->dhcp_routes_old, &route_hash_ops, rt); + if (r < 0) + return log_link_error_errno(link, r, "Failed to store old DHCPv4 route: %m"); + } + + table = link_get_dhcp_route_table(link); + + r = sd_dhcp_lease_get_address(link->dhcp_lease, &address); + if (r < 0) + return log_link_warning_errno(link, r, "DHCP error: could not get address: %m"); + + if (!link_prefixroute(link)) { + _cleanup_(route_freep) Route *prefix_route = NULL; + + r = dhcp_prefix_route_from_lease(link->dhcp_lease, table, &address, &prefix_route); + if (r < 0) + return log_link_error_errno(link, r, "Could not create prefix route: %m"); + + r = dhcp_route_configure(prefix_route, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not set prefix route: %m"); + } + + n = sd_dhcp_lease_get_routes(link->dhcp_lease, &static_routes); + if (n == -ENODATA) + log_link_debug_errno(link, n, "DHCP: No routes received from DHCP server: %m"); + else if (n < 0) + return log_link_error_errno(link, n, "DHCP: could not get routes: %m"); + + for (int i = 0; i < n; i++) { + switch (sd_dhcp_route_get_option(static_routes[i])) { + case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE: + classless_route = true; + break; + case SD_DHCP_OPTION_STATIC_ROUTE: + static_route = true; + break; + } + } + + if (link->network->dhcp_use_routes) { + /* if the DHCP server returns both a Classless Static Routes option and a Static Routes option, + * the DHCP client MUST ignore the Static Routes option. */ + if (classless_route && static_route) + log_link_warning(link, "Classless static routes received from DHCP server: ignoring static-route option"); + + for (int i = 0; i < n; i++) { + _cleanup_(route_freep) Route *route = NULL; + + if (classless_route && + sd_dhcp_route_get_option(static_routes[i]) != SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE) + continue; + + r = route_new(&route); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate route: %m"); + + route->family = AF_INET; + route->protocol = RTPROT_DHCP; + route->gw_family = AF_INET; + assert_se(sd_dhcp_route_get_gateway(static_routes[i], &route->gw.in) >= 0); + assert_se(sd_dhcp_route_get_destination(static_routes[i], &route->dst.in) >= 0); + assert_se(sd_dhcp_route_get_destination_prefix_length(static_routes[i], &route->dst_prefixlen) >= 0); + route->priority = link->network->dhcp_route_metric; + route->table = table; + route->mtu = link->network->dhcp_route_mtu; + route->scope = route_scope_from_address(route, &address); + if (IN_SET(route->scope, RT_SCOPE_LINK, RT_SCOPE_UNIVERSE)) + route->prefsrc.in = address; + + if (set_contains(link->dhcp_routes, route)) + continue; + + r = dhcp_route_configure(route, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not set route: %m"); + } + } + + if (link->network->dhcp_use_gateway) { + const struct in_addr *router; + + r = sd_dhcp_lease_get_router(link->dhcp_lease, &router); + if (IN_SET(r, 0, -ENODATA)) + log_link_info(link, "DHCP: No gateway received from DHCP server."); + else if (r < 0) + return log_link_error_errno(link, r, "DHCP error: could not get gateway: %m"); + else if (in4_addr_is_null(&router[0])) + log_link_info(link, "DHCP: Received gateway is null."); + else if (classless_route) + /* According to RFC 3442: If the DHCP server returns both a Classless Static Routes option and + * a Router option, the DHCP client MUST ignore the Router option. */ + log_link_warning(link, "Classless static routes received from DHCP server: ignoring router option"); + else { + _cleanup_(route_freep) Route *route = NULL, *route_gw = NULL; + + r = route_new(&route_gw); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate route: %m"); + + /* The dhcp netmask may mask out the gateway. Add an explicit + * route for the gw host so that we can route no matter the + * netmask or existing kernel route tables. */ + route_gw->family = AF_INET; + route_gw->dst.in = router[0]; + route_gw->dst_prefixlen = 32; + route_gw->prefsrc.in = address; + route_gw->scope = RT_SCOPE_LINK; + route_gw->protocol = RTPROT_DHCP; + route_gw->priority = link->network->dhcp_route_metric; + route_gw->table = table; + route_gw->mtu = link->network->dhcp_route_mtu; + + r = dhcp_route_configure(route_gw, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not set host route: %m"); + + r = route_new(&route); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate route: %m"); + + route->family = AF_INET; + route->gw_family = AF_INET; + route->gw.in = router[0]; + route->prefsrc.in = address; + route->protocol = RTPROT_DHCP; + route->priority = link->network->dhcp_route_metric; + route->table = table; + route->mtu = link->network->dhcp_route_mtu; + + r = dhcp_route_configure(route, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not set router: %m"); + + HASHMAP_FOREACH(rt, link->network->routes_by_section) { + if (!rt->gateway_from_dhcp_or_ra) + continue; + + if (rt->gw_family != AF_INET) + continue; + + rt->gw.in = router[0]; + if (!rt->protocol_set) + rt->protocol = RTPROT_DHCP; + if (!rt->priority_set) + rt->priority = link->network->dhcp_route_metric; + if (!rt->table_set) + rt->table = table; + if (rt->mtu == 0) + rt->mtu = link->network->dhcp_route_mtu; + + r = dhcp_route_configure(rt, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not set gateway: %m"); + } + } + } + + return link_set_dns_routes(link, &address); +} + +static int dhcp_reset_mtu(Link *link) { + uint16_t mtu; + int r; + + assert(link); + + if (!link->network->dhcp_use_mtu) + return 0; + + r = sd_dhcp_lease_get_mtu(link->dhcp_lease, &mtu); + if (r == -ENODATA) + return 0; + if (r < 0) + return log_link_error_errno(link, r, "DHCP error: failed to get MTU from lease: %m"); + + if (link->original_mtu == mtu) + return 0; + + r = link_set_mtu(link, link->original_mtu); + if (r < 0) + return log_link_error_errno(link, r, "DHCP error: could not reset MTU: %m"); + + return 0; +} + +static int dhcp_reset_hostname(Link *link) { + const char *hostname; + int r; + + assert(link); + + if (!link->network->dhcp_use_hostname) + return 0; + + hostname = link->network->dhcp_hostname; + if (!hostname) + (void) sd_dhcp_lease_get_hostname(link->dhcp_lease, &hostname); + + if (!hostname) + return 0; + + /* If a hostname was set due to the lease, then unset it now. */ + r = manager_set_hostname(link->manager, NULL); + if (r < 0) + return log_link_error_errno(link, r, "DHCP error: Failed to reset transient hostname: %m"); + + return 0; +} + +static int dhcp4_remove_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(m); + assert(link); + assert(link->dhcp4_remove_messages > 0); + + link->dhcp4_remove_messages--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -ESRCH) + log_link_message_warning_errno(link, m, r, "Failed to remove DHCPv4 route, ignoring"); + + if (link->dhcp4_remove_messages == 0) { + r = dhcp4_update_address(link, false); + if (r < 0) + link_enter_failed(link); + } + + return 1; +} + +static int dhcp4_remove_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(m); + assert(link); + assert(link->dhcp4_remove_messages > 0); + + link->dhcp4_remove_messages--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EADDRNOTAVAIL) + log_link_message_warning_errno(link, m, r, "Failed to remove DHCPv4 address, ignoring"); + else + (void) manager_rtnl_process_address(rtnl, m, link->manager); + + if (link->dhcp4_remove_messages == 0) { + r = dhcp4_update_address(link, false); + if (r < 0) + link_enter_failed(link); + } + + return 1; +} + +static int dhcp4_remove_all(Link *link) { + Route *route; + int k, r = 0; + + assert(link); + + SET_FOREACH(route, link->dhcp_routes) { + k = route_remove(route, NULL, link, dhcp4_remove_route_handler); + if (k < 0) + r = k; + else + link->dhcp4_remove_messages++; + } + + if (link->dhcp_address) { + k = address_remove(link->dhcp_address, link, dhcp4_remove_address_handler); + if (k < 0) + r = k; + else + link->dhcp4_remove_messages++; + } + + return r; +} + +static int dhcp_lease_lost(Link *link) { + int k, r = 0; + + assert(link); + assert(link->dhcp_lease); + + log_link_info(link, "DHCP lease lost"); + + link->dhcp4_configured = false; + + /* dhcp_lease_lost() may be called during renewing IP address. */ + k = dhcp4_release_old_lease(link); + if (k < 0) + r = k; + + k = dhcp4_remove_all(link); + if (k < 0) + r = k; + + k = dhcp_reset_mtu(link); + if (k < 0) + r = k; + + k = dhcp_reset_hostname(link); + if (k < 0) + r = k; + + link->dhcp_lease = sd_dhcp_lease_unref(link->dhcp_lease); + link_dirty(link); + + (void) sd_ipv4acd_stop(link->dhcp_acd); + + return r; +} + +static void dhcp_address_on_acd(sd_ipv4acd *acd, int event, void *userdata) { + _cleanup_free_ char *pretty = NULL; + union in_addr_union address = {}; + Link *link; + int r; + + assert(acd); + assert(userdata); + + link = userdata; + + switch (event) { + case SD_IPV4ACD_EVENT_STOP: + log_link_debug(link, "Stopping ACD client for DHCP4..."); + return; + + case SD_IPV4ACD_EVENT_BIND: + if (DEBUG_LOGGING) { + (void) sd_dhcp_lease_get_address(link->dhcp_lease, &address.in); + (void) in_addr_to_string(AF_INET, &address, &pretty); + log_link_debug(link, "Successfully claimed DHCP4 address %s", strna(pretty)); + } + link->dhcp4_address_bind = true; + dhcp4_check_ready(link); + break; + + case SD_IPV4ACD_EVENT_CONFLICT: + (void) sd_dhcp_lease_get_address(link->dhcp_lease, &address.in); + (void) in_addr_to_string(AF_INET, &address, &pretty); + log_link_warning(link, "DAD conflict. Dropping DHCP4 address %s", strna(pretty)); + + r = sd_dhcp_client_send_decline(link->dhcp_client); + if (r < 0) + log_link_warning_errno(link, r, "Failed to send DHCP DECLINE, ignoring: %m"); + + if (link->dhcp_lease) { + r = dhcp_lease_lost(link); + if (r < 0) + link_enter_failed(link); + } + break; + + default: + assert_not_reached("Invalid IPv4ACD event."); + } + + (void) sd_ipv4acd_stop(acd); + + return; +} + +static int dhcp4_configure_dad(Link *link) { + int r; + + assert(link); + assert(link->manager); + assert(link->network); + + if (!link->network->dhcp_send_decline) + return 0; + + if (!link->dhcp_acd) { + r = sd_ipv4acd_new(&link->dhcp_acd); + if (r < 0) + return r; + + r = sd_ipv4acd_attach_event(link->dhcp_acd, link->manager->event, 0); + if (r < 0) + return r; + } + + r = sd_ipv4acd_set_ifindex(link->dhcp_acd, link->ifindex); + if (r < 0) + return r; + + r = sd_ipv4acd_set_mac(link->dhcp_acd, &link->hw_addr.addr.ether); + if (r < 0) + return r; + + return 0; +} + +static int dhcp4_dad_update_mac(Link *link) { + bool running; + int r; + + assert(link); + + if (!link->dhcp_acd) + return 0; + + running = sd_ipv4acd_is_running(link->dhcp_acd); + + r = sd_ipv4acd_stop(link->dhcp_acd); + if (r < 0) + return r; + + r = sd_ipv4acd_set_mac(link->dhcp_acd, &link->hw_addr.addr.ether); + if (r < 0) + return r; + + if (running) { + r = sd_ipv4acd_start(link->dhcp_acd, true); + if (r < 0) + return r; + } + + return 0; +} + +static int dhcp4_start_acd(Link *link) { + union in_addr_union addr; + struct in_addr old; + int r; + + if (!link->network->dhcp_send_decline) + return 0; + + if (!link->dhcp_lease) + return 0; + + (void) sd_ipv4acd_stop(link->dhcp_acd); + + link->dhcp4_address_bind = false; + + r = sd_dhcp_lease_get_address(link->dhcp_lease, &addr.in); + if (r < 0) + return r; + + r = sd_ipv4acd_get_address(link->dhcp_acd, &old); + if (r < 0) + return r; + + r = sd_ipv4acd_set_address(link->dhcp_acd, &addr.in); + if (r < 0) + return r; + + r = sd_ipv4acd_set_callback(link->dhcp_acd, dhcp_address_on_acd, link); + if (r < 0) + return r; + + if (DEBUG_LOGGING) { + _cleanup_free_ char *pretty = NULL; + + (void) in_addr_to_string(AF_INET, &addr, &pretty); + log_link_debug(link, "Starting IPv4ACD client. Probing DHCPv4 address %s", strna(pretty)); + } + + r = sd_ipv4acd_start(link->dhcp_acd, !in4_addr_equal(&addr.in, &old)); + if (r < 0) + return r; + + return 1; +} + +static int dhcp4_address_ready_callback(Address *address) { + Link *link; + int r; + + assert(address); + + link = address->link; + + /* Do not call this again. */ + address->callback = NULL; + + r = link_set_dhcp_routes(link); + if (r < 0) + return r; + + /* Reconfigure static routes as kernel may remove some routes when lease expires. */ + r = link_set_routes(link); + if (r < 0) + return r; + + r = dhcp4_start_acd(link); + if (r < 0) + return log_link_error_errno(link, r, "Failed to start IPv4ACD for DHCP4 address: %m"); + + dhcp4_check_ready(link); + return 0; +} + +static int dhcp4_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EEXIST) { + log_link_message_warning_errno(link, m, r, "Could not set DHCPv4 address"); + link_enter_failed(link); + return 1; + } else if (r >= 0) + (void) manager_rtnl_process_address(rtnl, m, link->manager); + + if (address_is_ready(link->dhcp_address)) { + r = dhcp4_address_ready_callback(link->dhcp_address); + if (r < 0) { + link_enter_failed(link); + return 1; + } + } else + link->dhcp_address->callback = dhcp4_address_ready_callback; + + return 1; +} + +static int dhcp4_update_address(Link *link, bool announce) { + _cleanup_(address_freep) Address *addr = NULL; + uint32_t lifetime = CACHE_INFO_INFINITY_LIFE_TIME; + struct in_addr address, netmask; + unsigned prefixlen; + Address *ret; + int r; + + assert(link); + assert(link->network); + + if (!link->dhcp_lease) + return 0; + + link_set_state(link, LINK_STATE_CONFIGURING); + link->dhcp4_configured = false; + + /* address_handler calls link_set_routes() and link_set_nexthop(). Before they are called, the + * related flags must be cleared. Otherwise, the link becomes configured state before routes + * are configured. */ + link->static_routes_configured = false; + link->static_nexthops_configured = false; + + r = sd_dhcp_lease_get_address(link->dhcp_lease, &address); + if (r < 0) + return log_link_warning_errno(link, r, "DHCP error: no address: %m"); + + r = sd_dhcp_lease_get_netmask(link->dhcp_lease, &netmask); + if (r < 0) + return log_link_warning_errno(link, r, "DHCP error: no netmask: %m"); + + if (!FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP)) { + r = sd_dhcp_lease_get_lifetime(link->dhcp_lease, &lifetime); + if (r < 0) + return log_link_warning_errno(link, r, "DHCP error: no lifetime: %m"); + } + + prefixlen = in4_addr_netmask_to_prefixlen(&netmask); + + if (announce) { + const struct in_addr *router; + + r = sd_dhcp_lease_get_router(link->dhcp_lease, &router); + if (r < 0 && r != -ENODATA) + return log_link_error_errno(link, r, "DHCP error: Could not get gateway: %m"); + + if (r > 0 && !in4_addr_is_null(&router[0])) + log_struct(LOG_INFO, + LOG_LINK_INTERFACE(link), + LOG_LINK_MESSAGE(link, "DHCPv4 address "IPV4_ADDRESS_FMT_STR"/%u via "IPV4_ADDRESS_FMT_STR, + IPV4_ADDRESS_FMT_VAL(address), + prefixlen, + IPV4_ADDRESS_FMT_VAL(router[0])), + "ADDRESS="IPV4_ADDRESS_FMT_STR, IPV4_ADDRESS_FMT_VAL(address), + "PREFIXLEN=%u", prefixlen, + "GATEWAY="IPV4_ADDRESS_FMT_STR, IPV4_ADDRESS_FMT_VAL(router[0])); + else + log_struct(LOG_INFO, + LOG_LINK_INTERFACE(link), + LOG_LINK_MESSAGE(link, "DHCPv4 address "IPV4_ADDRESS_FMT_STR"/%u", + IPV4_ADDRESS_FMT_VAL(address), + prefixlen), + "ADDRESS="IPV4_ADDRESS_FMT_STR, IPV4_ADDRESS_FMT_VAL(address), + "PREFIXLEN=%u", prefixlen); + } + + r = address_new(&addr); + if (r < 0) + return log_oom(); + + addr->family = AF_INET; + addr->in_addr.in.s_addr = address.s_addr; + addr->cinfo.ifa_prefered = lifetime; + addr->cinfo.ifa_valid = lifetime; + addr->prefixlen = prefixlen; + if (prefixlen <= 30) + addr->broadcast.s_addr = address.s_addr | ~netmask.s_addr; + SET_FLAG(addr->flags, IFA_F_NOPREFIXROUTE, !link_prefixroute(link)); + + /* allow reusing an existing address and simply update its lifetime + * in case it already exists */ + r = address_configure(addr, link, dhcp4_address_handler, true, &ret); + if (r < 0) + return log_link_error_errno(link, r, "Failed to set DHCPv4 address: %m"); + + if (!address_equal(link->dhcp_address, ret)) + link->dhcp_address_old = link->dhcp_address; + link->dhcp_address = ret; + + return 0; +} + +static int dhcp_lease_renew(sd_dhcp_client *client, Link *link) { + sd_dhcp_lease *lease; + int r; + + assert(link); + assert(client); + + r = sd_dhcp_client_get_lease(client, &lease); + if (r < 0) + return log_link_warning_errno(link, r, "DHCP error: no lease: %m"); + + sd_dhcp_lease_unref(link->dhcp_lease); + link->dhcp_lease = sd_dhcp_lease_ref(lease); + link_dirty(link); + + return dhcp4_update_address(link, false); +} + +static int dhcp_lease_acquired(sd_dhcp_client *client, Link *link) { + sd_dhcp_lease *lease; + int r; + + assert(client); + assert(link); + + r = sd_dhcp_client_get_lease(client, &lease); + if (r < 0) + return log_link_error_errno(link, r, "DHCP error: No lease: %m"); + + sd_dhcp_lease_unref(link->dhcp_lease); + link->dhcp_lease = sd_dhcp_lease_ref(lease); + link_dirty(link); + + if (link->network->dhcp_use_mtu) { + uint16_t mtu; + + r = sd_dhcp_lease_get_mtu(lease, &mtu); + if (r >= 0) { + r = link_set_mtu(link, mtu); + if (r < 0) + log_link_error_errno(link, r, "Failed to set MTU to %" PRIu16 ": %m", mtu); + } + } + + if (link->network->dhcp_use_hostname) { + const char *dhcpname = NULL; + _cleanup_free_ char *hostname = NULL; + + if (link->network->dhcp_hostname) + dhcpname = link->network->dhcp_hostname; + else + (void) sd_dhcp_lease_get_hostname(lease, &dhcpname); + + if (dhcpname) { + r = shorten_overlong(dhcpname, &hostname); + if (r < 0) + log_link_warning_errno(link, r, "Unable to shorten overlong DHCP hostname '%s', ignoring: %m", dhcpname); + if (r == 1) + log_link_notice(link, "Overlong DHCP hostname received, shortened from '%s' to '%s'", dhcpname, hostname); + } + + if (hostname) { + r = manager_set_hostname(link->manager, hostname); + if (r < 0) + log_link_error_errno(link, r, "Failed to set transient hostname to '%s': %m", hostname); + } + } + + if (link->network->dhcp_use_timezone) { + const char *tz = NULL; + + (void) sd_dhcp_lease_get_timezone(link->dhcp_lease, &tz); + + if (tz) { + r = manager_set_timezone(link->manager, tz); + if (r < 0) + log_link_error_errno(link, r, "Failed to set timezone to '%s': %m", tz); + } + } + + if (link->dhcp4_remove_messages == 0) { + r = dhcp4_update_address(link, true); + if (r < 0) + return r; + } else + log_link_debug(link, + "The link has previously assigned DHCPv4 address or routes. " + "The newly assigned address and routes will set up after old ones are removed."); + + return 0; +} + +static int dhcp_lease_ip_change(sd_dhcp_client *client, Link *link) { + int r; + + r = dhcp_lease_acquired(client, link); + if (r < 0) + (void) dhcp_lease_lost(link); + + return r; +} + +static int dhcp_server_is_deny_listed(Link *link, sd_dhcp_client *client) { + sd_dhcp_lease *lease; + struct in_addr addr; + int r; + + assert(link); + assert(link->network); + assert(client); + + r = sd_dhcp_client_get_lease(client, &lease); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get DHCP lease: %m"); + + r = sd_dhcp_lease_get_server_identifier(lease, &addr); + if (r < 0) + return log_link_debug_errno(link, r, "Failed to get DHCP server IP address: %m"); + + if (set_contains(link->network->dhcp_deny_listed_ip, UINT32_TO_PTR(addr.s_addr))) { + log_struct(LOG_DEBUG, + LOG_LINK_INTERFACE(link), + LOG_LINK_MESSAGE(link, "DHCPv4 server IP address "IPV4_ADDRESS_FMT_STR" found in deny-list, ignoring offer", + IPV4_ADDRESS_FMT_VAL(addr))); + return true; + } + + return false; +} + +static int dhcp_server_is_allow_listed(Link *link, sd_dhcp_client *client) { + sd_dhcp_lease *lease; + struct in_addr addr; + int r; + + assert(link); + assert(link->network); + assert(client); + + r = sd_dhcp_client_get_lease(client, &lease); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get DHCP lease: %m"); + + r = sd_dhcp_lease_get_server_identifier(lease, &addr); + if (r < 0) + return log_link_debug_errno(link, r, "Failed to get DHCP server IP address: %m"); + + if (set_contains(link->network->dhcp_allow_listed_ip, UINT32_TO_PTR(addr.s_addr))) { + log_struct(LOG_DEBUG, + LOG_LINK_INTERFACE(link), + LOG_LINK_MESSAGE(link, "DHCPv4 server IP address "IPV4_ADDRESS_FMT_STR" found in allow-list, accepting offer", + IPV4_ADDRESS_FMT_VAL(addr))); + return true; + } + + return false; +} + +static int dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) { + Link *link = userdata; + int r; + + assert(link); + assert(link->network); + assert(link->manager); + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 0; + + switch (event) { + case SD_DHCP_CLIENT_EVENT_STOP: + + if (link_ipv4ll_enabled(link, ADDRESS_FAMILY_FALLBACK_IPV4)) { + assert(link->ipv4ll); + + log_link_debug(link, "DHCP client is stopped. Acquiring IPv4 link-local address"); + + r = sd_ipv4ll_start(link->ipv4ll); + if (r < 0) + return log_link_warning_errno(link, r, "Could not acquire IPv4 link-local address: %m"); + } + + if (FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP)) { + log_link_notice(link, "DHCPv4 connection considered critical, ignoring request to reconfigure it."); + return 0; + } + + if (link->dhcp_lease) { + if (link->network->dhcp_send_release) { + r = sd_dhcp_client_send_release(client); + if (r < 0) + log_link_warning_errno(link, r, "Failed to send DHCP RELEASE, ignoring: %m"); + } + + r = dhcp_lease_lost(link); + if (r < 0) { + link_enter_failed(link); + return r; + } + } + + break; + case SD_DHCP_CLIENT_EVENT_EXPIRED: + if (FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP)) { + log_link_notice(link, "DHCPv4 connection considered critical, ignoring request to reconfigure it."); + return 0; + } + + if (link->dhcp_lease) { + r = dhcp_lease_lost(link); + if (r < 0) { + link_enter_failed(link); + return r; + } + } + + break; + case SD_DHCP_CLIENT_EVENT_IP_CHANGE: + if (FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP)) { + log_link_notice(link, "DHCPv4 connection considered critical, ignoring request to reconfigure it."); + return 0; + } + + r = dhcp_lease_ip_change(client, link); + if (r < 0) { + link_enter_failed(link); + return r; + } + + break; + case SD_DHCP_CLIENT_EVENT_RENEW: + r = dhcp_lease_renew(client, link); + if (r < 0) { + link_enter_failed(link); + return r; + } + break; + case SD_DHCP_CLIENT_EVENT_IP_ACQUIRE: + r = dhcp_lease_acquired(client, link); + if (r < 0) { + link_enter_failed(link); + return r; + } + break; + case SD_DHCP_CLIENT_EVENT_SELECTING: + if (!set_isempty(link->network->dhcp_allow_listed_ip)) { + r = dhcp_server_is_allow_listed(link, client); + if (r < 0) + return r; + if (r == 0) + return -ENOMSG; + } else { + r = dhcp_server_is_deny_listed(link, client); + if (r < 0) + return r; + if (r != 0) + return -ENOMSG; + } + break; + default: + if (event < 0) + log_link_warning_errno(link, event, "DHCP error: Client failed: %m"); + else + log_link_warning(link, "DHCP unknown event: %i", event); + break; + } + + return 0; +} + +static int dhcp4_set_hostname(Link *link) { + _cleanup_free_ char *hostname = NULL; + const char *hn; + int r; + + assert(link); + + if (!link->network->dhcp_send_hostname) + hn = NULL; + else if (link->network->dhcp_hostname) + hn = link->network->dhcp_hostname; + else { + r = gethostname_strict(&hostname); + if (r < 0 && r != -ENXIO) /* ENXIO: no hostname set or hostname is "localhost" */ + return r; + + hn = hostname; + } + + r = sd_dhcp_client_set_hostname(link->dhcp_client, hn); + if (r == -EINVAL && hostname) + /* Ignore error when the machine's hostname is not suitable to send in DHCP packet. */ + log_link_warning_errno(link, r, "DHCP4 CLIENT: Failed to set hostname from kernel hostname, ignoring: %m"); + else if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set hostname: %m"); + + return 0; +} + +static bool promote_secondaries_enabled(const char *ifname) { + _cleanup_free_ char *promote_secondaries_sysctl = NULL; + char *promote_secondaries_path; + int r; + + promote_secondaries_path = strjoina("net/ipv4/conf/", ifname, "/promote_secondaries"); + r = sysctl_read(promote_secondaries_path, &promote_secondaries_sysctl); + if (r < 0) { + log_debug_errno(r, "Cannot read sysctl %s", promote_secondaries_path); + return false; + } + + truncate_nl(promote_secondaries_sysctl); + r = parse_boolean(promote_secondaries_sysctl); + if (r < 0) + log_warning_errno(r, "Cannot parse sysctl %s with content %s as boolean", promote_secondaries_path, promote_secondaries_sysctl); + return r > 0; +} + +/* dhcp4_set_promote_secondaries will ensure this interface has + * the "promote_secondaries" option in the kernel set. If this sysctl + * is not set DHCP will work only as long as the IP address does not + * changes between leases. The kernel will remove all secondary IP + * addresses of an interface otherwise. The way systemd-network works + * is that the new IP of a lease is added as a secondary IP and when + * the primary one expires it relies on the kernel to promote the + * secondary IP. See also https://github.com/systemd/systemd/issues/7163 + */ +static int dhcp4_set_promote_secondaries(Link *link) { + int r; + + assert(link); + + /* check if the kernel has promote_secondaries enabled for our + * interface. If it is not globally enabled or enabled for the + * specific interface we must either enable it. + */ + if (!(promote_secondaries_enabled("all") || promote_secondaries_enabled(link->ifname))) { + char *promote_secondaries_path = NULL; + + log_link_debug(link, "promote_secondaries is unset, setting it"); + promote_secondaries_path = strjoina("net/ipv4/conf/", link->ifname, "/promote_secondaries"); + r = sysctl_write(promote_secondaries_path, "1"); + if (r < 0) + log_link_warning_errno(link, r, "cannot set sysctl %s to 1", promote_secondaries_path); + return r > 0; + } + + return 0; +} + +static int dhcp4_set_client_identifier(Link *link) { + int r; + + assert(link); + assert(link->network); + assert(link->dhcp_client); + + switch (link->network->dhcp_client_identifier) { + case DHCP_CLIENT_ID_DUID: { + /* If configured, apply user specified DUID and IAID */ + const DUID *duid = link_get_duid(link); + + if (duid->type == DUID_TYPE_LLT && duid->raw_data_len == 0) + r = sd_dhcp_client_set_iaid_duid_llt(link->dhcp_client, + link->network->iaid_set, + link->network->iaid, + duid->llt_time); + else + r = sd_dhcp_client_set_iaid_duid(link->dhcp_client, + link->network->iaid_set, + link->network->iaid, + duid->type, + duid->raw_data_len > 0 ? duid->raw_data : NULL, + duid->raw_data_len); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set IAID+DUID: %m"); + break; + } + case DHCP_CLIENT_ID_DUID_ONLY: { + /* If configured, apply user specified DUID */ + const DUID *duid = link_get_duid(link); + + if (duid->type == DUID_TYPE_LLT && duid->raw_data_len == 0) + r = sd_dhcp_client_set_duid_llt(link->dhcp_client, + duid->llt_time); + else + r = sd_dhcp_client_set_duid(link->dhcp_client, + duid->type, + duid->raw_data_len > 0 ? duid->raw_data : NULL, + duid->raw_data_len); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set DUID: %m"); + break; + } + case DHCP_CLIENT_ID_MAC: { + const uint8_t *hw_addr = link->hw_addr.addr.bytes; + size_t hw_addr_len = link->hw_addr.length; + + if (link->iftype == ARPHRD_INFINIBAND && hw_addr_len == INFINIBAND_ALEN) { + /* set_client_id expects only last 8 bytes of an IB address */ + hw_addr += INFINIBAND_ALEN - 8; + hw_addr_len -= INFINIBAND_ALEN - 8; + } + + r = sd_dhcp_client_set_client_id(link->dhcp_client, + link->iftype, + hw_addr, + hw_addr_len); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set client ID: %m"); + break; + } + default: + assert_not_reached("Unknown client identifier type."); + } + + return 0; +} + +static int dhcp4_init(Link *link) { + int r; + + assert(link); + + if (link->dhcp_client) + return 0; + + r = sd_dhcp_client_new(&link->dhcp_client, link->network->dhcp_anonymize); + if (r < 0) + return r; + + r = sd_dhcp_client_attach_event(link->dhcp_client, link->manager->event, 0); + if (r < 0) + return r; + + return 0; +} + +int dhcp4_configure(Link *link) { + sd_dhcp_option *send_option; + void *request_options; + int r; + + assert(link); + assert(link->network); + + if (!link_dhcp4_enabled(link)) + return 0; + + r = dhcp4_set_promote_secondaries(link); + if (r < 0) + return r; + + r = dhcp4_init(link); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to initialize DHCP4 client: %m"); + + r = sd_dhcp_client_set_mac(link->dhcp_client, + link->hw_addr.addr.bytes, + link->bcast_addr.length > 0 ? link->bcast_addr.addr.bytes : NULL, + link->hw_addr.length, link->iftype); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set MAC address: %m"); + + r = sd_dhcp_client_set_ifindex(link->dhcp_client, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set ifindex: %m"); + + r = sd_dhcp_client_set_callback(link->dhcp_client, dhcp4_handler, link); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set callback: %m"); + + r = sd_dhcp_client_set_request_broadcast(link->dhcp_client, + link->network->dhcp_broadcast); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set request flag for broadcast: %m"); + + if (link->mtu) { + r = sd_dhcp_client_set_mtu(link->dhcp_client, link->mtu); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set MTU: %m"); + } + + if (link->network->dhcp_use_mtu) { + r = sd_dhcp_client_set_request_option(link->dhcp_client, + SD_DHCP_OPTION_INTERFACE_MTU); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set request flag for MTU: %m"); + } + + /* NOTE: even if this variable is called "use", it also "sends" PRL + * options, maybe there should be a different configuration variable + * to send or not route options?. */ + /* NOTE: when using Anonymize=yes, routes PRL options are sent + * by default, so they don't need to be added here. */ + if (link->network->dhcp_use_routes && !link->network->dhcp_anonymize) { + r = sd_dhcp_client_set_request_option(link->dhcp_client, + SD_DHCP_OPTION_STATIC_ROUTE); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set request flag for static route: %m"); + + r = sd_dhcp_client_set_request_option(link->dhcp_client, + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set request flag for classless static route: %m"); + } + + if (link->network->dhcp_use_domains != DHCP_USE_DOMAINS_NO && !link->network->dhcp_anonymize) { + r = sd_dhcp_client_set_request_option(link->dhcp_client, SD_DHCP_OPTION_DOMAIN_SEARCH_LIST); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set request flag for domain search list: %m"); + } + + if (link->network->dhcp_use_ntp) { + r = sd_dhcp_client_set_request_option(link->dhcp_client, SD_DHCP_OPTION_NTP_SERVER); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set request flag for NTP server: %m"); + } + + if (link->network->dhcp_use_sip) { + r = sd_dhcp_client_set_request_option(link->dhcp_client, SD_DHCP_OPTION_SIP_SERVER); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set request flag for SIP server: %m"); + } + + if (link->network->dhcp_use_timezone) { + r = sd_dhcp_client_set_request_option(link->dhcp_client, SD_DHCP_OPTION_NEW_TZDB_TIMEZONE); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set request flag for timezone: %m"); + } + + SET_FOREACH(request_options, link->network->dhcp_request_options) { + uint32_t option = PTR_TO_UINT32(request_options); + + r = sd_dhcp_client_set_request_option(link->dhcp_client, option); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set request flag for '%u': %m", option); + } + + ORDERED_HASHMAP_FOREACH(send_option, link->network->dhcp_client_send_options) { + r = sd_dhcp_client_add_option(link->dhcp_client, send_option); + if (r == -EEXIST) + continue; + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set send option: %m"); + } + + ORDERED_HASHMAP_FOREACH(send_option, link->network->dhcp_client_send_vendor_options) { + r = sd_dhcp_client_add_vendor_option(link->dhcp_client, send_option); + if (r == -EEXIST) + continue; + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set send option: %m"); + } + + r = dhcp4_set_hostname(link); + if (r < 0) + return r; + + if (link->network->dhcp_vendor_class_identifier) { + r = sd_dhcp_client_set_vendor_class_identifier(link->dhcp_client, + link->network->dhcp_vendor_class_identifier); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set vendor class identifier: %m"); + } + + if (link->network->dhcp_mudurl) { + r = sd_dhcp_client_set_mud_url(link->dhcp_client, + link->network->dhcp_mudurl); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set MUD URL: %m"); + } + + if (link->network->dhcp_user_class) { + r = sd_dhcp_client_set_user_class(link->dhcp_client, link->network->dhcp_user_class); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set user class: %m"); + } + + if (link->network->dhcp_client_port) { + r = sd_dhcp_client_set_client_port(link->dhcp_client, link->network->dhcp_client_port); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set listen port: %m"); + } + + if (link->network->dhcp_max_attempts > 0) { + r = sd_dhcp_client_set_max_attempts(link->dhcp_client, link->network->dhcp_max_attempts); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set max attempts: %m"); + } + + if (link->network->dhcp_ip_service_type > 0) { + r = sd_dhcp_client_set_service_type(link->dhcp_client, link->network->dhcp_ip_service_type); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set IP service type: %m"); + } + + if (link->network->dhcp_fallback_lease_lifetime > 0) { + r = sd_dhcp_client_set_fallback_lease_lifetime(link->dhcp_client, link->network->dhcp_fallback_lease_lifetime); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed set to lease lifetime: %m"); + } + + r = dhcp4_configure_dad(link); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to configure service type: %m"); + + return dhcp4_set_client_identifier(link); +} + +int dhcp4_update_mac(Link *link) { + int r; + + assert(link); + + if (!link->dhcp_client) + return 0; + + r = sd_dhcp_client_set_mac(link->dhcp_client, link->hw_addr.addr.bytes, + link->bcast_addr.length > 0 ? link->bcast_addr.addr.bytes : NULL, + link->hw_addr.length, link->iftype); + if (r < 0) + return r; + + r = dhcp4_set_client_identifier(link); + if (r < 0) + return r; + + r = dhcp4_dad_update_mac(link); + if (r < 0) + return r; + + return 0; +} + +int link_deserialize_dhcp4(Link *link, const char *dhcp4_address) { + union in_addr_union address; + int r; + + assert(link); + + if (isempty(dhcp4_address)) + return 0; + + r = in_addr_from_string(AF_INET, dhcp4_address, &address); + if (r < 0) + return log_link_debug_errno(link, r, "Failed to parse DHCPv4 address: %s", dhcp4_address); + + r = dhcp4_init(link); + if (r < 0) + return log_link_debug_errno(link, r, "Failed to initialize DHCPv4 client: %m"); + + r = sd_dhcp_client_set_request_address(link->dhcp_client, &address.in); + if (r < 0) + return log_link_debug_errno(link, r, "Failed to set initial DHCPv4 address %s: %m", dhcp4_address); + + return 0; +} + +int config_parse_dhcp_max_attempts( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = data; + uint64_t a; + int r; + + assert(network); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + network->dhcp_max_attempts = 0; + return 0; + } + + if (streq(rvalue, "infinity")) { + network->dhcp_max_attempts = (uint64_t) -1; + return 0; + } + + r = safe_atou64(rvalue, &a); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse DHCP maximum attempts, ignoring: %s", rvalue); + return 0; + } + + if (a == 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "%s= must be positive integer or 'infinity', ignoring: %s", lvalue, rvalue); + return 0; + } + + network->dhcp_max_attempts = a; + + return 0; +} + +int config_parse_dhcp_acl_ip_address( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = data; + Set **acl; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + acl = STR_IN_SET(lvalue, "DenyList", "BlackList") ? &network->dhcp_deny_listed_ip : &network->dhcp_allow_listed_ip; + + if (isempty(rvalue)) { + *acl = set_free(*acl); + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *n = NULL; + union in_addr_union ip; + + r = extract_first_word(&p, &n, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse DHCP '%s=' IP address, ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + if (r == 0) + return 0; + + r = in_addr_from_string(AF_INET, n, &ip); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "DHCP '%s=' IP address is invalid, ignoring assignment: %s", lvalue, n); + continue; + } + + r = set_ensure_put(acl, NULL, UINT32_TO_PTR(ip.in.s_addr)); + if (r < 0) + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to store DHCP '%s=' IP address '%s', ignoring assignment: %m", lvalue, n); + } +} + +int config_parse_dhcp_ip_service_type( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (streq(rvalue, "CS4")) + *((int *)data) = IPTOS_CLASS_CS4; + else if (streq(rvalue, "CS6")) + *((int *)data) = IPTOS_CLASS_CS6; + else + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Failed to parse IPServiceType type '%s', ignoring.", rvalue); + + return 0; +} + +int config_parse_dhcp_mud_url( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_free_ char *unescaped = NULL; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + network->dhcp_mudurl = mfree(network->dhcp_mudurl); + return 0; + } + + r = cunescape(rvalue, 0, &unescaped); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to Failed to unescape MUD URL, ignoring: %s", rvalue); + return 0; + } + + if (!http_url_is_valid(unescaped) || strlen(unescaped) > 255) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Failed to parse MUD URL '%s', ignoring: %m", rvalue); + + return 0; + } + + return free_and_strdup_warn(&network->dhcp_mudurl, unescaped); +} + +int config_parse_dhcp_fallback_lease_lifetime(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + Network *network = userdata; + uint32_t k; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + network->dhcp_fallback_lease_lifetime = 0; + return 0; + } + + /* We accept only "forever" or "infinity". */ + if (STR_IN_SET(rvalue, "forever", "infinity")) + k = CACHE_INFO_INFINITY_LIFE_TIME; + else { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid LeaseLifetime= value, ignoring: %s", rvalue); + return 0; + } + + network->dhcp_fallback_lease_lifetime = k; + + return 0; +} + +static const char* const dhcp_client_identifier_table[_DHCP_CLIENT_ID_MAX] = { + [DHCP_CLIENT_ID_MAC] = "mac", + [DHCP_CLIENT_ID_DUID] = "duid", + [DHCP_CLIENT_ID_DUID_ONLY] = "duid-only", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(dhcp_client_identifier, DHCPClientIdentifier); +DEFINE_CONFIG_PARSE_ENUM(config_parse_dhcp_client_identifier, dhcp_client_identifier, DHCPClientIdentifier, + "Failed to parse client identifier type"); diff --git a/src/network/networkd-dhcp4.h b/src/network/networkd-dhcp4.h new file mode 100644 index 0000000..daab5b1 --- /dev/null +++ b/src/network/networkd-dhcp4.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "conf-parser.h" + +typedef struct Link Link; + +typedef enum DHCPClientIdentifier { + DHCP_CLIENT_ID_MAC, + DHCP_CLIENT_ID_DUID, + /* The following option may not be good for RFC regarding DHCP (3315 and 4361). + * But some setups require this. E.g., Sky Broadband, the second largest provider in the UK + * requires the client id to be set to a custom string, reported at + * https://github.com/systemd/systemd/issues/7828 */ + DHCP_CLIENT_ID_DUID_ONLY, + _DHCP_CLIENT_ID_MAX, + _DHCP_CLIENT_ID_INVALID = -1, +} DHCPClientIdentifier; + +int dhcp4_configure(Link *link); +int dhcp4_update_mac(Link *link); + +int link_deserialize_dhcp4(Link *link, const char *dhcp4_address); + +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_client_identifier); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_acl_ip_address); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_max_attempts); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_ip_service_type); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_mud_url); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_fallback_lease_lifetime); diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c new file mode 100644 index 0000000..d4d4182 --- /dev/null +++ b/src/network/networkd-dhcp6.c @@ -0,0 +1,1719 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2014 Intel Corporation. All rights reserved. +***/ + +#include <netinet/in.h> +#include <linux/if.h> +#include <linux/if_arp.h> + +#include "sd-dhcp6-client.h" + +#include "escape.h" +#include "hashmap.h" +#include "hostname-util.h" +#include "missing_network.h" +#include "network-internal.h" +#include "networkd-address.h" +#include "networkd-dhcp6.h" +#include "networkd-link.h" +#include "networkd-manager.h" +#include "networkd-radv.h" +#include "siphash24.h" +#include "string-table.h" +#include "string-util.h" +#include "radv-internal.h" +#include "web-util.h" + +bool link_dhcp6_pd_is_enabled(Link *link) { + assert(link); + + if (!link->network) + return false; + + return link->network->dhcp6_pd; +} + +static bool dhcp6_lease_has_pd_prefix(sd_dhcp6_lease *lease) { + uint32_t lifetime_preferred, lifetime_valid; + union in_addr_union pd_prefix; + uint8_t pd_prefix_len; + + if (!lease) + return false; + + sd_dhcp6_lease_reset_pd_prefix_iter(lease); + + return sd_dhcp6_lease_get_pd(lease, &pd_prefix.in6, &pd_prefix_len, &lifetime_preferred, &lifetime_valid) >= 0; +} + +DHCP6DelegatedPrefix *dhcp6_pd_free(DHCP6DelegatedPrefix *p) { + if (!p) + return NULL; + + if (p->link && p->link->manager) { + hashmap_remove(p->link->manager->dhcp6_prefixes, &p->prefix); + set_remove(p->link->manager->dhcp6_pd_prefixes, p); + } + + link_unref(p->link); + return mfree(p); +} + +static void dhcp6_pd_hash_func(const DHCP6DelegatedPrefix *p, struct siphash *state) { + assert(p); + + siphash24_compress(&p->pd_prefix, sizeof(p->pd_prefix), state); + siphash24_compress(&p->link, sizeof(p->link), state); +} + +static int dhcp6_pd_compare_func(const DHCP6DelegatedPrefix *a, const DHCP6DelegatedPrefix *b) { + int r; + + r = memcmp(&a->pd_prefix, &b->pd_prefix, sizeof(a->pd_prefix)); + if (r != 0) + return r; + + return CMP(a->link, b->link); +} + +DEFINE_HASH_OPS(dhcp6_pd_hash_ops, DHCP6DelegatedPrefix, dhcp6_pd_hash_func, dhcp6_pd_compare_func); + +static Link *dhcp6_pd_get_link_by_prefix(Link *link, const union in_addr_union *prefix) { + DHCP6DelegatedPrefix *pd; + + assert(link); + assert(link->manager); + assert(prefix); + + pd = hashmap_get(link->manager->dhcp6_prefixes, &prefix->in6); + if (!pd) + return NULL; + + return pd->link; +} + +static int dhcp6_pd_get_assigned_prefix(Link *link, const union in_addr_union *pd_prefix, union in_addr_union *ret_prefix) { + DHCP6DelegatedPrefix *pd, in; + + assert(link); + assert(link->manager); + assert(pd_prefix); + assert(ret_prefix); + + in = (DHCP6DelegatedPrefix) { + .pd_prefix = pd_prefix->in6, + .link = link, + }; + + pd = set_get(link->manager->dhcp6_pd_prefixes, &in); + if (!pd) + return -ENOENT; + + ret_prefix->in6 = pd->prefix; + return 0; +} + +static int dhcp6_pd_remove_old(Link *link, bool force); + +static int dhcp6_pd_address_callback(Address *address) { + Address *a; + + assert(address); + assert(address->link); + + /* Make this called only once */ + SET_FOREACH(a, address->link->dhcp6_pd_addresses) + a->callback = NULL; + + return dhcp6_pd_remove_old(address->link, true); +} + +static int dhcp6_pd_remove_old(Link *link, bool force) { + Address *address; + Route *route; + int k, r = 0; + + assert(link); + assert(link->manager); + + if (!force && (link->dhcp6_pd_address_messages != 0 || link->dhcp6_pd_route_configured != 0)) + return 0; + + if (set_isempty(link->dhcp6_pd_addresses_old) && set_isempty(link->dhcp6_pd_routes_old)) + return 0; + + if (!force) { + bool set_callback = !set_isempty(link->dhcp6_pd_addresses); + + SET_FOREACH(address, link->dhcp6_pd_addresses) + if (address_is_ready(address)) { + set_callback = false; + break; + } + + if (set_callback) { + SET_FOREACH(address, link->dhcp6_pd_addresses) + address->callback = dhcp6_pd_address_callback; + return 0; + } + } + + log_link_debug(link, "Removing old DHCPv6 Prefix Delegation addresses and routes."); + + link_dirty(link); + + SET_FOREACH(route, link->dhcp6_pd_routes_old) { + k = route_remove(route, NULL, link, NULL); + if (k < 0) + r = k; + + if (link->radv) + (void) sd_radv_remove_prefix(link->radv, &route->dst.in6, 64); + dhcp6_pd_free(hashmap_get(link->manager->dhcp6_prefixes, &route->dst.in6)); + } + + SET_FOREACH(address, link->dhcp6_pd_addresses_old) { + k = address_remove(address, link, NULL); + if (k < 0) + r = k; + } + + return r; +} + +int dhcp6_pd_remove(Link *link) { + Address *address; + Route *route; + int k, r = 0; + + assert(link); + assert(link->manager); + + if (!link_dhcp6_pd_is_enabled(link)) + return 0; + + link->dhcp6_pd_address_configured = false; + link->dhcp6_pd_route_configured = false; + + k = dhcp6_pd_remove_old(link, true); + if (k < 0) + r = k; + + if (set_isempty(link->dhcp6_pd_addresses) && set_isempty(link->dhcp6_pd_routes)) + return r; + + log_link_debug(link, "Removing DHCPv6 Prefix Delegation addresses and routes."); + + link_dirty(link); + + SET_FOREACH(route, link->dhcp6_pd_routes) { + k = route_remove(route, NULL, link, NULL); + if (k < 0) + r = k; + + if (link->radv) + (void) sd_radv_remove_prefix(link->radv, &route->dst.in6, 64); + dhcp6_pd_free(hashmap_get(link->manager->dhcp6_prefixes, &route->dst.in6)); + } + + SET_FOREACH(address, link->dhcp6_pd_addresses) { + k = address_remove(address, link, NULL); + if (k < 0) + r = k; + } + + return r; +} + +static int dhcp6_pd_route_handler(sd_netlink *nl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + assert(link->dhcp6_pd_route_messages > 0); + + link->dhcp6_pd_route_messages--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EEXIST) { + log_link_message_warning_errno(link, m, r, "Failed to add DHCPv6 Prefix Delegation route"); + link_enter_failed(link); + return 1; + } + + if (link->dhcp6_pd_route_messages == 0) { + log_link_debug(link, "DHCPv6 prefix delegation routes set"); + if (link->dhcp6_pd_prefixes_assigned) + link->dhcp6_pd_route_configured = true; + + r = dhcp6_pd_remove_old(link, false); + if (r < 0) { + link_enter_failed(link); + return 1; + } + + link_check_ready(link); + } + + return 1; +} + +static int dhcp6_set_pd_route(Link *link, const union in_addr_union *prefix, const union in_addr_union *pd_prefix) { + _cleanup_(dhcp6_pd_freep) DHCP6DelegatedPrefix *pd = NULL; + _cleanup_(route_freep) Route *route = NULL; + Link *assigned_link; + Route *ret; + int r; + + assert(link); + assert(link->manager); + assert(prefix); + assert(pd_prefix); + + r = route_new(&route); + if (r < 0) + return r; + + route->family = AF_INET6; + route->dst = *prefix; + route->dst_prefixlen = 64; + + r = route_configure(route, link, dhcp6_pd_route_handler, &ret); + if (r < 0) + return log_link_error_errno(link, r, "Failed to set DHCPv6 prefix route: %m"); + + link->dhcp6_pd_route_messages++; + + r = set_ensure_put(&link->dhcp6_pd_routes, &route_hash_ops, ret); + if (r < 0) + return log_link_error_errno(link, r, "Failed to store DHCPv6 prefix route: %m"); + + (void) set_remove(link->dhcp6_pd_routes_old, ret); + + assigned_link = dhcp6_pd_get_link_by_prefix(link, prefix); + if (assigned_link) { + assert(assigned_link == link); + return 0; + } + + pd = new(DHCP6DelegatedPrefix, 1); + if (!pd) + return log_oom(); + + *pd = (DHCP6DelegatedPrefix) { + .prefix = prefix->in6, + .pd_prefix = pd_prefix->in6, + .link = link_ref(link), + }; + + r = hashmap_ensure_allocated(&link->manager->dhcp6_prefixes, &in6_addr_hash_ops); + if (r < 0) + return log_oom(); + + r = hashmap_put(link->manager->dhcp6_prefixes, &pd->prefix, pd); + if (r < 0) + return log_link_error_errno(link, r, "Failed to store DHCPv6 prefix route at manager: %m"); + + r = set_ensure_put(&link->manager->dhcp6_pd_prefixes, &dhcp6_pd_hash_ops, pd); + if (r < 0) + return log_link_error_errno(link, r, "Failed to store DHCPv6 prefix route at manager: %m"); + + TAKE_PTR(pd); + return 0; +} + +static int dhcp6_pd_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + assert(link->dhcp6_pd_address_messages > 0); + + link->dhcp6_pd_address_messages--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EEXIST) { + log_link_message_warning_errno(link, m, r, "Could not set DHCPv6 delegated prefix address"); + link_enter_failed(link); + return 1; + } else if (r >= 0) + (void) manager_rtnl_process_address(rtnl, m, link->manager); + + if (link->dhcp6_pd_address_messages == 0) { + log_link_debug(link, "DHCPv6 delegated prefix addresses set"); + if (link->dhcp6_pd_prefixes_assigned) + link->dhcp6_pd_address_configured = true; + + r = dhcp6_pd_remove_old(link, false); + if (r < 0) { + link_enter_failed(link); + return 1; + } + + r = link_set_routes(link); + if (r < 0) { + link_enter_failed(link); + return 1; + } + } + + return 1; +} + +static int dhcp6_set_pd_address(Link *link, + const union in_addr_union *prefix, + uint8_t prefix_len, + uint32_t lifetime_preferred, + uint32_t lifetime_valid) { + + _cleanup_(address_freep) Address *address = NULL; + Address *ret; + int r; + + assert(link); + assert(link->network); + assert(prefix); + + if (!link->network->dhcp6_pd_assign) + return 0; + + r = address_new(&address); + if (r < 0) + return log_link_error_errno(link, r, "Failed to allocate address for DHCPv6 delegated prefix: %m"); + + address->in_addr = *prefix; + + if (!in_addr_is_null(AF_INET6, &link->network->dhcp6_pd_token)) + memcpy(address->in_addr.in6.s6_addr + 8, link->network->dhcp6_pd_token.in6.s6_addr + 8, 8); + else { + r = generate_ipv6_eui_64_address(link, &address->in_addr.in6); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to generate EUI64 address for acquired DHCPv6 delegated prefix: %m"); + } + + address->prefixlen = prefix_len; + address->family = AF_INET6; + address->cinfo.ifa_prefered = lifetime_preferred; + address->cinfo.ifa_valid = lifetime_valid; + + r = address_configure(address, link, dhcp6_pd_address_handler, true, &ret); + if (r < 0) + return log_link_error_errno(link, r, "Failed to set DHCPv6 delegated prefix address: %m"); + + link->dhcp6_pd_address_messages++; + + r = set_ensure_put(&link->dhcp6_pd_addresses, &address_hash_ops, ret); + if (r < 0) + return log_link_error_errno(link, r, "Failed to store DHCPv6 delegated prefix address: %m"); + + (void) set_remove(link->dhcp6_pd_addresses_old, ret); + + return 0; +} + +static int dhcp6_pd_assign_prefix(Link *link, const union in_addr_union *prefix, const union in_addr_union *pd_prefix, + uint8_t prefix_len, uint32_t lifetime_preferred, uint32_t lifetime_valid) { + int r; + + assert(link); + assert(link->network); + assert(prefix); + + if (link->network->dhcp6_pd_announce) { + r = radv_add_prefix(link, &prefix->in6, prefix_len, lifetime_preferred, lifetime_valid); + if (r < 0) + return r; + } + + r = dhcp6_set_pd_route(link, prefix, pd_prefix); + if (r < 0) + return r; + + r = dhcp6_set_pd_address(link, prefix, prefix_len, lifetime_preferred, lifetime_valid); + if (r < 0) + return r; + + return 0; +} + +static bool link_has_preferred_subnet_id(Link *link) { + if (!link->network) + return false; + + return link->network->dhcp6_pd_subnet_id >= 0; +} + +static int dhcp6_get_preferred_delegated_prefix( + Link *link, + const union in_addr_union *masked_pd_prefix, + uint8_t pd_prefix_len, + union in_addr_union *ret) { + + /* We start off with the original PD prefix we have been assigned and iterate from there */ + union in_addr_union prefix; + uint64_t n_prefixes; + Link *assigned_link; + int r; + + assert(link); + assert(link->manager); + assert(masked_pd_prefix); + assert(pd_prefix_len <= 64); + + n_prefixes = UINT64_C(1) << (64 - pd_prefix_len); + prefix = *masked_pd_prefix; + + if (link_has_preferred_subnet_id(link)) { + uint64_t subnet_id = link->network->dhcp6_pd_subnet_id; + + /* If the link has a preference for a particular subnet id try to allocate that */ + if (subnet_id >= n_prefixes) + return log_link_warning_errno(link, SYNTHETIC_ERRNO(ERANGE), + "subnet id %" PRIu64 " is out of range. Only have %" PRIu64 " subnets.", + subnet_id, n_prefixes); + + r = in_addr_prefix_nth(AF_INET6, &prefix, 64, subnet_id); + if (r < 0) + return log_link_warning_errno(link, r, + "subnet id %" PRIu64 " is out of range. Only have %" PRIu64 " subnets.", + subnet_id, n_prefixes); + + /* Verify that the prefix we did calculate fits in the pd prefix. + * This should not fail as we checked the prefix size beforehand */ + assert_se(in_addr_prefix_covers(AF_INET6, masked_pd_prefix, pd_prefix_len, &prefix) > 0); + + assigned_link = dhcp6_pd_get_link_by_prefix(link, &prefix); + if (assigned_link && assigned_link != link) { + _cleanup_free_ char *assigned_buf = NULL; + + (void) in_addr_to_string(AF_INET6, &prefix, &assigned_buf); + return log_link_warning_errno(link, SYNTHETIC_ERRNO(EAGAIN), + "The requested prefix %s is already assigned to another link.", + strna(assigned_buf)); + } + + *ret = prefix; + return 0; + } + + for (uint64_t n = 0; n < n_prefixes; n++) { + /* If we do not have an allocation preference just iterate + * through the address space and return the first free prefix. */ + assigned_link = dhcp6_pd_get_link_by_prefix(link, &prefix); + if (!assigned_link || assigned_link == link) { + *ret = prefix; + return 0; + } + + r = in_addr_prefix_next(AF_INET6, &prefix, 64); + if (r < 0) + return log_link_warning_errno(link, r, "Can't allocate another prefix. Out of address space?: %m"); + } + + return log_link_warning_errno(link, SYNTHETIC_ERRNO(ERANGE), "Couldn't find a suitable prefix. Ran out of address space."); +} + +static void dhcp6_pd_prefix_distribute(Link *dhcp6_link, + const union in_addr_union *masked_pd_prefix, + uint8_t pd_prefix_len, + uint32_t lifetime_preferred, + uint32_t lifetime_valid, + bool assign_preferred_subnet_id) { + + Link *link; + int r; + + assert(dhcp6_link); + assert(dhcp6_link->manager); + assert(masked_pd_prefix); + assert(pd_prefix_len <= 64); + + HASHMAP_FOREACH(link, dhcp6_link->manager->links) { + _cleanup_free_ char *assigned_buf = NULL; + union in_addr_union assigned_prefix; + + if (link == dhcp6_link) + continue; + + if (!link_dhcp6_pd_is_enabled(link)) + continue; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + continue; + + if (assign_preferred_subnet_id != link_has_preferred_subnet_id(link)) + continue; + + r = dhcp6_pd_get_assigned_prefix(link, masked_pd_prefix, &assigned_prefix); + if (r < 0) { + r = dhcp6_get_preferred_delegated_prefix(link, masked_pd_prefix, pd_prefix_len, &assigned_prefix); + if (r < 0) { + link->dhcp6_pd_prefixes_assigned = false; + continue; + } + } + + (void) in_addr_to_string(AF_INET6, &assigned_prefix, &assigned_buf); + r = dhcp6_pd_assign_prefix(link, &assigned_prefix, masked_pd_prefix, 64, + lifetime_preferred, lifetime_valid); + if (r < 0) { + log_link_error_errno(link, r, "Unable to assign/update prefix %s/64: %m", + strna(assigned_buf)); + link_enter_failed(link); + } else + log_link_debug(link, "Assigned prefix %s/64", strna(assigned_buf)); + } +} + +static int dhcp6_pd_prepare(Link *link) { + Address *address; + Route *route; + int r; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 0; + + if (!link_dhcp6_pd_is_enabled(link)) + return 0; + + link_dirty(link); + + link->dhcp6_pd_address_configured = false; + link->dhcp6_pd_route_configured = false; + link->dhcp6_pd_prefixes_assigned = true; + + while ((address = set_steal_first(link->dhcp6_pd_addresses))) { + r = set_ensure_put(&link->dhcp6_pd_addresses_old, &address_hash_ops, address); + if (r < 0) + return log_link_error_errno(link, r, "Failed to store old DHCPv6 Prefix Delegation address: %m"); + } + + while ((route = set_steal_first(link->dhcp6_pd_routes))) { + r = set_ensure_put(&link->dhcp6_pd_routes_old, &route_hash_ops, route); + if (r < 0) + return log_link_error_errno(link, r, "Failed to store old DHCPv6 Prefix Delegation route: %m"); + } + + return 0; +} + +static int dhcp6_pd_finalize(Link *link) { + int r; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 0; + + if (!link_dhcp6_pd_is_enabled(link)) + return 0; + + if (link->dhcp6_pd_address_messages == 0) { + if (link->dhcp6_pd_prefixes_assigned) + link->dhcp6_pd_address_configured = true; + } else { + log_link_debug(link, "Setting DHCPv6 PD addresses"); + /* address_handler calls link_set_routes() and link_set_nexthop(). Before they are + * called, the related flags must be cleared. Otherwise, the link becomes configured + * state before routes are configured. */ + link->static_routes_configured = false; + link->static_nexthops_configured = false; + } + + if (link->dhcp6_pd_route_messages == 0) { + if (link->dhcp6_pd_prefixes_assigned) + link->dhcp6_pd_route_configured = true; + } else + log_link_debug(link, "Setting DHCPv6 PD routes"); + + r = dhcp6_pd_remove_old(link, false); + if (r < 0) + return r; + + if (link->dhcp6_pd_address_configured && link->dhcp6_pd_route_configured) + link_check_ready(link); + else + link_set_state(link, LINK_STATE_CONFIGURING); + + return 0; +} + +static void dhcp6_pd_prefix_lost(Link *dhcp6_link) { + Link *link; + int r; + + assert(dhcp6_link); + assert(dhcp6_link->manager); + + HASHMAP_FOREACH(link, dhcp6_link->manager->links) { + if (link == dhcp6_link) + continue; + + r = dhcp6_pd_remove(link); + if (r < 0) + link_enter_failed(link); + } +} + +static int dhcp6_remove_old(Link *link, bool force); + +static int dhcp6_address_callback(Address *address) { + Address *a; + + assert(address); + assert(address->link); + + /* Make this called only once */ + SET_FOREACH(a, address->link->dhcp6_addresses) + a->callback = NULL; + + return dhcp6_remove_old(address->link, true); +} + +static int dhcp6_remove_old(Link *link, bool force) { + Address *address; + Route *route; + int k, r = 0; + + assert(link); + + if (!force && (!link->dhcp6_address_configured || !link->dhcp6_route_configured)) + return 0; + + if (set_isempty(link->dhcp6_addresses_old) && set_isempty(link->dhcp6_routes_old)) + return 0; + + if (!force) { + bool set_callback = !set_isempty(link->dhcp6_addresses); + + SET_FOREACH(address, link->dhcp6_addresses) + if (address_is_ready(address)) { + set_callback = false; + break; + } + + if (set_callback) { + SET_FOREACH(address, link->dhcp6_addresses) + address->callback = dhcp6_address_callback; + return 0; + } + } + + log_link_debug(link, "Removing old DHCPv6 addresses and routes."); + + link_dirty(link); + + SET_FOREACH(route, link->dhcp6_routes_old) { + k = route_remove(route, NULL, link, NULL); + if (k < 0) + r = k; + } + + SET_FOREACH(address, link->dhcp6_addresses_old) { + k = address_remove(address, link, NULL); + if (k < 0) + r = k; + } + + return r; +} + +static int dhcp6_remove(Link *link) { + Address *address; + Route *route; + int k, r = 0; + + assert(link); + + link->dhcp6_address_configured = false; + link->dhcp6_route_configured = false; + + k = dhcp6_remove_old(link, true); + if (k < 0) + r = k; + + if (set_isempty(link->dhcp6_addresses) && set_isempty(link->dhcp6_routes)) + return r; + + log_link_debug(link, "Removing DHCPv6 addresses and routes."); + + link_dirty(link); + + SET_FOREACH(route, link->dhcp6_routes) { + k = route_remove(route, NULL, link, NULL); + if (k < 0) + r = k; + } + + SET_FOREACH(address, link->dhcp6_addresses) { + k = address_remove(address, link, NULL); + if (k < 0) + r = k; + } + + return r; +} + +static int dhcp6_route_handler(sd_netlink *nl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + assert(link->dhcp6_route_messages > 0); + + link->dhcp6_route_messages--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EEXIST) { + log_link_message_warning_errno(link, m, r, "Failed to add unreachable route for DHCPv6 delegated subnet"); + link_enter_failed(link); + return 1; + } + + if (link->dhcp6_route_messages == 0) { + log_link_debug(link, "Unreachable routes for DHCPv6 delegated subnets set"); + link->dhcp6_route_configured = true; + + r = dhcp6_remove_old(link, false); + if (r < 0) { + link_enter_failed(link); + return 1; + } + + link_check_ready(link); + } + + return 1; +} + +static int dhcp6_set_unreachable_route(Link *link, const union in_addr_union *addr, uint8_t prefixlen) { + _cleanup_(route_freep) Route *route = NULL; + _cleanup_free_ char *buf = NULL; + Route *ret; + int r; + + assert(link); + assert(addr); + + (void) in_addr_to_string(AF_INET6, addr, &buf); + + if (prefixlen > 64) { + log_link_debug(link, "PD Prefix length > 64, ignoring prefix %s/%u", + strna(buf), prefixlen); + return 0; + } + + if (prefixlen == 64) { + log_link_debug(link, "Not adding a blocking route for DHCPv6 delegated subnet %s/64 since distributed prefix is 64", + strna(buf)); + return 1; + } + + if (prefixlen < 48) + log_link_warning(link, "PD Prefix length < 48, looks unusual %s/%u", + strna(buf), prefixlen); + + r = route_new(&route); + if (r < 0) + return log_oom(); + + route->family = AF_INET6; + route->dst = *addr; + route->dst_prefixlen = prefixlen; + route->table = link_get_dhcp_route_table(link); + route->type = RTN_UNREACHABLE; + + r = route_configure(route, link, dhcp6_route_handler, &ret); + if (r < 0) + return log_link_error_errno(link, r, "Failed to set unreachable route for DHCPv6 delegated subnet %s/%u: %m", + strna(buf), prefixlen); + + link->dhcp6_route_messages++; + + r = set_ensure_put(&link->dhcp6_routes, &route_hash_ops, ret); + if (r < 0) + return log_link_error_errno(link, r, "Failed to store unreachable route for DHCPv6 delegated subnet %s/%u: %m", + strna(buf), prefixlen); + + (void) set_remove(link->dhcp6_routes_old, ret); + + return 1; +} + +static int dhcp6_pd_prefix_acquired(Link *dhcp6_link) { + Link *link; + int r; + + assert(dhcp6_link); + assert(dhcp6_link->dhcp6_lease); + + HASHMAP_FOREACH(link, dhcp6_link->manager->links) { + if (link == dhcp6_link) + continue; + + r = dhcp6_pd_prepare(link); + if (r < 0) + link_enter_failed(link); + } + + for (sd_dhcp6_lease_reset_pd_prefix_iter(dhcp6_link->dhcp6_lease);;) { + uint32_t lifetime_preferred, lifetime_valid; + union in_addr_union pd_prefix, prefix; + uint8_t pd_prefix_len; + + r = sd_dhcp6_lease_get_pd(dhcp6_link->dhcp6_lease, &pd_prefix.in6, &pd_prefix_len, + &lifetime_preferred, &lifetime_valid); + if (r < 0) + break; + + r = dhcp6_set_unreachable_route(dhcp6_link, &pd_prefix, pd_prefix_len); + if (r < 0) + return r; + if (r == 0) + continue; + + /* We are doing prefix allocation in two steps: + * 1. all those links that have a preferred subnet id will be assigned their subnet + * 2. all those links that remain will receive prefixes in sequential order. Prefixes + * that were previously already allocated to another link will be skipped. + * The assignment has to be split in two phases since subnet id + * preferences should be honored. Meaning that any subnet id should be + * handed out to the requesting link and not to some link that didn't + * specify any preference. */ + + assert(pd_prefix_len <= 64); + + prefix = pd_prefix; + r = in_addr_mask(AF_INET6, &prefix, pd_prefix_len); + if (r < 0) + return log_link_error_errno(dhcp6_link, r, "Failed to mask DHCPv6 PD prefix: %m"); + + if (DEBUG_LOGGING) { + uint64_t n_prefixes = UINT64_C(1) << (64 - pd_prefix_len); + _cleanup_free_ char *buf = NULL; + + (void) in_addr_to_string(AF_INET6, &prefix, &buf); + log_link_debug(dhcp6_link, "Assigning up to %" PRIu64 " prefixes from %s/%u", + n_prefixes, strna(buf), pd_prefix_len); + } + + dhcp6_pd_prefix_distribute(dhcp6_link, + &prefix, + pd_prefix_len, + lifetime_preferred, + lifetime_valid, + true); + + dhcp6_pd_prefix_distribute(dhcp6_link, + &prefix, + pd_prefix_len, + lifetime_preferred, + lifetime_valid, + false); + } + + HASHMAP_FOREACH(link, dhcp6_link->manager->links) { + if (link == dhcp6_link) + continue; + + r = dhcp6_pd_finalize(link); + if (r < 0) + link_enter_failed(link); + } + + return 0; +} + +static int dhcp6_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + assert(link->dhcp6_address_messages > 0); + + link->dhcp6_address_messages--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EEXIST) { + log_link_message_warning_errno(link, m, r, "Could not set DHCPv6 address"); + link_enter_failed(link); + return 1; + } else if (r >= 0) + (void) manager_rtnl_process_address(rtnl, m, link->manager); + + if (link->dhcp6_address_messages == 0) { + log_link_debug(link, "DHCPv6 addresses set"); + link->dhcp6_address_configured = true; + + r = dhcp6_remove_old(link, false); + if (r < 0) { + link_enter_failed(link); + return 1; + } + + r = link_set_routes(link); + if (r < 0) { + link_enter_failed(link); + return 1; + } + } + + return 1; +} + +static int dhcp6_update_address( + Link *link, + const struct in6_addr *ip6_addr, + uint32_t lifetime_preferred, + uint32_t lifetime_valid) { + + _cleanup_(address_freep) Address *addr = NULL; + _cleanup_free_ char *buffer = NULL; + Address *ret; + int r; + + r = address_new(&addr); + if (r < 0) + return log_oom(); + + addr->family = AF_INET6; + addr->in_addr.in6 = *ip6_addr; + addr->flags = IFA_F_NOPREFIXROUTE; + addr->prefixlen = 128; + addr->cinfo.ifa_prefered = lifetime_preferred; + addr->cinfo.ifa_valid = lifetime_valid; + + (void) in_addr_to_string(addr->family, &addr->in_addr, &buffer); + log_link_full(link, set_contains(link->dhcp6_addresses, addr) ? LOG_DEBUG : LOG_INFO, + "DHCPv6 address %s/%u timeout preferred %d valid %d", + strna(buffer), addr->prefixlen, lifetime_preferred, lifetime_valid); + + r = address_configure(addr, link, dhcp6_address_handler, true, &ret); + if (r < 0) + return log_link_error_errno(link, r, "Failed to set DHCPv6 address %s/%u: %m", + strna(buffer), addr->prefixlen); + + link->dhcp6_address_messages++; + + r = set_ensure_put(&link->dhcp6_addresses, &address_hash_ops, ret); + if (r < 0) + return log_link_error_errno(link, r, "Failed to store DHCPv6 address %s/%u: %m", + strna(buffer), addr->prefixlen); + + (void) set_remove(link->dhcp6_addresses_old, ret); + + return 0; +} + +static int dhcp6_address_acquired(Link *link) { + int r; + + assert(link); + assert(link->dhcp6_lease); + + for (sd_dhcp6_lease_reset_address_iter(link->dhcp6_lease);;) { + uint32_t lifetime_preferred, lifetime_valid; + struct in6_addr ip6_addr; + + r = sd_dhcp6_lease_get_address(link->dhcp6_lease, &ip6_addr, &lifetime_preferred, &lifetime_valid); + if (r < 0) + break; + + r = dhcp6_update_address(link, &ip6_addr, lifetime_preferred, lifetime_valid); + if (r < 0) + return r; + } + + return 0; +} + +static int dhcp6_lease_ip_acquired(sd_dhcp6_client *client, Link *link) { + _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease_old = NULL; + sd_dhcp6_lease *lease; + Address *a; + Route *rt; + int r; + + link->dhcp6_address_configured = false; + link->dhcp6_route_configured = false; + + link_dirty(link); + + while ((a = set_steal_first(link->dhcp6_addresses))) { + r = set_ensure_put(&link->dhcp6_addresses_old, &address_hash_ops, a); + if (r < 0) + return log_link_error_errno(link, r, "Failed to store old DHCPv6 address: %m"); + } + + while ((rt = set_steal_first(link->dhcp6_routes))) { + r = set_ensure_put(&link->dhcp6_routes_old, &route_hash_ops, rt); + if (r < 0) + return log_link_error_errno(link, r, "Failed to store old DHCPv6 route: %m"); + } + + r = sd_dhcp6_client_get_lease(client, &lease); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get DHCPv6 lease: %m"); + + lease_old = TAKE_PTR(link->dhcp6_lease); + link->dhcp6_lease = sd_dhcp6_lease_ref(lease); + + r = dhcp6_address_acquired(link); + if (r < 0) + return r; + + if (dhcp6_lease_has_pd_prefix(lease)) { + r = dhcp6_pd_prefix_acquired(link); + if (r < 0) + return r; + } else if (dhcp6_lease_has_pd_prefix(lease_old)) + /* When we had PD prefixes but not now, we need to remove them. */ + dhcp6_pd_prefix_lost(link); + + if (link->dhcp6_address_messages == 0) + link->dhcp6_address_configured = true; + else { + log_link_debug(link, "Setting DHCPv6 addresses"); + /* address_handler calls link_set_routes() and link_set_nexthop(). Before they are + * called, the related flags must be cleared. Otherwise, the link becomes configured + * state before routes are configured. */ + link->static_routes_configured = false; + link->static_nexthops_configured = false; + } + + if (link->dhcp6_route_messages == 0) + link->dhcp6_route_configured = true; + else + log_link_debug(link, "Setting unreachable routes for DHCPv6 delegated subnets"); + + r = dhcp6_remove_old(link, false); + if (r < 0) + return r; + + if (link->dhcp6_address_configured && link->dhcp6_route_configured) + link_check_ready(link); + else + link_set_state(link, LINK_STATE_CONFIGURING); + + return 0; +} + +static int dhcp6_lease_information_acquired(sd_dhcp6_client *client, Link *link) { + return 0; +} + +static int dhcp6_lease_lost(Link *link) { + int r; + + assert(link); + assert(link->manager); + + log_link_info(link, "DHCPv6 lease lost"); + + if (dhcp6_lease_has_pd_prefix(link->dhcp6_lease)) + dhcp6_pd_prefix_lost(link); + + link->dhcp6_lease = sd_dhcp6_lease_unref(link->dhcp6_lease); + + r = dhcp6_remove(link); + if (r < 0) + return r; + + return 0; +} + +static void dhcp6_handler(sd_dhcp6_client *client, int event, void *userdata) { + Link *link = userdata; + int r; + + assert(link); + assert(link->network); + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return; + + switch (event) { + case SD_DHCP6_CLIENT_EVENT_STOP: + case SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE: + case SD_DHCP6_CLIENT_EVENT_RETRANS_MAX: + r = dhcp6_lease_lost(link); + if (r < 0) + link_enter_failed(link); + break; + + case SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE: + r = dhcp6_lease_ip_acquired(client, link); + if (r < 0) { + link_enter_failed(link); + return; + } + + _fallthrough_; + case SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST: + r = dhcp6_lease_information_acquired(client, link); + if (r < 0) + link_enter_failed(link); + break; + + default: + if (event < 0) + log_link_warning_errno(link, event, "DHCPv6 error: %m"); + else + log_link_warning(link, "DHCPv6 unknown event: %d", event); + return; + } +} + +int dhcp6_request_address(Link *link, int ir) { + int r, inf_req, pd; + bool running; + + assert(link); + assert(link->dhcp6_client); + assert(link->network); + assert(in_addr_is_link_local(AF_INET6, (const union in_addr_union*) &link->ipv6ll_address) > 0); + + r = sd_dhcp6_client_is_running(link->dhcp6_client); + if (r < 0) + return r; + running = r; + + r = sd_dhcp6_client_get_prefix_delegation(link->dhcp6_client, &pd); + if (r < 0) + return r; + + if (pd && ir && link->network->dhcp6_force_pd_other_information) { + log_link_debug(link, "Enabling managed mode to request DHCPv6 PD with 'Other Information' set"); + + r = sd_dhcp6_client_set_address_request(link->dhcp6_client, false); + if (r < 0) + return r; + + ir = false; + } + + if (running) { + r = sd_dhcp6_client_get_information_request(link->dhcp6_client, &inf_req); + if (r < 0) + return r; + + if (inf_req == ir) + return 0; + + r = sd_dhcp6_client_stop(link->dhcp6_client); + if (r < 0) + return r; + } else { + r = sd_dhcp6_client_set_local_address(link->dhcp6_client, &link->ipv6ll_address); + if (r < 0) + return r; + } + + r = sd_dhcp6_client_set_information_request(link->dhcp6_client, ir); + if (r < 0) + return r; + + r = sd_dhcp6_client_start(link->dhcp6_client); + if (r < 0) + return r; + + return 0; +} + +int dhcp6_request_prefix_delegation(Link *link) { + Link *l; + + assert(link); + assert(link->manager); + + if (!link_dhcp6_pd_is_enabled(link)) + return 0; + + log_link_debug(link, "Requesting DHCPv6 prefixes to be delegated for new link"); + + HASHMAP_FOREACH(l, link->manager->links) { + int r, enabled; + + if (l == link) + continue; + + if (!l->dhcp6_client) + continue; + + r = sd_dhcp6_client_get_prefix_delegation(l->dhcp6_client, &enabled); + if (r < 0) { + log_link_warning_errno(l, r, "Cannot get prefix delegation when adding new link: %m"); + link_enter_failed(l); + continue; + } + + if (enabled == 0) { + r = sd_dhcp6_client_set_prefix_delegation(l->dhcp6_client, 1); + if (r < 0) { + log_link_warning_errno(l, r, "Cannot enable prefix delegation when adding new link: %m"); + link_enter_failed(l); + continue; + } + } + + r = sd_dhcp6_client_is_running(l->dhcp6_client); + if (r <= 0) + continue; + + if (enabled != 0) { + if (dhcp6_lease_has_pd_prefix(l->dhcp6_lease)) { + log_link_debug(l, "Requesting re-assignment of delegated prefixes after adding new link"); + r = dhcp6_pd_prefix_acquired(l); + if (r < 0) + link_enter_failed(l); + } + continue; + } + + r = sd_dhcp6_client_stop(l->dhcp6_client); + if (r < 0) { + log_link_warning_errno(l, r, "Cannot stop DHCPv6 prefix delegation client after adding new link: %m"); + link_enter_failed(l); + continue; + } + + r = sd_dhcp6_client_start(l->dhcp6_client); + if (r < 0) { + log_link_warning_errno(l, r, "Cannot restart DHCPv6 prefix delegation client after adding new link: %m"); + link_enter_failed(l); + continue; + } + + log_link_debug(l, "Restarted DHCPv6 client to acquire prefix delegations after adding new link"); + } + + /* dhcp6_pd_prefix_acquired() may make the link in failed state. */ + if (link->state == LINK_STATE_FAILED) + return -ENOANO; + + return 0; +} + +static int dhcp6_set_hostname(sd_dhcp6_client *client, Link *link) { + _cleanup_free_ char *hostname = NULL; + const char *hn; + int r; + + assert(link); + + if (!link->network->dhcp_send_hostname) + hn = NULL; + else if (link->network->dhcp_hostname) + hn = link->network->dhcp_hostname; + else { + r = gethostname_strict(&hostname); + if (r < 0 && r != -ENXIO) /* ENXIO: no hostname set or hostname is "localhost" */ + return r; + + hn = hostname; + } + + r = sd_dhcp6_client_set_fqdn(client, hn); + if (r == -EINVAL && hostname) + /* Ignore error when the machine's hostname is not suitable to send in DHCP packet. */ + log_link_warning_errno(link, r, "DHCP6 CLIENT: Failed to set hostname from kernel hostname, ignoring: %m"); + else if (r < 0) + return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set hostname: %m"); + + return 0; +} + +static bool dhcp6_enable_prefix_delegation(Link *dhcp6_link) { + Link *link; + + assert(dhcp6_link); + assert(dhcp6_link->manager); + + HASHMAP_FOREACH(link, dhcp6_link->manager->links) { + if (link == dhcp6_link) + continue; + + if (!link_dhcp6_pd_is_enabled(link)) + continue; + + return true; + } + + return false; +} + +static int dhcp6_set_identifier(Link *link, sd_dhcp6_client *client) { + const DUID *duid; + int r; + + assert(link); + assert(link->network); + assert(client); + + r = sd_dhcp6_client_set_mac(client, link->hw_addr.addr.bytes, link->hw_addr.length, link->iftype); + if (r < 0) + return r; + + if (link->network->iaid_set) { + r = sd_dhcp6_client_set_iaid(client, link->network->iaid); + if (r < 0) + return r; + } + + duid = link_get_duid(link); + if (duid->type == DUID_TYPE_LLT && duid->raw_data_len == 0) + r = sd_dhcp6_client_set_duid_llt(client, duid->llt_time); + else + r = sd_dhcp6_client_set_duid(client, + duid->type, + duid->raw_data_len > 0 ? duid->raw_data : NULL, + duid->raw_data_len); + if (r < 0) + return r; + + return 0; +} + +int dhcp6_configure(Link *link) { + _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL; + sd_dhcp6_option *vendor_option; + sd_dhcp6_option *send_option; + void *request_options; + int r; + + assert(link); + assert(link->network); + + if (!link_dhcp6_enabled(link) && !link_ipv6_accept_ra_enabled(link)) + return 0; + + if (link->dhcp6_client) + return 0; + + r = sd_dhcp6_client_new(&client); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to create DHCP6 client: %m"); + + r = sd_dhcp6_client_attach_event(client, link->manager->event, 0); + if (r < 0) + return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to attach event: %m"); + + r = dhcp6_set_identifier(link, client); + if (r < 0) + return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set identifier: %m"); + + ORDERED_HASHMAP_FOREACH(send_option, link->network->dhcp6_client_send_options) { + r = sd_dhcp6_client_add_option(client, send_option); + if (r == -EEXIST) + continue; + if (r < 0) + return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set option: %m"); + } + + r = dhcp6_set_hostname(client, link); + if (r < 0) + return r; + + r = sd_dhcp6_client_set_ifindex(client, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set ifindex: %m"); + + if (link->network->dhcp6_rapid_commit) { + r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_RAPID_COMMIT); + if (r < 0) + return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set request flag for rapid commit: %m"); + } + + if (link->network->dhcp6_mudurl) { + r = sd_dhcp6_client_set_request_mud_url(client, link->network->dhcp6_mudurl); + if (r < 0) + return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set MUD URL: %m"); + } + + SET_FOREACH(request_options, link->network->dhcp6_request_options) { + uint32_t option = PTR_TO_UINT32(request_options); + + r = sd_dhcp6_client_set_request_option(client, option); + if (r == -EEXIST) { + log_link_debug(link, "DHCP6 CLIENT: Failed to set request flag for '%u' already exists, ignoring.", option); + continue; + } + if (r < 0) + return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set request flag for '%u': %m", option); + } + + if (link->network->dhcp6_user_class) { + r = sd_dhcp6_client_set_request_user_class(client, link->network->dhcp6_user_class); + if (r < 0) + return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set user class: %m"); + } + + if (link->network->dhcp6_vendor_class) { + r = sd_dhcp6_client_set_request_vendor_class(client, link->network->dhcp6_vendor_class); + if (r < 0) + return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set vendor class: %m"); + } + + ORDERED_HASHMAP_FOREACH(vendor_option, link->network->dhcp6_client_send_vendor_options) { + r = sd_dhcp6_client_add_vendor_option(client, vendor_option); + if (r == -EEXIST) + continue; + if (r < 0) + return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set vendor option: %m"); + } + + r = sd_dhcp6_client_set_callback(client, dhcp6_handler, link); + if (r < 0) + return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set callback: %m"); + + if (dhcp6_enable_prefix_delegation(link)) { + r = sd_dhcp6_client_set_prefix_delegation(client, true); + if (r < 0) + return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set prefix delegation: %m"); + } + + if (link->network->dhcp6_pd_length > 0) { + r = sd_dhcp6_client_set_prefix_delegation_hint(client, link->network->dhcp6_pd_length, &link->network->dhcp6_pd_address); + if (r < 0) + return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set prefix hint: %m"); + } + + link->dhcp6_client = TAKE_PTR(client); + + return 0; +} + +int dhcp6_update_mac(Link *link) { + bool restart; + int r; + + assert(link); + + if (!link->dhcp6_client) + return 0; + + restart = sd_dhcp6_client_is_running(link->dhcp6_client) > 0; + + if (restart) { + r = sd_dhcp6_client_stop(link->dhcp6_client); + if (r < 0) + return r; + } + + r = dhcp6_set_identifier(link, link->dhcp6_client); + if (r < 0) + return r; + + if (restart) { + r = sd_dhcp6_client_start(link->dhcp6_client); + if (r < 0) + return log_link_warning_errno(link, r, "Could not restart DHCPv6 client: %m"); + } + + return 0; +} + +int link_serialize_dhcp6_client(Link *link, FILE *f) { + _cleanup_free_ char *duid = NULL; + uint32_t iaid; + int r; + + assert(link); + + if (!link->dhcp6_client) + return 0; + + r = sd_dhcp6_client_get_iaid(link->dhcp6_client, &iaid); + if (r >= 0) + fprintf(f, "DHCP6_CLIENT_IAID=0x%x\n", iaid); + + r = sd_dhcp6_client_duid_as_string(link->dhcp6_client, &duid); + if (r >= 0) + fprintf(f, "DHCP6_CLIENT_DUID=%s\n", duid); + + return 0; +} + +int config_parse_dhcp6_pd_hint( + const char* unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = in_addr_prefix_from_string(rvalue, AF_INET6, (union in_addr_union *) &network->dhcp6_pd_address, &network->dhcp6_pd_length); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse PrefixDelegationHint=%s, ignoring assignment", rvalue); + return 0; + } + + if (network->dhcp6_pd_length < 1 || network->dhcp6_pd_length > 128) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid prefix length='%d', ignoring assignment", network->dhcp6_pd_length); + network->dhcp6_pd_length = 0; + return 0; + } + + return 0; +} + +int config_parse_dhcp6_mud_url( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_free_ char *unescaped = NULL; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + network->dhcp6_mudurl = mfree(network->dhcp6_mudurl); + return 0; + } + + r = cunescape(rvalue, 0, &unescaped); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to Failed to unescape MUD URL, ignoring: %s", rvalue); + return 0; + } + + if (!http_url_is_valid(unescaped) || strlen(unescaped) > UINT8_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Failed to parse MUD URL '%s', ignoring: %m", rvalue); + return 0; + } + + return free_and_replace(network->dhcp6_mudurl, unescaped); +} + +DEFINE_CONFIG_PARSE_ENUM(config_parse_dhcp6_client_start_mode, dhcp6_client_start_mode, DHCP6ClientStartMode, + "Failed to parse WithoutRA= setting"); + +static const char* const dhcp6_client_start_mode_table[_DHCP6_CLIENT_START_MODE_MAX] = { + [DHCP6_CLIENT_START_MODE_NO] = "no", + [DHCP6_CLIENT_START_MODE_INFORMATION_REQUEST] = "information-request", + [DHCP6_CLIENT_START_MODE_SOLICIT] = "solicit", +}; + +DEFINE_STRING_TABLE_LOOKUP(dhcp6_client_start_mode, DHCP6ClientStartMode); + +int config_parse_dhcp6_pd_subnet_id( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + int64_t *p = data; + uint64_t t; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue) || streq(rvalue, "auto")) { + *p = -1; + return 0; + } + + r = safe_atoux64(rvalue, &t); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse %s=, ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + if (t > INT64_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid subnet id '%s', ignoring assignment.", + rvalue); + return 0; + } + + *p = (int64_t) t; + + return 0; +} + +int config_parse_dhcp6_pd_token( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + union in_addr_union *addr = data, tmp; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + *addr = IN_ADDR_NULL; + return 0; + } + + r = in_addr_from_string(AF_INET6, rvalue, &tmp); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse DHCPv6 Prefix Delegation token, ignoring: %s", rvalue); + return 0; + } + + if (in_addr_is_null(AF_INET6, &tmp)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "DHCPv6 Prefix Delegation token cannot be the ANY address, ignoring: %s", rvalue); + return 0; + } + + *addr = tmp; + + return 0; +} diff --git a/src/network/networkd-dhcp6.h b/src/network/networkd-dhcp6.h new file mode 100644 index 0000000..65b35fd --- /dev/null +++ b/src/network/networkd-dhcp6.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-dhcp6-client.h" + +#include "conf-parser.h" +#include "macro.h" + +typedef enum DHCP6ClientStartMode { + DHCP6_CLIENT_START_MODE_NO, + DHCP6_CLIENT_START_MODE_INFORMATION_REQUEST, + DHCP6_CLIENT_START_MODE_SOLICIT, + _DHCP6_CLIENT_START_MODE_MAX, + _DHCP6_CLIENT_START_MODE_INVALID = -1, +} DHCP6ClientStartMode; + +typedef struct Link Link; +typedef struct Manager Manager; + +typedef struct DHCP6DelegatedPrefix { + struct in6_addr prefix; /* Prefix assigned to the link */ + struct in6_addr pd_prefix; /* PD prefix provided by DHCP6 lease */ + Link *link; +} DHCP6DelegatedPrefix; + +DHCP6DelegatedPrefix *dhcp6_pd_free(DHCP6DelegatedPrefix *p); +DEFINE_TRIVIAL_CLEANUP_FUNC(DHCP6DelegatedPrefix*, dhcp6_pd_free); + +bool link_dhcp6_pd_is_enabled(Link *link); +int dhcp6_pd_remove(Link *link); +int dhcp6_configure(Link *link); +int dhcp6_update_mac(Link *link); +int dhcp6_request_address(Link *link, int ir); +int dhcp6_request_prefix_delegation(Link *link); + +int link_serialize_dhcp6_client(Link *link, FILE *f); + +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp6_pd_hint); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp6_mud_url); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp6_client_start_mode); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp6_pd_subnet_id); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp6_pd_token); + +const char* dhcp6_client_start_mode_to_string(DHCP6ClientStartMode i) _const_; +DHCP6ClientStartMode dhcp6_client_start_mode_from_string(const char *s) _pure_; diff --git a/src/network/networkd-fdb.c b/src/network/networkd-fdb.c new file mode 100644 index 0000000..283dece --- /dev/null +++ b/src/network/networkd-fdb.c @@ -0,0 +1,409 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2014 Intel Corporation. All rights reserved. +***/ + +#include <net/ethernet.h> +#include <net/if.h> + +#include "alloc-util.h" +#include "bridge.h" +#include "netlink-util.h" +#include "networkd-fdb.h" +#include "networkd-link.h" +#include "networkd-manager.h" +#include "networkd-network.h" +#include "parse-util.h" +#include "string-table.h" +#include "vlan-util.h" +#include "vxlan.h" + +#define STATIC_FDB_ENTRIES_PER_NETWORK_MAX 1024U + +/* remove and FDB entry. */ +FdbEntry *fdb_entry_free(FdbEntry *fdb_entry) { + if (!fdb_entry) + return NULL; + + if (fdb_entry->network) { + assert(fdb_entry->section); + hashmap_remove(fdb_entry->network->fdb_entries_by_section, fdb_entry->section); + } + + network_config_section_free(fdb_entry->section); + return mfree(fdb_entry); +} + +DEFINE_NETWORK_SECTION_FUNCTIONS(FdbEntry, fdb_entry_free); + +/* create a new FDB entry or get an existing one. */ +static int fdb_entry_new_static( + Network *network, + const char *filename, + unsigned section_line, + FdbEntry **ret) { + + _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; + _cleanup_(fdb_entry_freep) FdbEntry *fdb_entry = NULL; + int r; + + assert(network); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = network_config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + /* search entry in hashmap first. */ + fdb_entry = hashmap_get(network->fdb_entries_by_section, n); + if (fdb_entry) { + *ret = TAKE_PTR(fdb_entry); + return 0; + } + + if (hashmap_size(network->fdb_entries_by_section) >= STATIC_FDB_ENTRIES_PER_NETWORK_MAX) + return -E2BIG; + + /* allocate space for and FDB entry. */ + fdb_entry = new(FdbEntry, 1); + if (!fdb_entry) + return -ENOMEM; + + /* init FDB structure. */ + *fdb_entry = (FdbEntry) { + .network = network, + .section = TAKE_PTR(n), + .vni = VXLAN_VID_MAX + 1, + .fdb_ntf_flags = NEIGHBOR_CACHE_ENTRY_FLAGS_SELF, + }; + + r = hashmap_ensure_allocated(&network->fdb_entries_by_section, &network_config_hash_ops); + if (r < 0) + return r; + + r = hashmap_put(network->fdb_entries_by_section, fdb_entry->section, fdb_entry); + if (r < 0) + return r; + + /* return allocated FDB structure. */ + *ret = TAKE_PTR(fdb_entry); + + return 0; +} + +static int set_fdb_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EEXIST) { + log_link_message_warning_errno(link, m, r, "Could not add FDB entry"); + link_enter_failed(link); + return 1; + } + + return 1; +} + +/* send a request to the kernel to add a FDB entry in its static MAC table. */ +static int fdb_entry_configure(Link *link, FdbEntry *fdb_entry) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(link); + assert(link->network); + assert(link->manager); + assert(fdb_entry); + + /* create new RTM message */ + r = sd_rtnl_message_new_neigh(link->manager->rtnl, &req, RTM_NEWNEIGH, link->ifindex, AF_BRIDGE); + if (r < 0) + return log_link_error_errno(link, r, "Could not create RTM_NEWNEIGH message: %m"); + + r = sd_rtnl_message_neigh_set_flags(req, fdb_entry->fdb_ntf_flags); + if (r < 0) + return log_link_error_errno(link, r, "Could not set neighbor flags: %m"); + + /* only NUD_PERMANENT state supported. */ + r = sd_rtnl_message_neigh_set_state(req, NUD_NOARP | NUD_PERMANENT); + if (r < 0) + return log_link_error_errno(link, r, "Could not set neighbor state: %m"); + + r = sd_netlink_message_append_data(req, NDA_LLADDR, &fdb_entry->mac_addr, sizeof(fdb_entry->mac_addr)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append NDA_LLADDR attribute: %m"); + + /* VLAN Id is optional. We'll add VLAN Id only if it's specified. */ + if (fdb_entry->vlan_id > 0) { + r = sd_netlink_message_append_u16(req, NDA_VLAN, fdb_entry->vlan_id); + if (r < 0) + return log_link_error_errno(link, r, "Could not append NDA_VLAN attribute: %m"); + } + + if (!in_addr_is_null(fdb_entry->family, &fdb_entry->destination_addr)) { + r = netlink_message_append_in_addr_union(req, NDA_DST, fdb_entry->family, &fdb_entry->destination_addr); + if (r < 0) + return log_link_error_errno(link, r, "Could not append NDA_DST attribute: %m"); + } + + if (fdb_entry->vni <= VXLAN_VID_MAX) { + r = sd_netlink_message_append_u32(req, NDA_VNI, fdb_entry->vni); + if (r < 0) + return log_link_error_errno(link, r, "Could not append NDA_VNI attribute: %m"); + } + + /* send message to the kernel to update its internal static MAC table. */ + r = netlink_call_async(link->manager->rtnl, NULL, req, set_fdb_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + return 1; +} + +int link_set_bridge_fdb(Link *link) { + FdbEntry *fdb_entry; + int r; + + assert(link); + assert(link->network); + + HASHMAP_FOREACH(fdb_entry, link->network->fdb_entries_by_section) { + r = fdb_entry_configure(link, fdb_entry); + if (r < 0) + return log_link_error_errno(link, r, "Failed to add MAC entry to static MAC table: %m"); + } + + return 0; +} + +void network_drop_invalid_fdb_entries(Network *network) { + FdbEntry *fdb_entry; + + assert(network); + + HASHMAP_FOREACH(fdb_entry, network->fdb_entries_by_section) + if (section_is_invalid(fdb_entry->section)) + fdb_entry_free(fdb_entry); +} + +/* parse the HW address from config files. */ +int config_parse_fdb_hwaddr( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(fdb_entry_free_or_set_invalidp) FdbEntry *fdb_entry = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = fdb_entry_new_static(network, filename, section_line, &fdb_entry); + if (r < 0) + return log_oom(); + + r = ether_addr_from_string(rvalue, &fdb_entry->mac_addr); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Not a valid MAC address, ignoring assignment: %s", rvalue); + return 0; + } + + fdb_entry = NULL; + + return 0; +} + +/* parse the VLAN Id from config files. */ +int config_parse_fdb_vlan_id( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(fdb_entry_free_or_set_invalidp) FdbEntry *fdb_entry = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = fdb_entry_new_static(network, filename, section_line, &fdb_entry); + if (r < 0) + return log_oom(); + + r = config_parse_vlanid(unit, filename, line, section, + section_line, lvalue, ltype, + rvalue, &fdb_entry->vlan_id, userdata); + if (r < 0) + return r; + + fdb_entry = NULL; + + return 0; +} + +int config_parse_fdb_destination( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(fdb_entry_free_or_set_invalidp) FdbEntry *fdb_entry = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = fdb_entry_new_static(network, filename, section_line, &fdb_entry); + if (r < 0) + return log_oom(); + + r = in_addr_from_string_auto(rvalue, &fdb_entry->family, &fdb_entry->destination_addr); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "FDB destination IP address is invalid, ignoring assignment: %s", + rvalue); + return 0; + } + + fdb_entry = NULL; + + return 0; +} + +int config_parse_fdb_vxlan_vni( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(fdb_entry_free_or_set_invalidp) FdbEntry *fdb_entry = NULL; + Network *network = userdata; + uint32_t vni; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = fdb_entry_new_static(network, filename, section_line, &fdb_entry); + if (r < 0) + return log_oom(); + + r = safe_atou32(rvalue, &vni); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse VXLAN Network Identifier (VNI), ignoring assignment: %s", + rvalue); + return 0; + } + + if (vni > VXLAN_VID_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "FDB invalid VXLAN Network Identifier (VNI), ignoring assignment: %s", + rvalue); + return 0; + } + + fdb_entry->vni = vni; + fdb_entry = NULL; + + return 0; +} + +static const char* const fdb_ntf_flags_table[_NEIGHBOR_CACHE_ENTRY_FLAGS_MAX] = { + [NEIGHBOR_CACHE_ENTRY_FLAGS_USE] = "use", + [NEIGHBOR_CACHE_ENTRY_FLAGS_SELF] = "self", + [NEIGHBOR_CACHE_ENTRY_FLAGS_MASTER] = "master", + [NEIGHBOR_CACHE_ENTRY_FLAGS_ROUTER] = "router", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(fdb_ntf_flags, NeighborCacheEntryFlags); + +int config_parse_fdb_ntf_flags( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(fdb_entry_free_or_set_invalidp) FdbEntry *fdb_entry = NULL; + Network *network = userdata; + NeighborCacheEntryFlags f; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = fdb_entry_new_static(network, filename, section_line, &fdb_entry); + if (r < 0) + return log_oom(); + + f = fdb_ntf_flags_from_string(rvalue); + if (f < 0) { + log_syntax(unit, LOG_WARNING, filename, line, SYNTHETIC_ERRNO(EINVAL), + "FDB failed to parse AssociatedWith=, ignoring assignment: %s", + rvalue); + return 0; + } + + fdb_entry->fdb_ntf_flags = f; + fdb_entry = NULL; + + return 0; +} diff --git a/src/network/networkd-fdb.h b/src/network/networkd-fdb.h new file mode 100644 index 0000000..48f4e40 --- /dev/null +++ b/src/network/networkd-fdb.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/*** + Copyright © 2014 Intel Corporation. All rights reserved. +***/ + +#include <inttypes.h> +#include <linux/neighbour.h> + +#include "conf-parser.h" +#include "ether-addr-util.h" +#include "in-addr-util.h" +#include "networkd-util.h" + +typedef struct Network Network; +typedef struct Link Link; + +typedef enum NeighborCacheEntryFlags { + NEIGHBOR_CACHE_ENTRY_FLAGS_USE = NTF_USE, + NEIGHBOR_CACHE_ENTRY_FLAGS_SELF = NTF_SELF, + NEIGHBOR_CACHE_ENTRY_FLAGS_MASTER = NTF_MASTER, + NEIGHBOR_CACHE_ENTRY_FLAGS_ROUTER = NTF_ROUTER, + _NEIGHBOR_CACHE_ENTRY_FLAGS_MAX, + _NEIGHBOR_CACHE_ENTRY_FLAGS_INVALID = -1, +} NeighborCacheEntryFlags; + +typedef struct FdbEntry { + Network *network; + NetworkConfigSection *section; + + uint32_t vni; + + int family; + uint16_t vlan_id; + + struct ether_addr mac_addr; + union in_addr_union destination_addr; + NeighborCacheEntryFlags fdb_ntf_flags; +} FdbEntry; + +FdbEntry *fdb_entry_free(FdbEntry *fdb_entry); + +void network_drop_invalid_fdb_entries(Network *network); + +int link_set_bridge_fdb(Link *link); + +CONFIG_PARSER_PROTOTYPE(config_parse_fdb_hwaddr); +CONFIG_PARSER_PROTOTYPE(config_parse_fdb_vlan_id); +CONFIG_PARSER_PROTOTYPE(config_parse_fdb_destination); +CONFIG_PARSER_PROTOTYPE(config_parse_fdb_vxlan_vni); +CONFIG_PARSER_PROTOTYPE(config_parse_fdb_ntf_flags); diff --git a/src/network/networkd-gperf.gperf b/src/network/networkd-gperf.gperf new file mode 100644 index 0000000..aaabb3d --- /dev/null +++ b/src/network/networkd-gperf.gperf @@ -0,0 +1,25 @@ +%{ +#if __GNUC__ >= 7 +_Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"") +#endif +#include <stddef.h> +#include "conf-parser.h" +#include "networkd-conf.h" +#include "networkd-manager.h" +%} +struct ConfigPerfItem; +%null_strings +%language=ANSI-C +%define slot-name section_and_lvalue +%define hash-function-name networkd_gperf_hash +%define lookup-function-name networkd_gperf_lookup +%readonly-tables +%omit-struct-type +%struct-type +%includes +%% +Network.SpeedMeter, config_parse_bool, 0, offsetof(Manager, use_speed_meter) +Network.SpeedMeterIntervalSec, config_parse_sec, 0, offsetof(Manager, speed_meter_interval_usec) +Network.ManageForeignRoutes, config_parse_bool, 0, offsetof(Manager, manage_foreign_routes) +DHCP.DUIDType, config_parse_duid_type, 0, offsetof(Manager, duid) +DHCP.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Manager, duid) diff --git a/src/network/networkd-ipv4ll.c b/src/network/networkd-ipv4ll.c new file mode 100644 index 0000000..295abe8 --- /dev/null +++ b/src/network/networkd-ipv4ll.c @@ -0,0 +1,313 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <netinet/in.h> +#include <linux/if.h> + +#include "network-internal.h" +#include "networkd-address.h" +#include "networkd-ipv4ll.h" +#include "networkd-link.h" +#include "networkd-manager.h" +#include "parse-util.h" + +static int ipv4ll_address_lost(Link *link) { + _cleanup_(address_freep) Address *address = NULL; + struct in_addr addr; + int r; + + assert(link); + + link->ipv4ll_address_configured = false; + + r = sd_ipv4ll_get_address(link->ipv4ll, &addr); + if (r < 0) + return 0; + + log_link_debug(link, "IPv4 link-local release "IPV4_ADDRESS_FMT_STR, IPV4_ADDRESS_FMT_VAL(addr)); + + r = address_new(&address); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate address: %m"); + + address->family = AF_INET; + address->in_addr.in = addr; + address->prefixlen = 16; + address->scope = RT_SCOPE_LINK; + + r = address_remove(address, link, NULL); + if (r < 0) + return r; + + link_check_ready(link); + + return 0; +} + +static int ipv4ll_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + assert(!link->ipv4ll_address_configured); + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EEXIST) { + log_link_message_warning_errno(link, m, r, "could not set ipv4ll address"); + link_enter_failed(link); + return 1; + } else if (r >= 0) + (void) manager_rtnl_process_address(rtnl, m, link->manager); + + link->ipv4ll_address_configured = true; + link_check_ready(link); + + return 1; +} + +static int ipv4ll_address_claimed(sd_ipv4ll *ll, Link *link) { + _cleanup_(address_freep) Address *ll_addr = NULL; + struct in_addr address; + int r; + + assert(ll); + assert(link); + + link->ipv4ll_address_configured = false; + + r = sd_ipv4ll_get_address(ll, &address); + if (r == -ENOENT) + return 0; + else if (r < 0) + return r; + + log_link_debug(link, "IPv4 link-local claim "IPV4_ADDRESS_FMT_STR, + IPV4_ADDRESS_FMT_VAL(address)); + + r = address_new(&ll_addr); + if (r < 0) + return r; + + ll_addr->family = AF_INET; + ll_addr->in_addr.in = address; + ll_addr->prefixlen = 16; + ll_addr->broadcast.s_addr = ll_addr->in_addr.in.s_addr | htobe32(0xfffffffflu >> ll_addr->prefixlen); + ll_addr->scope = RT_SCOPE_LINK; + + r = address_configure(ll_addr, link, ipv4ll_address_handler, false, NULL); + if (r < 0) + return r; + + return 0; +} + +static void ipv4ll_handler(sd_ipv4ll *ll, int event, void *userdata) { + Link *link = userdata; + int r; + + assert(link); + assert(link->network); + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return; + + switch(event) { + case SD_IPV4LL_EVENT_STOP: + r = ipv4ll_address_lost(link); + if (r < 0) { + link_enter_failed(link); + return; + } + break; + case SD_IPV4LL_EVENT_CONFLICT: + r = ipv4ll_address_lost(link); + if (r < 0) { + link_enter_failed(link); + return; + } + + r = sd_ipv4ll_restart(ll); + if (r < 0) + log_link_warning_errno(link, r, "Could not acquire IPv4 link-local address: %m"); + break; + case SD_IPV4LL_EVENT_BIND: + r = ipv4ll_address_claimed(ll, link); + if (r < 0) { + log_link_error(link, "Failed to configure ipv4ll address: %m"); + link_enter_failed(link); + return; + } + break; + default: + log_link_warning(link, "IPv4 link-local unknown event: %d", event); + break; + } +} + +static int ipv4ll_init(Link *link) { + int r; + + assert(link); + + if (link->ipv4ll) + return 0; + + r = sd_ipv4ll_new(&link->ipv4ll); + if (r < 0) + return r; + + r = sd_ipv4ll_attach_event(link->ipv4ll, link->manager->event, 0); + if (r < 0) + return r; + + return 0; +} + +int ipv4ll_configure(Link *link) { + uint64_t seed; + int r; + + assert(link); + + if (!link_ipv4ll_enabled(link, ADDRESS_FAMILY_IPV4 | ADDRESS_FAMILY_FALLBACK_IPV4)) + return 0; + + r = ipv4ll_init(link); + if (r < 0) + return r; + + if (link->sd_device && + net_get_unique_predictable_data(link->sd_device, true, &seed) >= 0) { + r = sd_ipv4ll_set_address_seed(link->ipv4ll, seed); + if (r < 0) + return r; + } + + r = sd_ipv4ll_set_mac(link->ipv4ll, &link->hw_addr.addr.ether); + if (r < 0) + return r; + + r = sd_ipv4ll_set_ifindex(link->ipv4ll, link->ifindex); + if (r < 0) + return r; + + r = sd_ipv4ll_set_callback(link->ipv4ll, ipv4ll_handler, link); + if (r < 0) + return r; + + return 0; +} + +int ipv4ll_update_mac(Link *link) { + bool restart; + int r; + + assert(link); + + if (!link->ipv4ll) + return 0; + + restart = sd_ipv4ll_is_running(link->ipv4ll) > 0; + + r = sd_ipv4ll_stop(link->ipv4ll); + if (r < 0) + return r; + + r = sd_ipv4ll_set_mac(link->ipv4ll, &link->hw_addr.addr.ether); + if (r < 0) + return r; + + if (restart) { + r = sd_ipv4ll_start(link->ipv4ll); + if (r < 0) + return r; + } + + return 0; +} + +int link_serialize_ipv4ll(Link *link, FILE *f) { + struct in_addr address; + int r; + + assert(link); + + if (!link->ipv4ll) + return 0; + + r = sd_ipv4ll_get_address(link->ipv4ll, &address); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + fputs("IPV4LL_ADDRESS=", f); + serialize_in_addrs(f, &address, 1, false, NULL); + fputc('\n', f); + + return 0; +} + +int link_deserialize_ipv4ll(Link *link, const char *ipv4ll_address) { + union in_addr_union address; + int r; + + assert(link); + + if (isempty(ipv4ll_address)) + return 0; + + r = in_addr_from_string(AF_INET, ipv4ll_address, &address); + if (r < 0) + return log_link_debug_errno(link, r, "Failed to parse IPv4LL address: %s", ipv4ll_address); + + r = ipv4ll_init(link); + if (r < 0) + return log_link_debug_errno(link, r, "Failed to initialize IPv4LL client: %m"); + + r = sd_ipv4ll_set_address(link->ipv4ll, &address.in); + if (r < 0) + return log_link_debug_errno(link, r, "Failed to set initial IPv4LL address %s: %m", ipv4ll_address); + + return 0; +} + +int config_parse_ipv4ll( + const char* unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + AddressFamily *link_local = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + /* Note that this is mostly like + * config_parse_address_family(), except that it + * applies only to IPv4 */ + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse %s=%s, ignoring assignment. " + "Note that the setting %s= is deprecated, please use LinkLocalAddressing= instead.", + lvalue, rvalue, lvalue); + return 0; + } + + SET_FLAG(*link_local, ADDRESS_FAMILY_IPV4, r); + + log_syntax(unit, LOG_WARNING, filename, line, 0, + "%s=%s is deprecated, please use LinkLocalAddressing=%s instead.", + lvalue, rvalue, address_family_to_string(*link_local)); + + return 0; +} diff --git a/src/network/networkd-ipv4ll.h b/src/network/networkd-ipv4ll.h new file mode 100644 index 0000000..fae48cd --- /dev/null +++ b/src/network/networkd-ipv4ll.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "conf-parser.h" + +#define IPV4LL_ROUTE_METRIC 2048 + +typedef struct Link Link; + +int ipv4ll_configure(Link *link); +int ipv4ll_update_mac(Link *link); +int link_serialize_ipv4ll(Link *link, FILE *f); +int link_deserialize_ipv4ll(Link *link, const char *ipv4ll_address); + +CONFIG_PARSER_PROTOTYPE(config_parse_ipv4ll); diff --git a/src/network/networkd-ipv6-proxy-ndp.c b/src/network/networkd-ipv6-proxy-ndp.c new file mode 100644 index 0000000..7a370e9 --- /dev/null +++ b/src/network/networkd-ipv6-proxy-ndp.c @@ -0,0 +1,164 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <netinet/in.h> +#include <linux/if.h> + +#include "netlink-util.h" +#include "networkd-ipv6-proxy-ndp.h" +#include "networkd-link.h" +#include "networkd-manager.h" +#include "networkd-network.h" +#include "socket-util.h" +#include "string-util.h" +#include "sysctl-util.h" + +static int set_ipv6_proxy_ndp_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EEXIST) + log_link_message_warning_errno(link, m, r, "Could not add IPv6 proxy ndp address entry, ignoring"); + + return 1; +} + +/* send a request to the kernel to add a IPv6 Proxy entry to the neighbour table */ +static int ipv6_proxy_ndp_address_configure(Link *link, const struct in6_addr *address) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(link); + assert(link->manager); + assert(address); + + /* create new netlink message */ + r = sd_rtnl_message_new_neigh(link->manager->rtnl, &req, RTM_NEWNEIGH, link->ifindex, AF_INET6); + if (r < 0) + return log_link_error_errno(link, r, "Could not create RTM_NEWNEIGH message: %m"); + + r = sd_rtnl_message_neigh_set_flags(req, NTF_PROXY); + if (r < 0) + return log_link_error_errno(link, r, "Could not set neighbor flags: %m"); + + r = sd_netlink_message_append_in6_addr(req, NDA_DST, address); + if (r < 0) + return log_link_error_errno(link, r, "Could not append NDA_DST attribute: %m"); + + r = netlink_call_async(link->manager->rtnl, NULL, req, set_ipv6_proxy_ndp_address_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + return 0; +} + +static bool ipv6_proxy_ndp_is_needed(Link *link) { + assert(link); + + if (link->flags & IFF_LOOPBACK) + return false; + + if (!link->network) + return false; + + if (link->network->ipv6_proxy_ndp >= 0) + return link->network->ipv6_proxy_ndp; + + return !set_isempty(link->network->ipv6_proxy_ndp_addresses); +} + +static int ipv6_proxy_ndp_set(Link *link) { + bool v; + int r; + + assert(link); + + if (!socket_ipv6_is_supported()) + return 0; + + v = ipv6_proxy_ndp_is_needed(link); + + r = sysctl_write_ip_property_boolean(AF_INET6, link->ifname, "proxy_ndp", v); + if (r < 0) + return log_link_warning_errno(link, r, "Cannot configure proxy NDP for the interface, ignoring: %m"); + + return v; +} + +/* configure all ipv6 proxy ndp addresses */ +int link_set_ipv6_proxy_ndp_addresses(Link *link) { + struct in6_addr *address; + int r; + + assert(link); + assert(link->network); + + /* enable or disable proxy_ndp itself depending on whether ipv6_proxy_ndp_addresses are set or not */ + r = ipv6_proxy_ndp_set(link); + if (r <= 0) + return 0; + + SET_FOREACH(address, link->network->ipv6_proxy_ndp_addresses) { + r = ipv6_proxy_ndp_address_configure(link, address); + if (r < 0) + return r; + } + + return 0; +} + +int config_parse_ipv6_proxy_ndp_address( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_free_ struct in6_addr *address = NULL; + Network *network = userdata; + union in_addr_union buffer; + int r; + + assert(filename); + assert(rvalue); + assert(network); + + if (isempty(rvalue)) { + network->ipv6_proxy_ndp_addresses = set_free_free(network->ipv6_proxy_ndp_addresses); + return 0; + } + + r = in_addr_from_string(AF_INET6, rvalue, &buffer); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse IPv6 proxy NDP address, ignoring: %s", rvalue); + return 0; + } + + if (in_addr_is_null(AF_INET6, &buffer)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "IPv6 proxy NDP address cannot be the ANY address, ignoring: %s", rvalue); + return 0; + } + + address = newdup(struct in6_addr, &buffer.in6, 1); + if (!address) + return log_oom(); + + r = set_ensure_put(&network->ipv6_proxy_ndp_addresses, &in6_addr_hash_ops, address); + if (r < 0) + return log_oom(); + if (r > 0) + TAKE_PTR(address); + + return 0; +} diff --git a/src/network/networkd-ipv6-proxy-ndp.h b/src/network/networkd-ipv6-proxy-ndp.h new file mode 100644 index 0000000..27313ef --- /dev/null +++ b/src/network/networkd-ipv6-proxy-ndp.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "conf-parser.h" + +typedef struct Link Link; + +int link_set_ipv6_proxy_ndp_addresses(Link *link); + +CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_proxy_ndp_address); diff --git a/src/network/networkd-link-bus.c b/src/network/networkd-link-bus.c new file mode 100644 index 0000000..4df31df --- /dev/null +++ b/src/network/networkd-link-bus.c @@ -0,0 +1,816 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <net/if.h> +#include <netinet/in.h> +#include <sys/capability.h> + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-get-properties.h" +#include "bus-message-util.h" +#include "bus-polkit.h" +#include "dns-domain.h" +#include "networkd-link-bus.h" +#include "networkd-link.h" +#include "networkd-manager.h" +#include "parse-util.h" +#include "resolve-util.h" +#include "socket-netlink.h" +#include "strv.h" +#include "user-util.h" + +BUS_DEFINE_PROPERTY_GET_ENUM(property_get_operational_state, link_operstate, LinkOperationalState); +BUS_DEFINE_PROPERTY_GET_ENUM(property_get_carrier_state, link_carrier_state, LinkCarrierState); +BUS_DEFINE_PROPERTY_GET_ENUM(property_get_address_state, link_address_state, LinkAddressState); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_administrative_state, link_state, LinkState); + +static int property_get_bit_rates( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Link *link = userdata; + Manager *manager; + double interval_sec; + uint64_t tx, rx; + + assert(bus); + assert(reply); + assert(userdata); + + manager = link->manager; + + if (!manager->use_speed_meter || + manager->speed_meter_usec_old == 0 || + !link->stats_updated) + return sd_bus_message_append(reply, "(tt)", UINT64_MAX, UINT64_MAX); + + assert(manager->speed_meter_usec_new > manager->speed_meter_usec_old); + interval_sec = (manager->speed_meter_usec_new - manager->speed_meter_usec_old) / USEC_PER_SEC; + + if (link->stats_new.tx_bytes > link->stats_old.tx_bytes) + tx = (uint64_t) ((link->stats_new.tx_bytes - link->stats_old.tx_bytes) / interval_sec); + else + tx = (uint64_t) ((UINT64_MAX - (link->stats_old.tx_bytes - link->stats_new.tx_bytes)) / interval_sec); + + if (link->stats_new.rx_bytes > link->stats_old.rx_bytes) + rx = (uint64_t) ((link->stats_new.rx_bytes - link->stats_old.rx_bytes) / interval_sec); + else + rx = (uint64_t) ((UINT64_MAX - (link->stats_old.rx_bytes - link->stats_new.rx_bytes)) / interval_sec); + + return sd_bus_message_append(reply, "(tt)", tx, rx); +} + +static int verify_managed_link(Link *l, sd_bus_error *error) { + assert(l); + + if (l->flags & IFF_LOOPBACK) + return sd_bus_error_setf(error, BUS_ERROR_LINK_BUSY, "Link %s is loopback device.", l->ifname); + + return 0; +} + +int bus_link_method_set_ntp_servers(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_strv_free_ char **ntp = NULL; + Link *l = userdata; + int r; + char **i; + + assert(message); + assert(l); + + r = verify_managed_link(l, error); + if (r < 0) + return r; + + r = sd_bus_message_read_strv(message, &ntp); + if (r < 0) + return r; + + STRV_FOREACH(i, ntp) { + r = dns_name_is_valid_or_address(*i); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid NTP server: %s", *i); + } + + r = bus_verify_polkit_async(message, CAP_NET_ADMIN, + "org.freedesktop.network1.set-ntp-servers", + NULL, true, UID_INVALID, + &l->manager->polkit_registry, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Polkit will call us back */ + + strv_free_and_replace(l->ntp, ntp); + + link_dirty(l); + r = link_save_and_clean(l); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +static int bus_link_method_set_dns_servers_internal(sd_bus_message *message, void *userdata, sd_bus_error *error, bool extended) { + struct in_addr_full **dns; + Link *l = userdata; + size_t n; + int r; + + assert(message); + assert(l); + + r = verify_managed_link(l, error); + if (r < 0) + return r; + + r = bus_message_read_dns_servers(message, error, extended, &dns, &n); + if (r < 0) + return r; + + r = bus_verify_polkit_async(message, CAP_NET_ADMIN, + "org.freedesktop.network1.set-dns-servers", + NULL, true, UID_INVALID, + &l->manager->polkit_registry, error); + if (r < 0) + goto finalize; + if (r == 0) { + r = 1; /* Polkit will call us back */ + goto finalize; + } + + if (l->n_dns != (unsigned) -1) + for (unsigned i = 0; i < l->n_dns; i++) + in_addr_full_free(l->dns[i]); + + free_and_replace(l->dns, dns); + l->n_dns = n; + + link_dirty(l); + r = link_save_and_clean(l); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); + +finalize: + for (size_t i = 0; i < n; i++) + in_addr_full_free(dns[i]); + free(dns); + + return r; +} + +int bus_link_method_set_dns_servers(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return bus_link_method_set_dns_servers_internal(message, userdata, error, false); +} + +int bus_link_method_set_dns_servers_ex(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return bus_link_method_set_dns_servers_internal(message, userdata, error, true); +} + +int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(ordered_set_freep) OrderedSet *search_domains = NULL, *route_domains = NULL; + Link *l = userdata; + int r; + + assert(message); + assert(l); + + r = verify_managed_link(l, error); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(message, 'a', "(sb)"); + if (r < 0) + return r; + + for (;;) { + _cleanup_free_ char *str = NULL; + OrderedSet **domains; + const char *name; + int route_only; + + r = sd_bus_message_read(message, "(sb)", &name, &route_only); + if (r < 0) + return r; + if (r == 0) + break; + + r = dns_name_is_valid(name); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid search domain %s", name); + if (!route_only && dns_name_is_root(name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Root domain is not suitable as search domain"); + + r = dns_name_normalize(name, 0, &str); + if (r < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid search domain %s", name); + + domains = route_only ? &route_domains : &search_domains; + r = ordered_set_ensure_allocated(domains, &string_hash_ops); + if (r < 0) + return r; + + r = ordered_set_put(*domains, str); + if (r < 0) + return r; + + TAKE_PTR(str); + } + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + r = bus_verify_polkit_async(message, CAP_NET_ADMIN, + "org.freedesktop.network1.set-domains", + NULL, true, UID_INVALID, + &l->manager->polkit_registry, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Polkit will call us back */ + + ordered_set_free_free(l->search_domains); + ordered_set_free_free(l->route_domains); + l->search_domains = TAKE_PTR(search_domains); + l->route_domains = TAKE_PTR(route_domains); + + link_dirty(l); + r = link_save_and_clean(l); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_link_method_set_default_route(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Link *l = userdata; + int r, b; + + assert(message); + assert(l); + + r = verify_managed_link(l, error); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + r = bus_verify_polkit_async(message, CAP_NET_ADMIN, + "org.freedesktop.network1.set-default-route", + NULL, true, UID_INVALID, + &l->manager->polkit_registry, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Polkit will call us back */ + + if (l->dns_default_route != b) { + l->dns_default_route = b; + + link_dirty(l); + r = link_save_and_clean(l); + if (r < 0) + return r; + } + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Link *l = userdata; + ResolveSupport mode; + const char *llmnr; + int r; + + assert(message); + assert(l); + + r = verify_managed_link(l, error); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "s", &llmnr); + if (r < 0) + return r; + + if (isempty(llmnr)) + mode = RESOLVE_SUPPORT_YES; + else { + mode = resolve_support_from_string(llmnr); + if (mode < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid LLMNR setting: %s", llmnr); + } + + r = bus_verify_polkit_async(message, CAP_NET_ADMIN, + "org.freedesktop.network1.set-llmnr", + NULL, true, UID_INVALID, + &l->manager->polkit_registry, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Polkit will call us back */ + + if (l->llmnr != mode) { + l->llmnr = mode; + + link_dirty(l); + r = link_save_and_clean(l); + if (r < 0) + return r; + } + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Link *l = userdata; + ResolveSupport mode; + const char *mdns; + int r; + + assert(message); + assert(l); + + r = verify_managed_link(l, error); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "s", &mdns); + if (r < 0) + return r; + + if (isempty(mdns)) + mode = RESOLVE_SUPPORT_NO; + else { + mode = resolve_support_from_string(mdns); + if (mode < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid MulticastDNS setting: %s", mdns); + } + + r = bus_verify_polkit_async(message, CAP_NET_ADMIN, + "org.freedesktop.network1.set-mdns", + NULL, true, UID_INVALID, + &l->manager->polkit_registry, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Polkit will call us back */ + + if (l->mdns != mode) { + l->mdns = mode; + + link_dirty(l); + r = link_save_and_clean(l); + if (r < 0) + return r; + } + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_link_method_set_dns_over_tls(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Link *l = userdata; + const char *dns_over_tls; + DnsOverTlsMode mode; + int r; + + assert(message); + assert(l); + + r = verify_managed_link(l, error); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "s", &dns_over_tls); + if (r < 0) + return r; + + if (isempty(dns_over_tls)) + mode = _DNS_OVER_TLS_MODE_INVALID; + else { + mode = dns_over_tls_mode_from_string(dns_over_tls); + if (mode < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid DNSOverTLS setting: %s", dns_over_tls); + } + + r = bus_verify_polkit_async(message, CAP_NET_ADMIN, + "org.freedesktop.network1.set-dns-over-tls", + NULL, true, UID_INVALID, + &l->manager->polkit_registry, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Polkit will call us back */ + + if (l->dns_over_tls_mode != mode) { + l->dns_over_tls_mode = mode; + + link_dirty(l); + r = link_save_and_clean(l); + if (r < 0) + return r; + } + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_link_method_set_dnssec(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Link *l = userdata; + const char *dnssec; + DnssecMode mode; + int r; + + assert(message); + assert(l); + + r = verify_managed_link(l, error); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "s", &dnssec); + if (r < 0) + return r; + + if (isempty(dnssec)) + mode = _DNSSEC_MODE_INVALID; + else { + mode = dnssec_mode_from_string(dnssec); + if (mode < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid DNSSEC setting: %s", dnssec); + } + + r = bus_verify_polkit_async(message, CAP_NET_ADMIN, + "org.freedesktop.network1.set-dnssec", + NULL, true, UID_INVALID, + &l->manager->polkit_registry, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Polkit will call us back */ + + if (l->dnssec_mode != mode) { + l->dnssec_mode = mode; + + link_dirty(l); + r = link_save_and_clean(l); + if (r < 0) + return r; + } + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_set_free_free_ Set *ns = NULL; + _cleanup_strv_free_ char **ntas = NULL; + Link *l = userdata; + int r; + char **i; + + assert(message); + assert(l); + + r = verify_managed_link(l, error); + if (r < 0) + return r; + + r = sd_bus_message_read_strv(message, &ntas); + if (r < 0) + return r; + + STRV_FOREACH(i, ntas) { + r = dns_name_is_valid(*i); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid negative trust anchor domain: %s", *i); + } + + ns = set_new(&dns_name_hash_ops); + if (!ns) + return -ENOMEM; + + STRV_FOREACH(i, ntas) { + r = set_put_strdup(&ns, *i); + if (r < 0) + return r; + } + + r = bus_verify_polkit_async(message, CAP_NET_ADMIN, + "org.freedesktop.network1.set-dnssec-negative-trust-anchors", + NULL, true, UID_INVALID, + &l->manager->polkit_registry, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Polkit will call us back */ + + set_free_free(l->dnssec_negative_trust_anchors); + l->dnssec_negative_trust_anchors = TAKE_PTR(ns); + + link_dirty(l); + r = link_save_and_clean(l); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_link_method_revert_ntp(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Link *l = userdata; + int r; + + assert(message); + assert(l); + + r = verify_managed_link(l, error); + if (r < 0) + return r; + + r = bus_verify_polkit_async(message, CAP_NET_ADMIN, + "org.freedesktop.network1.revert-ntp", + NULL, true, UID_INVALID, + &l->manager->polkit_registry, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Polkit will call us back */ + + link_ntp_settings_clear(l); + + link_dirty(l); + r = link_save_and_clean(l); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_link_method_revert_dns(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Link *l = userdata; + int r; + + assert(message); + assert(l); + + r = verify_managed_link(l, error); + if (r < 0) + return r; + + r = bus_verify_polkit_async(message, CAP_NET_ADMIN, + "org.freedesktop.network1.revert-dns", + NULL, true, UID_INVALID, + &l->manager->polkit_registry, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Polkit will call us back */ + + link_dns_settings_clear(l); + + link_dirty(l); + r = link_save_and_clean(l); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_link_method_force_renew(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Link *l = userdata; + int r; + + assert(l); + + if (!l->network) + return sd_bus_error_setf(error, BUS_ERROR_UNMANAGED_INTERFACE, + "Interface %s is not managed by systemd-networkd", + l->ifname); + + r = bus_verify_polkit_async(message, CAP_NET_ADMIN, + "org.freedesktop.network1.forcerenew", + NULL, true, UID_INVALID, + &l->manager->polkit_registry, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Polkit will call us back */ + + if (l->dhcp_server) { + r = sd_dhcp_server_forcerenew(l->dhcp_server); + if (r < 0) + return r; + } + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_link_method_renew(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Link *l = userdata; + int r; + + assert(l); + + if (!l->network) + return sd_bus_error_setf(error, BUS_ERROR_UNMANAGED_INTERFACE, + "Interface %s is not managed by systemd-networkd", + l->ifname); + + r = bus_verify_polkit_async(message, CAP_NET_ADMIN, + "org.freedesktop.network1.renew", + NULL, true, UID_INVALID, + &l->manager->polkit_registry, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Polkit will call us back */ + + if (l->dhcp_client) { + r = sd_dhcp_client_send_renew(l->dhcp_client); + if (r < 0) + return r; + } + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_link_method_reconfigure(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Link *l = userdata; + int r; + + assert(message); + assert(l); + + r = bus_verify_polkit_async(message, CAP_NET_ADMIN, + "org.freedesktop.network1.reconfigure", + NULL, true, UID_INVALID, + &l->manager->polkit_registry, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Polkit will call us back */ + + r = link_reconfigure(l, true); + if (r < 0) + return r; + if (r > 0) { + link_set_state(l, LINK_STATE_INITIALIZED); + r = link_save_and_clean(l); + if (r < 0) + return r; + } + + return sd_bus_reply_method_return(message, NULL); +} + +const sd_bus_vtable link_vtable[] = { + SD_BUS_VTABLE_START(0), + + SD_BUS_PROPERTY("OperationalState", "s", property_get_operational_state, offsetof(Link, operstate), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("CarrierState", "s", property_get_carrier_state, offsetof(Link, carrier_state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("AddressState", "s", property_get_address_state, offsetof(Link, address_state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("AdministrativeState", "s", property_get_administrative_state, offsetof(Link, state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("BitRates", "(tt)", property_get_bit_rates, 0, 0), + + SD_BUS_METHOD("SetNTP", "as", NULL, bus_link_method_set_ntp_servers, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetDNS", "a(iay)", NULL, bus_link_method_set_dns_servers, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetDNSEx", "a(iayqs)", NULL, bus_link_method_set_dns_servers_ex, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetDomains", "a(sb)", NULL, bus_link_method_set_domains, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetDefaultRoute", "b", NULL, bus_link_method_set_default_route, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetLLMNR", "s", NULL, bus_link_method_set_llmnr, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetMulticastDNS", "s", NULL, bus_link_method_set_mdns, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetDNSOverTLS", "s", NULL, bus_link_method_set_dns_over_tls, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetDNSSEC", "s", NULL, bus_link_method_set_dnssec, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetDNSSECNegativeTrustAnchors", "as", NULL, bus_link_method_set_dnssec_negative_trust_anchors, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("RevertNTP", NULL, NULL, bus_link_method_revert_ntp, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("RevertDNS", NULL, NULL, bus_link_method_revert_dns, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Renew", NULL, NULL, bus_link_method_renew, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ForceRenew", NULL, NULL, bus_link_method_force_renew, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Reconfigure", NULL, NULL, bus_link_method_reconfigure, SD_BUS_VTABLE_UNPRIVILEGED), + + SD_BUS_VTABLE_END +}; + +char *link_bus_path(Link *link) { + _cleanup_free_ char *ifindex = NULL; + char *p; + int r; + + assert(link); + assert(link->ifindex > 0); + + if (asprintf(&ifindex, "%d", link->ifindex) < 0) + return NULL; + + r = sd_bus_path_encode("/org/freedesktop/network1/link", ifindex, &p); + if (r < 0) + return NULL; + + return p; +} + +int link_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { + _cleanup_strv_free_ char **l = NULL; + Manager *m = userdata; + unsigned c = 0; + Link *link; + + assert(bus); + assert(path); + assert(m); + assert(nodes); + + l = new0(char*, hashmap_size(m->links) + 1); + if (!l) + return -ENOMEM; + + HASHMAP_FOREACH(link, m->links) { + char *p; + + p = link_bus_path(link); + if (!p) + return -ENOMEM; + + l[c++] = p; + } + + l[c] = NULL; + *nodes = TAKE_PTR(l); + + return 1; +} + +int link_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + _cleanup_free_ char *identifier = NULL; + Manager *m = userdata; + Link *link; + int ifindex, r; + + assert(bus); + assert(path); + assert(interface); + assert(m); + assert(found); + + r = sd_bus_path_decode(path, "/org/freedesktop/network1/link", &identifier); + if (r <= 0) + return 0; + + ifindex = parse_ifindex(identifier); + if (ifindex < 0) + return 0; + + r = link_get(m, ifindex, &link); + if (r < 0) + return 0; + + if (streq(interface, "org.freedesktop.network1.DHCPServer") && !link->dhcp_server) + return 0; + + *found = link; + + return 1; +} + +int link_send_changed_strv(Link *link, char **properties) { + _cleanup_free_ char *p = NULL; + + assert(link); + assert(link->manager); + assert(properties); + + if (!link->manager->bus) + return 0; + + p = link_bus_path(link); + if (!p) + return -ENOMEM; + + return sd_bus_emit_properties_changed_strv( + link->manager->bus, + p, + "org.freedesktop.network1.Link", + properties); +} + +int link_send_changed(Link *link, const char *property, ...) { + char **properties; + + properties = strv_from_stdarg_alloca(property); + + return link_send_changed_strv(link, properties); +} diff --git a/src/network/networkd-link-bus.h b/src/network/networkd-link-bus.h new file mode 100644 index 0000000..45594df --- /dev/null +++ b/src/network/networkd-link-bus.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-bus.h" + +#include "macro.h" + +typedef struct Link Link; + +extern const sd_bus_vtable link_vtable[]; + +char *link_bus_path(Link *link); +int link_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error); +int link_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error); +int link_send_changed_strv(Link *link, char **properties); +int link_send_changed(Link *link, const char *property, ...) _sentinel_; + +int property_get_operational_state(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); +int property_get_carrier_state(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); +int property_get_address_state(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); + +int bus_link_method_set_ntp_servers(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_set_dns_servers(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_set_dns_servers_ex(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_set_default_route(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_set_dns_over_tls(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_set_dnssec(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_revert_ntp(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_revert_dns(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_renew(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_force_renew(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_reconfigure(sd_bus_message *message, void *userdata, sd_bus_error *error); diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c new file mode 100644 index 0000000..8120343 --- /dev/null +++ b/src/network/networkd-link.c @@ -0,0 +1,3263 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <netinet/in.h> +#include <linux/if.h> +#include <linux/if_arp.h> +#include <linux/if_link.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "bond.h" +#include "bridge.h" +#include "bus-util.h" +#include "dhcp-identifier.h" +#include "dhcp-lease-internal.h" +#include "env-file.h" +#include "ethtool-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "ipvlan.h" +#include "missing_network.h" +#include "netlink-util.h" +#include "network-internal.h" +#include "networkd-address-label.h" +#include "networkd-address.h" +#include "networkd-can.h" +#include "networkd-dhcp-server.h" +#include "networkd-dhcp4.h" +#include "networkd-dhcp6.h" +#include "networkd-fdb.h" +#include "networkd-ipv4ll.h" +#include "networkd-ipv6-proxy-ndp.h" +#include "networkd-link-bus.h" +#include "networkd-link.h" +#include "networkd-lldp-tx.h" +#include "networkd-manager.h" +#include "networkd-mdb.h" +#include "networkd-ndisc.h" +#include "networkd-neighbor.h" +#include "networkd-nexthop.h" +#include "networkd-sriov.h" +#include "networkd-sysctl.h" +#include "networkd-radv.h" +#include "networkd-routing-policy-rule.h" +#include "networkd-wifi.h" +#include "set.h" +#include "socket-util.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "string-table.h" +#include "strv.h" +#include "sysctl-util.h" +#include "tc.h" +#include "tmpfile-util.h" +#include "udev-util.h" +#include "util.h" +#include "vrf.h" + +bool link_ipv4ll_enabled(Link *link, AddressFamily mask) { + assert(link); + assert((mask & ~(ADDRESS_FAMILY_IPV4 | ADDRESS_FAMILY_FALLBACK_IPV4)) == 0); + + if (link->flags & IFF_LOOPBACK) + return false; + + if (!link->network) + return false; + + if (link->iftype == ARPHRD_CAN) + return false; + + if (STRPTR_IN_SET(link->kind, + "vrf", "wireguard", "ipip", "gre", "ip6gre","ip6tnl", "sit", "vti", + "vti6", "nlmon", "xfrm", "bareudp")) + return false; + + /* L3 or L3S mode do not support ARP. */ + if (IN_SET(link_get_ipvlan_mode(link), NETDEV_IPVLAN_MODE_L3, NETDEV_IPVLAN_MODE_L3S)) + return false; + + if (link->network->bond) + return false; + + return link->network->link_local & mask; +} + +bool link_ipv6ll_enabled(Link *link) { + assert(link); + + if (!socket_ipv6_is_supported()) + return false; + + if (link->flags & IFF_LOOPBACK) + return false; + + if (!link->network) + return false; + + if (link->iftype == ARPHRD_CAN) + return false; + + if (STRPTR_IN_SET(link->kind, "vrf", "wireguard", "ipip", "gre", "sit", "vti", "nlmon")) + return false; + + if (link->network->bond) + return false; + + return link->network->link_local & ADDRESS_FAMILY_IPV6; +} + +bool link_ipv6_enabled(Link *link) { + assert(link); + + if (!socket_ipv6_is_supported()) + return false; + + if (link->network->bond) + return false; + + if (link->iftype == ARPHRD_CAN) + return false; + + /* DHCPv6 client will not be started if no IPv6 link-local address is configured. */ + if (link_ipv6ll_enabled(link)) + return true; + + if (network_has_static_ipv6_configurations(link->network)) + return true; + + return false; +} + +static bool link_is_enslaved(Link *link) { + if (link->flags & IFF_SLAVE) + /* Even if the link is not managed by networkd, honor IFF_SLAVE flag. */ + return true; + + if (!link->network) + return false; + + if (link->master_ifindex > 0 && link->network->bridge) + return true; + + /* TODO: add conditions for other netdevs. */ + + return false; +} + +static void link_update_master_operstate(Link *link, NetDev *netdev) { + Link *master; + + if (!netdev) + return; + + if (netdev->ifindex <= 0) + return; + + if (link_get(link->manager, netdev->ifindex, &master) < 0) + return; + + link_update_operstate(master, true); +} + +void link_update_operstate(Link *link, bool also_update_master) { + LinkOperationalState operstate; + LinkCarrierState carrier_state; + LinkAddressState address_state; + _cleanup_strv_free_ char **p = NULL; + uint8_t scope = RT_SCOPE_NOWHERE; + bool changed = false; + Address *address; + + assert(link); + + if (link->kernel_operstate == IF_OPER_DORMANT) + carrier_state = LINK_CARRIER_STATE_DORMANT; + else if (link_has_carrier(link)) { + if (link_is_enslaved(link)) + carrier_state = LINK_CARRIER_STATE_ENSLAVED; + else + carrier_state = LINK_CARRIER_STATE_CARRIER; + } else if (link->flags & IFF_UP) + carrier_state = LINK_CARRIER_STATE_NO_CARRIER; + else + carrier_state = LINK_CARRIER_STATE_OFF; + + if (carrier_state >= LINK_CARRIER_STATE_CARRIER) { + Link *slave; + + SET_FOREACH(slave, link->slaves) { + link_update_operstate(slave, false); + + if (slave->carrier_state < LINK_CARRIER_STATE_CARRIER) + carrier_state = LINK_CARRIER_STATE_DEGRADED_CARRIER; + } + } + + SET_FOREACH(address, link->addresses) { + if (!address_is_ready(address)) + continue; + + if (address->scope < scope) + scope = address->scope; + } + + /* for operstate we also take foreign addresses into account */ + SET_FOREACH(address, link->addresses_foreign) { + if (!address_is_ready(address)) + continue; + + if (address->scope < scope) + scope = address->scope; + } + + if (scope < RT_SCOPE_SITE) + /* universally accessible addresses found */ + address_state = LINK_ADDRESS_STATE_ROUTABLE; + else if (scope < RT_SCOPE_HOST) + /* only link or site local addresses found */ + address_state = LINK_ADDRESS_STATE_DEGRADED; + else + /* no useful addresses found */ + address_state = LINK_ADDRESS_STATE_OFF; + + /* Mapping of address and carrier state vs operational state + * carrier state + * | off | no-carrier | dormant | degraded-carrier | carrier | enslaved + * ------------------------------------------------------------------------------ + * off | off | no-carrier | dormant | degraded-carrier | carrier | enslaved + * address_state degraded | off | no-carrier | dormant | degraded-carrier | degraded | enslaved + * routable | off | no-carrier | dormant | degraded-carrier | routable | routable + */ + + if (carrier_state < LINK_CARRIER_STATE_CARRIER || address_state == LINK_ADDRESS_STATE_OFF) + operstate = (LinkOperationalState) carrier_state; + else if (address_state == LINK_ADDRESS_STATE_ROUTABLE) + operstate = LINK_OPERSTATE_ROUTABLE; + else if (carrier_state == LINK_CARRIER_STATE_CARRIER) + operstate = LINK_OPERSTATE_DEGRADED; + else + operstate = LINK_OPERSTATE_ENSLAVED; + + if (link->carrier_state != carrier_state) { + link->carrier_state = carrier_state; + changed = true; + if (strv_extend(&p, "CarrierState") < 0) + log_oom(); + } + + if (link->address_state != address_state) { + link->address_state = address_state; + changed = true; + if (strv_extend(&p, "AddressState") < 0) + log_oom(); + } + + if (link->operstate != operstate) { + link->operstate = operstate; + changed = true; + if (strv_extend(&p, "OperationalState") < 0) + log_oom(); + } + + if (p) + link_send_changed_strv(link, p); + if (changed) + link_dirty(link); + + if (also_update_master && link->network) { + link_update_master_operstate(link, link->network->bond); + link_update_master_operstate(link, link->network->bridge); + } +} + +#define FLAG_STRING(string, flag, old, new) \ + (((old ^ new) & flag) \ + ? ((old & flag) ? (" -" string) : (" +" string)) \ + : "") + +static int link_update_flags(Link *link, sd_netlink_message *m, bool force_update_operstate) { + unsigned flags, unknown_flags_added, unknown_flags_removed, unknown_flags; + uint8_t operstate; + int r; + + assert(link); + + r = sd_rtnl_message_link_get_flags(m, &flags); + if (r < 0) + return log_link_warning_errno(link, r, "Could not get link flags: %m"); + + r = sd_netlink_message_read_u8(m, IFLA_OPERSTATE, &operstate); + if (r < 0) + /* if we got a message without operstate, take it to mean + the state was unchanged */ + operstate = link->kernel_operstate; + + if (!force_update_operstate && (link->flags == flags) && (link->kernel_operstate == operstate)) + return 0; + + if (link->flags != flags) { + log_link_debug(link, "Flags change:%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + FLAG_STRING("LOOPBACK", IFF_LOOPBACK, link->flags, flags), + FLAG_STRING("MASTER", IFF_MASTER, link->flags, flags), + FLAG_STRING("SLAVE", IFF_SLAVE, link->flags, flags), + FLAG_STRING("UP", IFF_UP, link->flags, flags), + FLAG_STRING("DORMANT", IFF_DORMANT, link->flags, flags), + FLAG_STRING("LOWER_UP", IFF_LOWER_UP, link->flags, flags), + FLAG_STRING("RUNNING", IFF_RUNNING, link->flags, flags), + FLAG_STRING("MULTICAST", IFF_MULTICAST, link->flags, flags), + FLAG_STRING("BROADCAST", IFF_BROADCAST, link->flags, flags), + FLAG_STRING("POINTOPOINT", IFF_POINTOPOINT, link->flags, flags), + FLAG_STRING("PROMISC", IFF_PROMISC, link->flags, flags), + FLAG_STRING("ALLMULTI", IFF_ALLMULTI, link->flags, flags), + FLAG_STRING("PORTSEL", IFF_PORTSEL, link->flags, flags), + FLAG_STRING("AUTOMEDIA", IFF_AUTOMEDIA, link->flags, flags), + FLAG_STRING("DYNAMIC", IFF_DYNAMIC, link->flags, flags), + FLAG_STRING("NOARP", IFF_NOARP, link->flags, flags), + FLAG_STRING("NOTRAILERS", IFF_NOTRAILERS, link->flags, flags), + FLAG_STRING("DEBUG", IFF_DEBUG, link->flags, flags), + FLAG_STRING("ECHO", IFF_ECHO, link->flags, flags)); + + unknown_flags = ~(IFF_LOOPBACK | IFF_MASTER | IFF_SLAVE | IFF_UP | + IFF_DORMANT | IFF_LOWER_UP | IFF_RUNNING | + IFF_MULTICAST | IFF_BROADCAST | IFF_POINTOPOINT | + IFF_PROMISC | IFF_ALLMULTI | IFF_PORTSEL | + IFF_AUTOMEDIA | IFF_DYNAMIC | IFF_NOARP | + IFF_NOTRAILERS | IFF_DEBUG | IFF_ECHO); + unknown_flags_added = ((link->flags ^ flags) & flags & unknown_flags); + unknown_flags_removed = ((link->flags ^ flags) & link->flags & unknown_flags); + + /* link flags are currently at most 18 bits, let's align to + * printing 20 */ + if (unknown_flags_added) + log_link_debug(link, + "Unknown link flags gained: %#.5x (ignoring)", + unknown_flags_added); + + if (unknown_flags_removed) + log_link_debug(link, + "Unknown link flags lost: %#.5x (ignoring)", + unknown_flags_removed); + } + + link->flags = flags; + link->kernel_operstate = operstate; + + link_update_operstate(link, true); + + return 0; +} + +static int link_new(Manager *manager, sd_netlink_message *message, Link **ret) { + _cleanup_(link_unrefp) Link *link = NULL; + const char *ifname, *kind = NULL; + unsigned short iftype; + int r, ifindex; + uint16_t type; + + assert(manager); + assert(message); + assert(ret); + + /* check for link kind */ + r = sd_netlink_message_enter_container(message, IFLA_LINKINFO); + if (r == 0) { + (void) sd_netlink_message_read_string(message, IFLA_INFO_KIND, &kind); + r = sd_netlink_message_exit_container(message); + if (r < 0) + return r; + } + + r = sd_netlink_message_get_type(message, &type); + if (r < 0) + return r; + else if (type != RTM_NEWLINK) + return -EINVAL; + + r = sd_rtnl_message_link_get_ifindex(message, &ifindex); + if (r < 0) + return r; + else if (ifindex <= 0) + return -EINVAL; + + r = sd_rtnl_message_link_get_type(message, &iftype); + if (r < 0) + return r; + + r = sd_netlink_message_read_string(message, IFLA_IFNAME, &ifname); + if (r < 0) + return r; + + link = new(Link, 1); + if (!link) + return -ENOMEM; + + *link = (Link) { + .n_ref = 1, + .manager = manager, + .state = LINK_STATE_PENDING, + .ifindex = ifindex, + .iftype = iftype, + + .n_dns = (unsigned) -1, + .dns_default_route = -1, + .llmnr = _RESOLVE_SUPPORT_INVALID, + .mdns = _RESOLVE_SUPPORT_INVALID, + .dnssec_mode = _DNSSEC_MODE_INVALID, + .dns_over_tls_mode = _DNS_OVER_TLS_MODE_INVALID, + }; + + link->ifname = strdup(ifname); + if (!link->ifname) + return -ENOMEM; + + if (kind) { + link->kind = strdup(kind); + if (!link->kind) + return -ENOMEM; + } + + r = sd_netlink_message_read_u32(message, IFLA_MASTER, (uint32_t *)&link->master_ifindex); + if (r < 0) + log_link_debug_errno(link, r, "New device has no master, continuing without"); + + r = netlink_message_read_hw_addr(message, IFLA_ADDRESS, &link->hw_addr); + if (r < 0) + log_link_debug_errno(link, r, "Hardware address not found for new device, continuing without"); + + r = netlink_message_read_hw_addr(message, IFLA_BROADCAST, &link->bcast_addr); + if (r < 0) + log_link_debug_errno(link, r, "Broadcast address not found for new device, continuing without"); + + r = ethtool_get_permanent_macaddr(&manager->ethtool_fd, link->ifname, &link->permanent_mac); + if (r < 0) + log_link_debug_errno(link, r, "Permanent MAC address not found for new device, continuing without: %m"); + + r = ethtool_get_driver(&manager->ethtool_fd, link->ifname, &link->driver); + if (r < 0) + log_link_debug_errno(link, r, "Failed to get driver, continuing without: %m"); + + r = sd_netlink_message_read_strv(message, IFLA_PROP_LIST, IFLA_ALT_IFNAME, &link->alternative_names); + if (r < 0 && r != -ENODATA) + return r; + + if (asprintf(&link->state_file, "/run/systemd/netif/links/%d", link->ifindex) < 0) + return -ENOMEM; + + if (asprintf(&link->lease_file, "/run/systemd/netif/leases/%d", link->ifindex) < 0) + return -ENOMEM; + + if (asprintf(&link->lldp_file, "/run/systemd/netif/lldp/%d", link->ifindex) < 0) + return -ENOMEM; + + r = hashmap_ensure_allocated(&manager->links, NULL); + if (r < 0) + return r; + + r = hashmap_put(manager->links, INT_TO_PTR(link->ifindex), link); + if (r < 0) + return r; + + r = link_update_flags(link, message, false); + if (r < 0) + return r; + + *ret = TAKE_PTR(link); + + return 0; +} + +void link_ntp_settings_clear(Link *link) { + link->ntp = strv_free(link->ntp); +} + +void link_dns_settings_clear(Link *link) { + if (link->n_dns != (unsigned) -1) + for (unsigned i = 0; i < link->n_dns; i++) + in_addr_full_free(link->dns[i]); + link->dns = mfree(link->dns); + link->n_dns = (unsigned) -1; + + link->search_domains = ordered_set_free_free(link->search_domains); + link->route_domains = ordered_set_free_free(link->route_domains); + + link->dns_default_route = -1; + link->llmnr = _RESOLVE_SUPPORT_INVALID; + link->mdns = _RESOLVE_SUPPORT_INVALID; + link->dnssec_mode = _DNSSEC_MODE_INVALID; + link->dns_over_tls_mode = _DNS_OVER_TLS_MODE_INVALID; + + link->dnssec_negative_trust_anchors = set_free_free(link->dnssec_negative_trust_anchors); +} + +static void link_free_engines(Link *link) { + if (!link) + return; + + link->dhcp_server = sd_dhcp_server_unref(link->dhcp_server); + link->dhcp_client = sd_dhcp_client_unref(link->dhcp_client); + link->dhcp_lease = sd_dhcp_lease_unref(link->dhcp_lease); + link->dhcp_acd = sd_ipv4acd_unref(link->dhcp_acd); + + link->lldp = sd_lldp_unref(link->lldp); + link_lldp_emit_stop(link); + + ndisc_flush(link); + + link->ipv4ll = sd_ipv4ll_unref(link->ipv4ll); + link->dhcp6_client = sd_dhcp6_client_unref(link->dhcp6_client); + link->dhcp6_lease = sd_dhcp6_lease_unref(link->dhcp6_lease); + link->ndisc = sd_ndisc_unref(link->ndisc); + link->radv = sd_radv_unref(link->radv); + + ipv4_dad_unref(link); +} + +static Link *link_free(Link *link) { + assert(link); + + link_ntp_settings_clear(link); + link_dns_settings_clear(link); + + link->routes = set_free(link->routes); + link->routes_foreign = set_free(link->routes_foreign); + link->dhcp_routes = set_free(link->dhcp_routes); + link->dhcp_routes_old = set_free(link->dhcp_routes_old); + link->dhcp6_routes = set_free(link->dhcp6_routes); + link->dhcp6_routes_old = set_free(link->dhcp6_routes_old); + link->dhcp6_pd_routes = set_free(link->dhcp6_pd_routes); + link->dhcp6_pd_routes_old = set_free(link->dhcp6_pd_routes_old); + link->ndisc_routes = set_free(link->ndisc_routes); + + link->nexthops = set_free(link->nexthops); + link->nexthops_foreign = set_free(link->nexthops_foreign); + + link->neighbors = set_free(link->neighbors); + link->neighbors_foreign = set_free(link->neighbors_foreign); + + link->addresses = set_free(link->addresses); + link->addresses_foreign = set_free(link->addresses_foreign); + link->pool_addresses = set_free(link->pool_addresses); + link->static_addresses = set_free(link->static_addresses); + link->dhcp6_addresses = set_free(link->dhcp6_addresses); + link->dhcp6_addresses_old = set_free(link->dhcp6_addresses_old); + link->dhcp6_pd_addresses = set_free(link->dhcp6_pd_addresses); + link->dhcp6_pd_addresses_old = set_free(link->dhcp6_pd_addresses_old); + link->ndisc_addresses = set_free(link->ndisc_addresses); + + link_free_engines(link); + free(link->lease_file); + free(link->lldp_file); + + free(link->ifname); + strv_free(link->alternative_names); + free(link->kind); + free(link->ssid); + free(link->driver); + + (void) unlink(link->state_file); + free(link->state_file); + + sd_device_unref(link->sd_device); + + hashmap_free(link->bound_to_links); + hashmap_free(link->bound_by_links); + + set_free_with_destructor(link->slaves, link_unref); + + network_unref(link->network); + + return mfree(link); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(Link, link, link_free); + +int link_get(Manager *m, int ifindex, Link **ret) { + Link *link; + + assert(m); + assert(ifindex > 0); + assert(ret); + + link = hashmap_get(m->links, INT_TO_PTR(ifindex)); + if (!link) + return -ENODEV; + + *ret = link; + + return 0; +} + +void link_set_state(Link *link, LinkState state) { + assert(link); + + if (link->state == state) + return; + + log_link_debug(link, "State changed: %s -> %s", + link_state_to_string(link->state), + link_state_to_string(state)); + + link->state = state; + + link_send_changed(link, "AdministrativeState", NULL); +} + +static void link_enter_unmanaged(Link *link) { + assert(link); + + link_set_state(link, LINK_STATE_UNMANAGED); + + link_dirty(link); +} + +int link_stop_engines(Link *link, bool may_keep_dhcp) { + int r = 0, k; + + assert(link); + assert(link->manager); + assert(link->manager->event); + + bool keep_dhcp = may_keep_dhcp && + link->network && + (link->manager->restarting || + FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP_ON_STOP)); + + if (!keep_dhcp) { + k = sd_dhcp_client_stop(link->dhcp_client); + if (k < 0) + r = log_link_warning_errno(link, k, "Could not stop DHCPv4 client: %m"); + } + + k = sd_ipv4acd_stop(link->dhcp_acd); + if (k < 0) + r = log_link_warning_errno(link, k, "Could not stop IPv4 ACD client for DHCPv4: %m"); + + k = sd_dhcp_server_stop(link->dhcp_server); + if (k < 0) + r = log_link_warning_errno(link, k, "Could not stop DHCPv4 server: %m"); + + k = sd_lldp_stop(link->lldp); + if (k < 0) + r = log_link_warning_errno(link, k, "Could not stop LLDP: %m"); + + k = sd_ipv4ll_stop(link->ipv4ll); + if (k < 0) + r = log_link_warning_errno(link, k, "Could not stop IPv4 link-local: %m"); + + k = ipv4_dad_stop(link); + if (k < 0) + r = log_link_warning_errno(link, k, "Could not stop IPv4 ACD client: %m"); + + k = sd_dhcp6_client_stop(link->dhcp6_client); + if (k < 0) + r = log_link_warning_errno(link, k, "Could not stop DHCPv6 client: %m"); + + k = dhcp6_pd_remove(link); + if (k < 0) + r = log_link_warning_errno(link, k, "Could not remove DHCPv6 PD addresses and routes: %m"); + + k = sd_ndisc_stop(link->ndisc); + if (k < 0) + r = log_link_warning_errno(link, k, "Could not stop IPv6 Router Discovery: %m"); + + k = sd_radv_stop(link->radv); + if (k < 0) + r = log_link_warning_errno(link, k, "Could not stop IPv6 Router Advertisement: %m"); + + link_lldp_emit_stop(link); + return r; +} + +void link_enter_failed(Link *link) { + assert(link); + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return; + + log_link_warning(link, "Failed"); + + link_set_state(link, LINK_STATE_FAILED); + + (void) link_stop_engines(link, false); + + link_dirty(link); +} + +static int link_join_netdevs_after_configured(Link *link) { + NetDev *netdev; + int r; + + HASHMAP_FOREACH(netdev, link->network->stacked_netdevs) { + if (netdev->ifindex > 0) + /* Assume already enslaved. */ + continue; + + if (netdev_get_create_type(netdev) != NETDEV_CREATE_AFTER_CONFIGURED) + continue; + + log_struct(LOG_DEBUG, + LOG_LINK_INTERFACE(link), + LOG_NETDEV_INTERFACE(netdev), + LOG_LINK_MESSAGE(link, "Enslaving by '%s'", netdev->ifname)); + + r = netdev_join(netdev, link, NULL); + if (r < 0) + return log_struct_errno(LOG_WARNING, r, + LOG_LINK_INTERFACE(link), + LOG_NETDEV_INTERFACE(netdev), + LOG_LINK_MESSAGE(link, "Could not join netdev '%s': %m", netdev->ifname)); + } + + return 0; +} + +static void link_enter_configured(Link *link) { + assert(link); + assert(link->network); + + if (link->state != LINK_STATE_CONFIGURING) + return; + + link_set_state(link, LINK_STATE_CONFIGURED); + + (void) link_join_netdevs_after_configured(link); + + link_dirty(link); +} + +void link_check_ready(Link *link) { + Address *a; + + assert(link); + + if (link->state == LINK_STATE_CONFIGURED) + return; + + if (link->state != LINK_STATE_CONFIGURING) { + log_link_debug(link, "%s(): link is in %s state.", __func__, link_state_to_string(link->state)); + return; + } + + if (!link->network) + return; + + if (!link->addresses_configured) { + log_link_debug(link, "%s(): static addresses are not configured.", __func__); + return; + } + + if (!link->neighbors_configured) { + log_link_debug(link, "%s(): static neighbors are not configured.", __func__); + return; + } + + SET_FOREACH(a, link->addresses) + if (!address_is_ready(a)) { + _cleanup_free_ char *str = NULL; + + (void) in_addr_to_string(a->family, &a->in_addr, &str); + log_link_debug(link, "%s(): an address %s/%d is not ready.", __func__, strnull(str), a->prefixlen); + return; + } + + if (!link->static_routes_configured) { + log_link_debug(link, "%s(): static routes are not configured.", __func__); + return; + } + + if (!link->static_nexthops_configured) { + log_link_debug(link, "%s(): static nexthops are not configured.", __func__); + return; + } + + if (!link->routing_policy_rules_configured) { + log_link_debug(link, "%s(): static routing policy rules are not configured.", __func__); + return; + } + + if (!link->tc_configured) { + log_link_debug(link, "%s(): traffic controls are not configured.", __func__); + return; + } + + if (!link->sr_iov_configured) { + log_link_debug(link, "%s(): SR-IOV is not configured.", __func__); + return; + } + + if (!link->bridge_mdb_configured) { + log_link_debug(link, "%s(): Bridge MDB is not configured.", __func__); + return; + } + + if (link_has_carrier(link) || !link->network->configure_without_carrier) { + bool has_ndisc_address = false; + NDiscAddress *n; + + if (link_ipv4ll_enabled(link, ADDRESS_FAMILY_IPV4) && !link->ipv4ll_address_configured) { + log_link_debug(link, "%s(): IPv4LL is not configured.", __func__); + return; + } + + if (link_ipv6ll_enabled(link) && + in_addr_is_null(AF_INET6, (const union in_addr_union*) &link->ipv6ll_address)) { + log_link_debug(link, "%s(): IPv6LL is not configured.", __func__); + return; + } + + SET_FOREACH(n, link->ndisc_addresses) + if (!n->marked) { + has_ndisc_address = true; + break; + } + + if ((link_dhcp4_enabled(link) || link_dhcp6_enabled(link)) && + !link->dhcp_address && set_isempty(link->dhcp6_addresses) && !has_ndisc_address && + !(link_ipv4ll_enabled(link, ADDRESS_FAMILY_FALLBACK_IPV4) && link->ipv4ll_address_configured)) { + log_link_debug(link, "%s(): DHCP4 or DHCP6 is enabled but no dynamic address is assigned yet.", __func__); + return; + } + + if (link_dhcp4_enabled(link) || link_dhcp6_enabled(link) || link_dhcp6_pd_is_enabled(link) || link_ipv6_accept_ra_enabled(link)) { + if (!link->dhcp4_configured && + !(link->dhcp6_address_configured && link->dhcp6_route_configured) && + !(link->dhcp6_pd_address_configured && link->dhcp6_pd_route_configured) && + !(link->ndisc_addresses_configured && link->ndisc_routes_configured) && + !(link_ipv4ll_enabled(link, ADDRESS_FAMILY_FALLBACK_IPV4) && link->ipv4ll_address_configured)) { + /* When DHCP or RA is enabled, at least one protocol must provide an address, or + * an IPv4ll fallback address must be configured. */ + log_link_debug(link, "%s(): dynamic addresses or routes are not configured.", __func__); + return; + } + + log_link_debug(link, "%s(): dhcp4:%s dhcp6_addresses:%s dhcp_routes:%s dhcp_pd_addresses:%s dhcp_pd_routes:%s ndisc_addresses:%s ndisc_routes:%s", + __func__, + yes_no(link->dhcp4_configured), + yes_no(link->dhcp6_address_configured), + yes_no(link->dhcp6_route_configured), + yes_no(link->dhcp6_pd_address_configured), + yes_no(link->dhcp6_pd_route_configured), + yes_no(link->ndisc_addresses_configured), + yes_no(link->ndisc_routes_configured)); + } + } + + link_enter_configured(link); + + return; +} + +static int link_set_static_configs(Link *link) { + int r; + + assert(link); + assert(link->network); + assert(link->state != _LINK_STATE_INVALID); + + /* Reset all *_configured flags we are configuring. */ + link->request_static_addresses = false; + link->addresses_configured = false; + link->addresses_ready = false; + link->neighbors_configured = false; + link->static_routes_configured = false; + link->static_nexthops_configured = false; + link->routing_policy_rules_configured = false; + + r = link_set_bridge_fdb(link); + if (r < 0) + return r; + + r = link_set_bridge_mdb(link); + if (r < 0) + return r; + + r = link_set_neighbors(link); + if (r < 0) + return r; + + r = link_set_addresses(link); + if (r < 0) + return r; + + r = link_set_address_labels(link); + if (r < 0) + return r; + + /* now that we can figure out a default address for the dhcp server, start it */ + r = dhcp4_server_configure(link); + if (r < 0) + return r; + + return 0; +} + +static int link_configure_continue(Link *link); + +static int link_mac_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0) + log_link_message_warning_errno(link, m, r, "Could not set MAC address, ignoring"); + else + log_link_debug(link, "Setting MAC address done."); + + return 1; +} + +static int link_set_mac(Link *link) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(link); + assert(link->network); + assert(link->manager); + assert(link->manager->rtnl); + + if (!link->network->mac) + return 0; + + log_link_debug(link, "Setting MAC address"); + + r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m"); + + r = sd_netlink_message_append_ether_addr(req, IFLA_ADDRESS, link->network->mac); + if (r < 0) + return log_link_error_errno(link, r, "Could not set MAC address: %m"); + + r = netlink_call_async(link->manager->rtnl, NULL, req, link_mac_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + return 0; +} + +static int link_nomaster_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0) + log_link_message_warning_errno(link, m, r, "Could not set nomaster, ignoring"); + else + log_link_debug(link, "Setting nomaster done."); + + return 1; +} + +static int link_set_nomaster(Link *link) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(link); + assert(link->network); + assert(link->manager); + assert(link->manager->rtnl); + + /* set it free if not enslaved with networkd */ + if (link->network->bridge || link->network->bond || link->network->vrf) + return 0; + + log_link_debug(link, "Setting nomaster"); + + r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m"); + + r = sd_netlink_message_append_u32(req, IFLA_MASTER, 0); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_MASTER attribute: %m"); + + r = netlink_call_async(link->manager->rtnl, NULL, req, link_nomaster_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + return 0; +} + +static int set_mtu_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(m); + assert(link); + assert(link->ifname); + + link->setting_mtu = false; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0) + log_link_message_warning_errno(link, m, r, "Could not set MTU, ignoring"); + else + log_link_debug(link, "Setting MTU done."); + + if (link->state == LINK_STATE_INITIALIZED) { + r = link_configure_continue(link); + if (r < 0) + link_enter_failed(link); + } + + return 1; +} + +int link_set_mtu(Link *link, uint32_t mtu) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(link); + assert(link->manager); + assert(link->manager->rtnl); + + if (mtu == 0 || link->setting_mtu) + return 0; + + if (link->mtu == mtu) + return 0; + + log_link_debug(link, "Setting MTU: %" PRIu32, mtu); + + r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m"); + + /* IPv6 protocol requires a minimum MTU of IPV6_MTU_MIN(1280) bytes + * on the interface. Bump up MTU bytes to IPV6_MTU_MIN. */ + if (link_ipv6_enabled(link) && mtu < IPV6_MIN_MTU) { + + log_link_warning(link, "Bumping MTU to " STRINGIFY(IPV6_MIN_MTU) ", as " + "IPv6 is requested and requires a minimum MTU of " STRINGIFY(IPV6_MIN_MTU) " bytes"); + + mtu = IPV6_MIN_MTU; + } + + r = sd_netlink_message_append_u32(req, IFLA_MTU, mtu); + if (r < 0) + return log_link_error_errno(link, r, "Could not append MTU: %m"); + + r = netlink_call_async(link->manager->rtnl, NULL, req, set_mtu_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + link->setting_mtu = true; + + return 0; +} + +static bool link_reduces_vlan_mtu(Link *link) { + /* See netif_reduces_vlan_mtu() in kernel. */ + return streq_ptr(link->kind, "macsec"); +} + +static uint32_t link_get_requested_mtu_by_stacked_netdevs(Link *link) { + uint32_t mtu = 0; + NetDev *dev; + + HASHMAP_FOREACH(dev, link->network->stacked_netdevs) + if (dev->kind == NETDEV_KIND_VLAN && dev->mtu > 0) + /* See vlan_dev_change_mtu() in kernel. */ + mtu = MAX(mtu, link_reduces_vlan_mtu(link) ? dev->mtu + 4 : dev->mtu); + + else if (dev->kind == NETDEV_KIND_MACVLAN && dev->mtu > mtu) + /* See macvlan_change_mtu() in kernel. */ + mtu = dev->mtu; + + return mtu; +} + +static int link_configure_mtu(Link *link) { + uint32_t mtu; + + assert(link); + assert(link->network); + + if (link->network->mtu > 0) + return link_set_mtu(link, link->network->mtu); + + mtu = link_get_requested_mtu_by_stacked_netdevs(link); + if (link->mtu >= mtu) + return 0; + + log_link_notice(link, "Bumping MTU bytes from %"PRIu32" to %"PRIu32" because of stacked device. " + "If it is not desired, then please explicitly specify MTUBytes= setting.", + link->mtu, mtu); + + return link_set_mtu(link, mtu); +} + +static int set_flags_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(m); + assert(link); + assert(link->ifname); + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0) + log_link_message_warning_errno(link, m, r, "Could not set link flags, ignoring"); + + return 1; +} + +static int link_set_flags(Link *link) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + unsigned ifi_change = 0; + unsigned ifi_flags = 0; + int r; + + assert(link); + assert(link->manager); + assert(link->manager->rtnl); + + if (link->flags & IFF_LOOPBACK) + return 0; + + if (!link->network) + return 0; + + if (link->network->arp < 0 && link->network->multicast < 0 && link->network->allmulticast < 0) + return 0; + + r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m"); + + if (link->network->arp >= 0) { + ifi_change |= IFF_NOARP; + SET_FLAG(ifi_flags, IFF_NOARP, link->network->arp == 0); + } + + if (link->network->multicast >= 0) { + ifi_change |= IFF_MULTICAST; + SET_FLAG(ifi_flags, IFF_MULTICAST, link->network->multicast); + } + + if (link->network->allmulticast >= 0) { + ifi_change |= IFF_ALLMULTI; + SET_FLAG(ifi_flags, IFF_ALLMULTI, link->network->allmulticast); + } + + r = sd_rtnl_message_link_set_flags(req, ifi_flags, ifi_change); + if (r < 0) + return log_link_error_errno(link, r, "Could not set link flags: %m"); + + r = netlink_call_async(link->manager->rtnl, NULL, req, set_flags_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + return 0; +} + +static int link_acquire_ipv6_conf(Link *link) { + int r; + + assert(link); + + if (link->ndisc) { + log_link_debug(link, "Discovering IPv6 routers"); + + r = sd_ndisc_start(link->ndisc); + if (r < 0 && r != -EBUSY) + return log_link_warning_errno(link, r, "Could not start IPv6 Router Discovery: %m"); + } + + if (link->radv) { + assert(link->radv); + assert(in_addr_is_link_local(AF_INET6, (const union in_addr_union*)&link->ipv6ll_address) > 0); + + log_link_debug(link, "Starting IPv6 Router Advertisements"); + + r = radv_emit_dns(link); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to configure DNS or Domains in IPv6 Router Advertisement: %m"); + + r = sd_radv_start(link->radv); + if (r < 0 && r != -EBUSY) + return log_link_warning_errno(link, r, "Could not start IPv6 Router Advertisement: %m"); + } + + if (link_dhcp6_enabled(link) && IN_SET(link->network->dhcp6_without_ra, + DHCP6_CLIENT_START_MODE_INFORMATION_REQUEST, + DHCP6_CLIENT_START_MODE_SOLICIT)) { + assert(link->dhcp6_client); + assert(in_addr_is_link_local(AF_INET6, (const union in_addr_union*)&link->ipv6ll_address) > 0); + + r = dhcp6_request_address(link, link->network->dhcp6_without_ra == DHCP6_CLIENT_START_MODE_INFORMATION_REQUEST); + if (r < 0 && r != -EBUSY) + return log_link_warning_errno(link, r, "Could not acquire DHCPv6 lease: %m"); + else + log_link_debug(link, "Acquiring DHCPv6 lease"); + } + + r = dhcp6_request_prefix_delegation(link); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to request DHCPv6 prefix delegation: %m"); + + return 0; +} + +static int link_acquire_ipv4_conf(Link *link) { + int r; + + assert(link); + assert(link->manager); + assert(link->manager->event); + + if (link_ipv4ll_enabled(link, ADDRESS_FAMILY_IPV4)) { + assert(link->ipv4ll); + + log_link_debug(link, "Acquiring IPv4 link-local address"); + + r = sd_ipv4ll_start(link->ipv4ll); + if (r < 0) + return log_link_warning_errno(link, r, "Could not acquire IPv4 link-local address: %m"); + } + + if (link->dhcp_client) { + log_link_debug(link, "Acquiring DHCPv4 lease"); + + r = sd_dhcp_client_start(link->dhcp_client); + if (r < 0) + return log_link_warning_errno(link, r, "Could not acquire DHCPv4 lease: %m"); + } + + return 0; +} + +static int link_acquire_conf(Link *link) { + int r; + + assert(link); + + r = link_acquire_ipv4_conf(link); + if (r < 0) + return r; + + if (!in_addr_is_null(AF_INET6, (const union in_addr_union*) &link->ipv6ll_address)) { + r = link_acquire_ipv6_conf(link); + if (r < 0) + return r; + } + + r = link_lldp_emit_start(link); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to start LLDP transmission: %m"); + + return 0; +} + +bool link_has_carrier(Link *link) { + /* see Documentation/networking/operstates.txt in the kernel sources */ + + if (link->kernel_operstate == IF_OPER_UP) + return true; + + if (link->kernel_operstate == IF_OPER_UNKNOWN) + /* operstate may not be implemented, so fall back to flags */ + if (FLAGS_SET(link->flags, IFF_LOWER_UP | IFF_RUNNING) && + !FLAGS_SET(link->flags, IFF_DORMANT)) + return true; + + return false; +} + +static int link_address_genmode_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + + link->setting_genmode = false; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0) + log_link_message_warning_errno(link, m, r, "Could not set address genmode for interface, ignoring"); + else + log_link_debug(link, "Setting address genmode done."); + + if (link->state == LINK_STATE_INITIALIZED) { + r = link_configure_continue(link); + if (r < 0) + link_enter_failed(link); + } + + return 1; +} + +static int link_configure_addrgen_mode(Link *link) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + uint8_t ipv6ll_mode; + int r; + + assert(link); + assert(link->network); + assert(link->manager); + assert(link->manager->rtnl); + + if (!socket_ipv6_is_supported() || link->setting_genmode) + return 0; + + log_link_debug(link, "Setting address genmode for link"); + + r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m"); + + r = sd_netlink_message_open_container(req, IFLA_AF_SPEC); + if (r < 0) + return log_link_error_errno(link, r, "Could not open IFLA_AF_SPEC container: %m"); + + r = sd_netlink_message_open_container(req, AF_INET6); + if (r < 0) + return log_link_error_errno(link, r, "Could not open AF_INET6 container: %m"); + + if (!link_ipv6ll_enabled(link)) + ipv6ll_mode = IN6_ADDR_GEN_MODE_NONE; + else if (link->network->ipv6ll_address_gen_mode < 0) { + r = sysctl_read_ip_property(AF_INET6, link->ifname, "stable_secret", NULL); + if (r < 0) { + /* The file may not exist. And even if it exists, when stable_secret is unset, + * reading the file fails with EIO. */ + log_link_debug_errno(link, r, "Failed to read sysctl property stable_secret: %m"); + + ipv6ll_mode = IN6_ADDR_GEN_MODE_EUI64; + } else + ipv6ll_mode = IN6_ADDR_GEN_MODE_STABLE_PRIVACY; + } else + ipv6ll_mode = link->network->ipv6ll_address_gen_mode; + + r = sd_netlink_message_append_u8(req, IFLA_INET6_ADDR_GEN_MODE, ipv6ll_mode); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_INET6_ADDR_GEN_MODE: %m"); + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not close AF_INET6 container: %m"); + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not close IFLA_AF_SPEC container: %m"); + + r = netlink_call_async(link->manager->rtnl, NULL, req, link_address_genmode_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + link->setting_genmode = true; + + return 0; +} + +static int link_up_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0) + /* we warn but don't fail the link, as it may be brought up later */ + log_link_message_warning_errno(link, m, r, "Could not bring up interface"); + + return 1; +} + +static int link_up(Link *link) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(link); + assert(link->network); + assert(link->manager); + assert(link->manager->rtnl); + + log_link_debug(link, "Bringing link up"); + + r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m"); + + r = sd_rtnl_message_link_set_flags(req, IFF_UP, IFF_UP); + if (r < 0) + return log_link_error_errno(link, r, "Could not set link flags: %m"); + + r = netlink_call_async(link->manager->rtnl, NULL, req, link_up_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + return 0; +} + +static int link_down_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0) + log_link_message_warning_errno(link, m, r, "Could not bring down interface"); + + return 1; +} + +int link_down(Link *link, link_netlink_message_handler_t callback) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(link); + assert(link->manager); + assert(link->manager->rtnl); + + log_link_debug(link, "Bringing link down"); + + r = sd_rtnl_message_new_link(link->manager->rtnl, &req, + RTM_SETLINK, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m"); + + r = sd_rtnl_message_link_set_flags(req, 0, IFF_UP); + if (r < 0) + return log_link_error_errno(link, r, "Could not set link flags: %m"); + + r = netlink_call_async(link->manager->rtnl, NULL, req, + callback ?: link_down_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + return 0; +} + +static int link_group_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0) + log_link_message_warning_errno(link, m, r, "Could not set group for the interface"); + + return 1; +} + +static int link_set_group(Link *link) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(link); + assert(link->network); + assert(link->manager); + assert(link->manager->rtnl); + + if (link->network->group <= 0) + return 0; + + log_link_debug(link, "Setting group"); + + r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m"); + + r = sd_netlink_message_append_u32(req, IFLA_GROUP, link->network->group); + if (r < 0) + return log_link_error_errno(link, r, "Could not set link group: %m"); + + r = netlink_call_async(link->manager->rtnl, NULL, req, link_group_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + return 0; +} + +static int link_handle_bound_to_list(Link *link) { + Link *l; + int r; + bool required_up = false; + bool link_is_up = false; + + assert(link); + + if (hashmap_isempty(link->bound_to_links)) + return 0; + + if (link->flags & IFF_UP) + link_is_up = true; + + HASHMAP_FOREACH (l, link->bound_to_links) + if (link_has_carrier(l)) { + required_up = true; + break; + } + + if (!required_up && link_is_up) { + r = link_down(link, NULL); + if (r < 0) + return r; + } else if (required_up && !link_is_up) { + r = link_up(link); + if (r < 0) + return r; + } + + return 0; +} + +static int link_handle_bound_by_list(Link *link) { + Link *l; + int r; + + assert(link); + + if (hashmap_isempty(link->bound_by_links)) + return 0; + + HASHMAP_FOREACH (l, link->bound_by_links) { + r = link_handle_bound_to_list(l); + if (r < 0) + return r; + } + + return 0; +} + +static int link_put_carrier(Link *link, Link *carrier, Hashmap **h) { + int r; + + assert(link); + assert(carrier); + + if (link == carrier) + return 0; + + if (hashmap_get(*h, INT_TO_PTR(carrier->ifindex))) + return 0; + + r = hashmap_ensure_allocated(h, NULL); + if (r < 0) + return r; + + r = hashmap_put(*h, INT_TO_PTR(carrier->ifindex), carrier); + if (r < 0) + return r; + + return 0; +} + +static int link_new_bound_by_list(Link *link) { + Manager *m; + Link *carrier; + int r; + bool list_updated = false; + + assert(link); + assert(link->manager); + + m = link->manager; + + HASHMAP_FOREACH(carrier, m->links) { + if (!carrier->network) + continue; + + if (strv_isempty(carrier->network->bind_carrier)) + continue; + + if (strv_fnmatch(carrier->network->bind_carrier, link->ifname)) { + r = link_put_carrier(link, carrier, &link->bound_by_links); + if (r < 0) + return r; + + list_updated = true; + } + } + + if (list_updated) + link_dirty(link); + + HASHMAP_FOREACH(carrier, link->bound_by_links) { + r = link_put_carrier(carrier, link, &carrier->bound_to_links); + if (r < 0) + return r; + + link_dirty(carrier); + } + + return 0; +} + +static int link_new_bound_to_list(Link *link) { + Manager *m; + Link *carrier; + int r; + bool list_updated = false; + + assert(link); + assert(link->manager); + + if (!link->network) + return 0; + + if (strv_isempty(link->network->bind_carrier)) + return 0; + + m = link->manager; + + HASHMAP_FOREACH (carrier, m->links) { + if (strv_fnmatch(link->network->bind_carrier, carrier->ifname)) { + r = link_put_carrier(link, carrier, &link->bound_to_links); + if (r < 0) + return r; + + list_updated = true; + } + } + + if (list_updated) + link_dirty(link); + + HASHMAP_FOREACH (carrier, link->bound_to_links) { + r = link_put_carrier(carrier, link, &carrier->bound_by_links); + if (r < 0) + return r; + + link_dirty(carrier); + } + + return 0; +} + +static int link_new_carrier_maps(Link *link) { + int r; + + r = link_new_bound_by_list(link); + if (r < 0) + return r; + + r = link_handle_bound_by_list(link); + if (r < 0) + return r; + + r = link_new_bound_to_list(link); + if (r < 0) + return r; + + r = link_handle_bound_to_list(link); + if (r < 0) + return r; + + return 0; +} + +static void link_free_bound_to_list(Link *link) { + Link *bound_to; + + HASHMAP_FOREACH (bound_to, link->bound_to_links) { + hashmap_remove(link->bound_to_links, INT_TO_PTR(bound_to->ifindex)); + + if (hashmap_remove(bound_to->bound_by_links, INT_TO_PTR(link->ifindex))) + link_dirty(bound_to); + } + + return; +} + +static void link_free_bound_by_list(Link *link) { + Link *bound_by; + + HASHMAP_FOREACH (bound_by, link->bound_by_links) { + hashmap_remove(link->bound_by_links, INT_TO_PTR(bound_by->ifindex)); + + if (hashmap_remove(bound_by->bound_to_links, INT_TO_PTR(link->ifindex))) { + link_dirty(bound_by); + link_handle_bound_to_list(bound_by); + } + } + + return; +} + +static void link_free_carrier_maps(Link *link) { + bool list_updated = false; + + assert(link); + + if (!hashmap_isempty(link->bound_to_links)) { + link_free_bound_to_list(link); + list_updated = true; + } + + if (!hashmap_isempty(link->bound_by_links)) { + link_free_bound_by_list(link); + list_updated = true; + } + + if (list_updated) + link_dirty(link); + + return; +} + +static int link_append_to_master(Link *link, NetDev *netdev) { + Link *master; + int r; + + assert(link); + assert(netdev); + + r = link_get(link->manager, netdev->ifindex, &master); + if (r < 0) + return r; + + r = set_ensure_put(&master->slaves, NULL, link); + if (r <= 0) + return r; + + link_ref(link); + return 0; +} + +static void link_drop_from_master(Link *link, NetDev *netdev) { + Link *master; + + assert(link); + + if (!link->manager || !netdev) + return; + + if (link_get(link->manager, netdev->ifindex, &master) < 0) + return; + + link_unref(set_remove(master->slaves, link)); +} + +static void link_detach_from_manager(Link *link) { + if (!link || !link->manager) + return; + + link_unref(set_remove(link->manager->links_requesting_uuid, link)); + link_clean(link); + + /* The following must be called at last. */ + assert_se(hashmap_remove(link->manager->links, INT_TO_PTR(link->ifindex)) == link); + link_unref(link); +} + +void link_drop(Link *link) { + if (!link || link->state == LINK_STATE_LINGER) + return; + + link_set_state(link, LINK_STATE_LINGER); + + link_free_carrier_maps(link); + + if (link->network) { + link_drop_from_master(link, link->network->bridge); + link_drop_from_master(link, link->network->bond); + } + + log_link_debug(link, "Link removed"); + + (void) unlink(link->state_file); + link_detach_from_manager(link); +} + +static int link_joined(Link *link) { + int r; + + assert(link); + assert(link->network); + + if (!hashmap_isempty(link->bound_to_links)) { + r = link_handle_bound_to_list(link); + if (r < 0) + return r; + } else if (!(link->flags & IFF_UP)) { + r = link_up(link); + if (r < 0) { + link_enter_failed(link); + return r; + } + } + + if (link->network->bridge) { + r = link_set_bridge(link); + if (r < 0) + log_link_error_errno(link, r, "Could not set bridge message: %m"); + + r = link_append_to_master(link, link->network->bridge); + if (r < 0) + log_link_error_errno(link, r, "Failed to add to bridge master's slave list: %m"); + } + + if (link->network->bond) { + r = link_set_bond(link); + if (r < 0) + log_link_error_errno(link, r, "Could not set bond message: %m"); + + r = link_append_to_master(link, link->network->bond); + if (r < 0) + log_link_error_errno(link, r, "Failed to add to bond master's slave list: %m"); + } + + r = link_set_bridge_vlan(link); + if (r < 0) + log_link_error_errno(link, r, "Could not set bridge vlan: %m"); + + /* Skip setting up addresses until it gets carrier, + or it would try to set addresses twice, + which is bad for non-idempotent steps. */ + if (!link_has_carrier(link) && !link->network->configure_without_carrier) + return 0; + + link_set_state(link, LINK_STATE_CONFIGURING); + + r = link_acquire_conf(link); + if (r < 0) + return r; + + return link_set_static_configs(link); +} + +static int netdev_join_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + assert(link->network); + assert(link->enslaving > 0); + + link->enslaving--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EEXIST) { + log_link_message_warning_errno(link, m, r, "Could not join netdev"); + link_enter_failed(link); + return 1; + } + + log_link_debug(link, "Joined netdev"); + + if (link->enslaving == 0) { + r = link_joined(link); + if (r < 0) + link_enter_failed(link); + } + + return 1; +} + +static int link_enter_join_netdev(Link *link) { + NetDev *netdev; + int r; + + assert(link); + assert(link->network); + assert(link->state == LINK_STATE_INITIALIZED); + + link_set_state(link, LINK_STATE_CONFIGURING); + + link_dirty(link); + link->enslaving = 0; + + if (link->network->bond) { + if (link->network->bond->state == NETDEV_STATE_READY && + link->network->bond->ifindex == link->master_ifindex) + return link_joined(link); + + log_struct(LOG_DEBUG, + LOG_LINK_INTERFACE(link), + LOG_NETDEV_INTERFACE(link->network->bond), + LOG_LINK_MESSAGE(link, "Enslaving by '%s'", link->network->bond->ifname)); + + link->enslaving++; + + r = netdev_join(link->network->bond, link, netdev_join_handler); + if (r < 0) { + log_struct_errno(LOG_WARNING, r, + LOG_LINK_INTERFACE(link), + LOG_NETDEV_INTERFACE(link->network->bond), + LOG_LINK_MESSAGE(link, "Could not join netdev '%s': %m", link->network->bond->ifname)); + link_enter_failed(link); + return r; + } + } + + if (link->network->bridge) { + log_struct(LOG_DEBUG, + LOG_LINK_INTERFACE(link), + LOG_NETDEV_INTERFACE(link->network->bridge), + LOG_LINK_MESSAGE(link, "Enslaving by '%s'", link->network->bridge->ifname)); + + link->enslaving++; + + r = netdev_join(link->network->bridge, link, netdev_join_handler); + if (r < 0) { + log_struct_errno(LOG_WARNING, r, + LOG_LINK_INTERFACE(link), + LOG_NETDEV_INTERFACE(link->network->bridge), + LOG_LINK_MESSAGE(link, "Could not join netdev '%s': %m", link->network->bridge->ifname)); + link_enter_failed(link); + return r; + } + } + + if (link->network->vrf) { + log_struct(LOG_DEBUG, + LOG_LINK_INTERFACE(link), + LOG_NETDEV_INTERFACE(link->network->vrf), + LOG_LINK_MESSAGE(link, "Enslaving by '%s'", link->network->vrf->ifname)); + + link->enslaving++; + + r = netdev_join(link->network->vrf, link, netdev_join_handler); + if (r < 0) { + log_struct_errno(LOG_WARNING, r, + LOG_LINK_INTERFACE(link), + LOG_NETDEV_INTERFACE(link->network->vrf), + LOG_LINK_MESSAGE(link, "Could not join netdev '%s': %m", link->network->vrf->ifname)); + link_enter_failed(link); + return r; + } + } + + HASHMAP_FOREACH(netdev, link->network->stacked_netdevs) { + + if (netdev->ifindex > 0) + /* Assume already enslaved. */ + continue; + + if (netdev_get_create_type(netdev) != NETDEV_CREATE_STACKED) + continue; + + log_struct(LOG_DEBUG, + LOG_LINK_INTERFACE(link), + LOG_NETDEV_INTERFACE(netdev), + LOG_LINK_MESSAGE(link, "Enslaving by '%s'", netdev->ifname)); + + link->enslaving++; + + r = netdev_join(netdev, link, netdev_join_handler); + if (r < 0) { + log_struct_errno(LOG_WARNING, r, + LOG_LINK_INTERFACE(link), + LOG_NETDEV_INTERFACE(netdev), + LOG_LINK_MESSAGE(link, "Could not join netdev '%s': %m", netdev->ifname)); + link_enter_failed(link); + return r; + } + } + + if (link->enslaving == 0) + return link_joined(link); + + return 0; +} + +static int link_drop_foreign_config(Link *link) { + int r; + + r = link_drop_foreign_addresses(link); + if (r < 0) + return r; + + r = link_drop_foreign_neighbors(link); + if (r < 0) + return r; + + return link_drop_foreign_routes(link); +} + +static int link_drop_config(Link *link) { + int r; + + r = link_drop_addresses(link); + if (r < 0) + return r; + + r = link_drop_neighbors(link); + if (r < 0) + return r; + + r = link_drop_routes(link); + if (r < 0) + return r; + + ndisc_flush(link); + + return 0; +} + +int link_configure(Link *link) { + int r; + + assert(link); + assert(link->network); + assert(link->state == LINK_STATE_INITIALIZED); + + r = link_configure_traffic_control(link); + if (r < 0) + return r; + + r = link_configure_sr_iov(link); + if (r < 0) + return r; + + if (link->iftype == ARPHRD_CAN) + return link_configure_can(link); + + r = link_set_sysctl(link); + if (r < 0) + return r; + + r = link_set_ipv6_proxy_ndp_addresses(link); + if (r < 0) + return r; + + r = link_set_mac(link); + if (r < 0) + return r; + + r = link_set_nomaster(link); + if (r < 0) + return r; + + r = link_set_flags(link); + if (r < 0) + return r; + + r = link_set_group(link); + if (r < 0) + return r; + + r = ipv4ll_configure(link); + if (r < 0) + return r; + + r = dhcp4_configure(link); + if (r < 0) + return r; + + r = dhcp6_configure(link); + if (r < 0) + return r; + + r = ndisc_configure(link); + if (r < 0) + return r; + + r = radv_configure(link); + if (r < 0) + return r; + + r = link_lldp_rx_configure(link); + if (r < 0) + return r; + + r = link_configure_mtu(link); + if (r < 0) + return r; + + r = link_configure_addrgen_mode(link); + if (r < 0) + return r; + + return link_configure_continue(link); +} + +/* The configuration continues in this separate function, instead of + * including this in the above link_configure() function, for two + * reasons: + * 1) some devices reset the link when the mtu is set, which caused + * an infinite loop here in networkd; see: + * https://github.com/systemd/systemd/issues/6593 + * https://github.com/systemd/systemd/issues/9831 + * 2) if ipv6ll is disabled, then bringing the interface up must be + * delayed until after we get confirmation from the kernel that + * the addr_gen_mode parameter has been set (via netlink), see: + * https://github.com/systemd/systemd/issues/13882 + */ +static int link_configure_continue(Link *link) { + int r; + + assert(link); + assert(link->network); + assert(link->state == LINK_STATE_INITIALIZED); + + if (link->setting_mtu || link->setting_genmode) + return 0; + + /* Drop foreign config, but ignore loopback or critical devices. + * We do not want to remove loopback address or addresses used for root NFS. */ + if (!(link->flags & IFF_LOOPBACK) && + link->network->keep_configuration != KEEP_CONFIGURATION_YES) { + r = link_drop_foreign_config(link); + if (r < 0) + return r; + } + + /* The kernel resets ipv6 mtu after changing device mtu; + * we must set this here, after we've set device mtu */ + r = link_set_ipv6_mtu(link); + if (r < 0) + log_link_warning_errno(link, r, "Cannot set IPv6 MTU for interface, ignoring: %m"); + + return link_enter_join_netdev(link); +} + +static int link_reconfigure_internal(Link *link, sd_netlink_message *m, bool force) { + _cleanup_strv_free_ char **s = NULL; + Network *network; + int r; + + assert(m); + + r = sd_netlink_message_get_errno(m); + if (r < 0) + return r; + + r = sd_netlink_message_read_strv(m, IFLA_PROP_LIST, IFLA_ALT_IFNAME, &s); + if (r < 0 && r != -ENODATA) + return r; + + strv_free_and_replace(link->alternative_names, s); + + r = network_get(link->manager, link->iftype, link->sd_device, + link->ifname, link->alternative_names, link->driver, + &link->hw_addr.addr.ether, &link->permanent_mac, + link->wlan_iftype, link->ssid, &link->bssid, &network); + if (r == -ENOENT) { + link_enter_unmanaged(link); + return 0; + } else if (r == 0 && network->unmanaged) { + link_enter_unmanaged(link); + return 0; + } else if (r < 0) + return r; + + if (link->network == network && !force) + return 0; + + log_link_info(link, "Re-configuring with %s", network->filename); + + /* Dropping old .network file */ + r = link_stop_engines(link, false); + if (r < 0) + return r; + + r = link_drop_config(link); + if (r < 0) + return r; + + if (!IN_SET(link->state, LINK_STATE_UNMANAGED, LINK_STATE_PENDING, LINK_STATE_INITIALIZED)) { + log_link_debug(link, "State is %s, dropping config", link_state_to_string(link->state)); + r = link_drop_foreign_config(link); + if (r < 0) + return r; + } + + link_free_carrier_maps(link); + link_free_engines(link); + link->network = network_unref(link->network); + + /* Then, apply new .network file */ + r = network_apply(network, link); + if (r < 0) + return r; + + r = link_new_carrier_maps(link); + if (r < 0) + return r; + + link_set_state(link, LINK_STATE_INITIALIZED); + link_dirty(link); + + /* link_configure_duid() returns 0 if it requests product UUID. In that case, + * link_configure() is called later asynchronously. */ + r = link_configure_duid(link); + if (r <= 0) + return r; + + r = link_configure(link); + if (r < 0) + return r; + + return 0; +} + +static int link_reconfigure_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + r = link_reconfigure_internal(link, m, false); + if (r < 0) + link_enter_failed(link); + + return 1; +} + +static int link_force_reconfigure_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + r = link_reconfigure_internal(link, m, true); + if (r < 0) + link_enter_failed(link); + + return 1; +} + +int link_reconfigure(Link *link, bool force) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + /* When link in pending or initialized state, then link_configure() will be called. To prevent + * the function be called multiple times simultaneously, refuse to reconfigure the interface in + * these case. */ + if (IN_SET(link->state, LINK_STATE_PENDING, LINK_STATE_INITIALIZED, LINK_STATE_LINGER)) + return 0; /* o means no-op. */ + + r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_GETLINK, + link->ifindex); + if (r < 0) + return r; + + r = netlink_call_async(link->manager->rtnl, NULL, req, + force ? link_force_reconfigure_handler : link_reconfigure_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return r; + + link_ref(link); + + return 1; /* 1 means the interface will be reconfigured. */ +} + +static int link_initialized_and_synced(Link *link) { + Network *network; + int r; + + assert(link); + assert(link->ifname); + assert(link->manager); + + /* We may get called either from the asynchronous netlink callback, + * or directly for link_add() if running in a container. See link_add(). */ + if (!IN_SET(link->state, LINK_STATE_PENDING, LINK_STATE_INITIALIZED)) + return 0; + + log_link_debug(link, "Link state is up-to-date"); + link_set_state(link, LINK_STATE_INITIALIZED); + + r = link_new_bound_by_list(link); + if (r < 0) + return r; + + r = link_handle_bound_by_list(link); + if (r < 0) + return r; + + if (!link->network) { + r = wifi_get_info(link); + if (r < 0) + return r; + + r = network_get(link->manager, link->iftype, link->sd_device, + link->ifname, link->alternative_names, link->driver, + &link->hw_addr.addr.ether, &link->permanent_mac, + link->wlan_iftype, link->ssid, &link->bssid, &network); + if (r == -ENOENT) { + link_enter_unmanaged(link); + return 0; + } else if (r == 0 && network->unmanaged) { + link_enter_unmanaged(link); + return 0; + } else if (r < 0) + return r; + + if (link->flags & IFF_LOOPBACK) { + if (network->link_local != ADDRESS_FAMILY_NO) + log_link_debug(link, "Ignoring link-local autoconfiguration for loopback link"); + + if (network->dhcp != ADDRESS_FAMILY_NO) + log_link_debug(link, "Ignoring DHCP clients for loopback link"); + + if (network->dhcp_server) + log_link_debug(link, "Ignoring DHCP server for loopback link"); + } + + r = network_apply(network, link); + if (r < 0) + return r; + } + + r = link_new_bound_to_list(link); + if (r < 0) + return r; + + /* link_configure_duid() returns 0 if it requests product UUID. In that case, + * link_configure() is called later asynchronously. */ + r = link_configure_duid(link); + if (r <= 0) + return r; + + r = link_configure(link); + if (r < 0) + return r; + + return 0; +} + +static int link_initialized_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + _cleanup_strv_free_ char **s = NULL; + int r; + + r = sd_netlink_message_get_errno(m); + if (r < 0) { + log_link_warning_errno(link, r, "Failed to wait for the interface to be initialized: %m"); + link_enter_failed(link); + return 0; + } + + r = sd_netlink_message_read_strv(m, IFLA_PROP_LIST, IFLA_ALT_IFNAME, &s); + if (r < 0 && r != -ENODATA) { + link_enter_failed(link); + return 0; + } + + strv_free_and_replace(link->alternative_names, s); + + r = link_initialized_and_synced(link); + if (r < 0) + link_enter_failed(link); + return 1; +} + +int link_initialized(Link *link, sd_device *device) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(link); + assert(link->manager); + assert(link->manager->rtnl); + assert(device); + + if (link->state != LINK_STATE_PENDING) + return 0; + + if (link->sd_device) + return 0; + + log_link_debug(link, "udev initialized link"); + link_set_state(link, LINK_STATE_INITIALIZED); + + link->sd_device = sd_device_ref(device); + + /* udev has initialized the link, but we don't know if we have yet + * processed the NEWLINK messages with the latest state. Do a GETLINK, + * when it returns we know that the pending NEWLINKs have already been + * processed and that we are up-to-date */ + + r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_GETLINK, + link->ifindex); + if (r < 0) + return r; + + r = netlink_call_async(link->manager->rtnl, NULL, req, link_initialized_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return r; + + link_ref(link); + + return 0; +} + +static int link_load(Link *link) { + _cleanup_free_ char *network_file = NULL, + *addresses = NULL, + *routes = NULL, + *dhcp4_address = NULL, + *ipv4ll_address = NULL; + int r; + + assert(link); + + r = parse_env_file(NULL, link->state_file, + "NETWORK_FILE", &network_file, + "ADDRESSES", &addresses, + "ROUTES", &routes, + "DHCP4_ADDRESS", &dhcp4_address, + "IPV4LL_ADDRESS", &ipv4ll_address); + if (r < 0 && r != -ENOENT) + return log_link_error_errno(link, r, "Failed to read %s: %m", link->state_file); + + if (network_file) { + Network *network; + char *suffix; + + /* drop suffix */ + suffix = strrchr(network_file, '.'); + if (!suffix) { + log_link_debug(link, "Failed to get network name from %s", network_file); + goto network_file_fail; + } + *suffix = '\0'; + + r = network_get_by_name(link->manager, basename(network_file), &network); + if (r < 0) { + log_link_debug_errno(link, r, "Failed to get network %s: %m", basename(network_file)); + goto network_file_fail; + } + + r = network_apply(network, link); + if (r < 0) + return log_link_error_errno(link, r, "Failed to apply network %s: %m", basename(network_file)); + } + +network_file_fail: + + r = link_deserialize_addresses(link, addresses); + if (r < 0) + log_link_warning_errno(link, r, "Failed to load addresses from %s, ignoring: %m", link->state_file); + + r = link_deserialize_routes(link, routes); + if (r < 0) + log_link_warning_errno(link, r, "Failed to load routes from %s, ignoring: %m", link->state_file); + + r = link_deserialize_dhcp4(link, dhcp4_address); + if (r < 0) + log_link_warning_errno(link, r, "Failed to load DHCPv4 address from %s, ignoring: %m", link->state_file); + + r = link_deserialize_ipv4ll(link, ipv4ll_address); + if (r < 0) + log_link_warning_errno(link, r, "Failed to load IPv4LL address from %s, ignoring: %m", link->state_file); + + return 0; +} + +int link_add(Manager *m, sd_netlink_message *message, Link **ret) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + char ifindex_str[2 + DECIMAL_STR_MAX(int)]; + Link *link; + int r; + + assert(m); + assert(m->rtnl); + assert(message); + assert(ret); + + r = link_new(m, message, ret); + if (r < 0) + return r; + + link = *ret; + + log_link_debug(link, "Link %d added", link->ifindex); + + r = link_load(link); + if (r < 0) + return r; + + if (path_is_read_only_fs("/sys") <= 0) { + /* udev should be around */ + sprintf(ifindex_str, "n%d", link->ifindex); + r = sd_device_new_from_device_id(&device, ifindex_str); + if (r < 0) { + log_link_warning_errno(link, r, "Could not find device, waiting for device initialization: %m"); + return 0; + } + + r = sd_device_get_is_initialized(device); + if (r < 0) { + log_link_warning_errno(link, r, "Could not determine whether the device is initialized: %m"); + goto failed; + } + if (r == 0) { + /* not yet ready */ + log_link_debug(link, "link pending udev initialization..."); + return 0; + } + + r = device_is_renaming(device); + if (r < 0) { + log_link_warning_errno(link, r, "Failed to determine the device is being renamed: %m"); + goto failed; + } + if (r > 0) { + log_link_debug(link, "Interface is being renamed, pending initialization."); + return 0; + } + + r = link_initialized(link, device); + if (r < 0) + goto failed; + } else { + r = link_initialized_and_synced(link); + if (r < 0) + goto failed; + } + + return 0; +failed: + link_enter_failed(link); + return r; +} + +int link_ipv6ll_gained(Link *link, const struct in6_addr *address) { + int r; + + assert(link); + + log_link_info(link, "Gained IPv6LL"); + + link->ipv6ll_address = *address; + link_check_ready(link); + + if (IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) { + r = link_acquire_ipv6_conf(link); + if (r < 0) { + link_enter_failed(link); + return r; + } + } + + return 0; +} + +static int link_carrier_gained(Link *link) { + int r; + + assert(link); + + r = wifi_get_info(link); + if (r < 0) + return r; + if (r > 0) { + r = link_reconfigure(link, false); + if (r < 0) { + link_enter_failed(link); + return r; + } + } + + if (IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) { + r = link_acquire_conf(link); + if (r < 0) { + link_enter_failed(link); + return r; + } + + link_set_state(link, LINK_STATE_CONFIGURING); + r = link_set_static_configs(link); + if (r < 0) + return r; + } + + r = link_handle_bound_by_list(link); + if (r < 0) + return r; + + if (!link->bridge_mdb_configured) { + r = link_set_bridge_mdb(link); + if (r < 0) + return r; + } + + if (streq_ptr(link->kind, "bridge")) { + Link *slave; + + SET_FOREACH(slave, link->slaves) { + if (slave->bridge_mdb_configured) + continue; + + r = link_set_bridge_mdb(slave); + if (r < 0) + link_enter_failed(slave); + } + } + + return 0; +} + +static int link_carrier_lost(Link *link) { + int r; + + assert(link); + + if (link->network && link->network->ignore_carrier_loss) + return 0; + + /* Some devices reset itself while setting the MTU. This causes the DHCP client fall into a loop. + * setting_mtu keep track whether the device got reset because of setting MTU and does not drop the + * configuration and stop the clients as well. */ + if (link->setting_mtu) + return 0; + + r = link_stop_engines(link, false); + if (r < 0) { + link_enter_failed(link); + return r; + } + + r = link_drop_config(link); + if (r < 0) + return r; + + if (!IN_SET(link->state, LINK_STATE_UNMANAGED, LINK_STATE_PENDING, LINK_STATE_INITIALIZED)) { + log_link_debug(link, "State is %s, dropping config", link_state_to_string(link->state)); + r = link_drop_foreign_config(link); + if (r < 0) + return r; + } + + r = link_handle_bound_by_list(link); + if (r < 0) + return r; + + return 0; +} + +int link_carrier_reset(Link *link) { + int r; + + assert(link); + + if (link_has_carrier(link)) { + r = link_carrier_lost(link); + if (r < 0) + return r; + + r = link_carrier_gained(link); + if (r < 0) + return r; + + log_link_info(link, "Reset carrier"); + } + + return 0; +} + +/* This is called every time an interface admin state changes to up; + * specifically, when IFF_UP flag changes from unset to set */ +static int link_admin_state_up(Link *link) { + int r; + + /* We set the ipv6 mtu after the device mtu, but the kernel resets + * ipv6 mtu on NETDEV_UP, so we need to reset it. The check for + * ipv6_mtu_set prevents this from trying to set it too early before + * the link->network has been setup; we only need to reset it + * here if we've already set it during normal initialization. */ + if (link->ipv6_mtu_set) { + r = link_set_ipv6_mtu(link); + if (r < 0) + return r; + } + + return 0; +} + +int link_update(Link *link, sd_netlink_message *m) { + _cleanup_strv_free_ char **s = NULL; + hw_addr_data hw_addr; + const char *ifname; + uint32_t mtu; + bool had_carrier, carrier_gained, carrier_lost, link_was_admin_up; + int old_master, r; + + assert(link); + assert(link->ifname); + assert(m); + + if (link->state == LINK_STATE_LINGER) { + log_link_info(link, "Link re-added"); + link_set_state(link, LINK_STATE_CONFIGURING); + + r = link_new_carrier_maps(link); + if (r < 0) + return r; + } + + r = sd_netlink_message_read_string(m, IFLA_IFNAME, &ifname); + if (r >= 0 && !streq(ifname, link->ifname)) { + Manager *manager = link->manager; + + log_link_info(link, "Interface name change detected, %s has been renamed to %s.", link->ifname, ifname); + + link_drop(link); + r = link_add(manager, m, &link); + if (r < 0) + return r; + } + + r = sd_netlink_message_read_strv(m, IFLA_PROP_LIST, IFLA_ALT_IFNAME, &s); + if (r >= 0) + strv_free_and_replace(link->alternative_names, s); + + r = sd_netlink_message_read_u32(m, IFLA_MTU, &mtu); + if (r >= 0 && mtu > 0) { + link->mtu = mtu; + if (link->original_mtu == 0) { + link->original_mtu = mtu; + log_link_debug(link, "Saved original MTU: %" PRIu32, link->original_mtu); + } + + if (link->dhcp_client) { + r = sd_dhcp_client_set_mtu(link->dhcp_client, + link->mtu); + if (r < 0) + return log_link_warning_errno(link, r, "Could not update MTU in DHCP client: %m"); + } + + if (link->radv) { + r = sd_radv_set_mtu(link->radv, link->mtu); + if (r < 0) + return log_link_warning_errno(link, r, "Could not set MTU for Router Advertisement: %m"); + } + } + + /* The kernel may broadcast NEWLINK messages without the MAC address + set, simply ignore them. */ + r = netlink_message_read_hw_addr(m, IFLA_ADDRESS, &hw_addr); + if (r >= 0 && (link->hw_addr.length != hw_addr.length || + memcmp(link->hw_addr.addr.bytes, hw_addr.addr.bytes, hw_addr.length) != 0)) { + + memcpy(link->hw_addr.addr.bytes, hw_addr.addr.bytes, hw_addr.length); + + log_link_debug(link, "Gained new hardware address: %s", HW_ADDR_TO_STR(&hw_addr)); + + r = ipv4ll_update_mac(link); + if (r < 0) + return log_link_warning_errno(link, r, "Could not update MAC address in IPv4LL client: %m"); + + r = dhcp4_update_mac(link); + if (r < 0) + return log_link_warning_errno(link, r, "Could not update MAC address in DHCP client: %m"); + + r = dhcp6_update_mac(link); + if (r < 0) + return log_link_warning_errno(link, r, "Could not update MAC address in DHCPv6 client: %m"); + + r = radv_update_mac(link); + if (r < 0) + return log_link_warning_errno(link, r, "Could not update MAC address for Router Advertisement: %m"); + + if (link->ndisc) { + r = sd_ndisc_set_mac(link->ndisc, &link->hw_addr.addr.ether); + if (r < 0) + return log_link_warning_errno(link, r, "Could not update MAC for NDisc: %m"); + } + + r = ipv4_dad_update_mac(link); + if (r < 0) + return log_link_warning_errno(link, r, "Could not update MAC address in IPv4 ACD client: %m"); + } + + old_master = link->master_ifindex; + (void) sd_netlink_message_read_u32(m, IFLA_MASTER, (uint32_t *) &link->master_ifindex); + + link_was_admin_up = link->flags & IFF_UP; + had_carrier = link_has_carrier(link); + + r = link_update_flags(link, m, old_master != link->master_ifindex); + if (r < 0) + return r; + + if (!link_was_admin_up && (link->flags & IFF_UP)) { + log_link_info(link, "Link UP"); + + r = link_admin_state_up(link); + if (r < 0) + return r; + } else if (link_was_admin_up && !(link->flags & IFF_UP)) + log_link_info(link, "Link DOWN"); + + r = link_update_lldp(link); + if (r < 0) + return r; + + carrier_gained = !had_carrier && link_has_carrier(link); + carrier_lost = had_carrier && !link_has_carrier(link); + + if (carrier_gained) { + log_link_info(link, "Gained carrier"); + + r = link_carrier_gained(link); + if (r < 0) + return r; + } else if (carrier_lost) { + log_link_info(link, "Lost carrier"); + + r = link_carrier_lost(link); + if (r < 0) + return r; + } + + return 0; +} + +static void print_link_hashmap(FILE *f, const char *prefix, Hashmap* h) { + bool space = false; + Link *link; + + assert(f); + assert(prefix); + + if (hashmap_isempty(h)) + return; + + fputs(prefix, f); + HASHMAP_FOREACH(link, h) { + if (space) + fputc(' ', f); + + fprintf(f, "%i", link->ifindex); + space = true; + } + + fputc('\n', f); +} + +static void link_save_dns(Link *link, FILE *f, struct in_addr_full **dns, unsigned n_dns, bool *space) { + for (unsigned j = 0; j < n_dns; j++) { + const char *str; + + if (dns[j]->ifindex != 0 && dns[j]->ifindex != link->ifindex) + continue; + + str = in_addr_full_to_string(dns[j]); + if (!str) + continue; + + if (*space) + fputc(' ', f); + fputs(str, f); + *space = true; + } +} + +static void serialize_addresses( + FILE *f, + const char *lvalue, + bool *space, + char **addresses, + sd_dhcp_lease *lease, + bool conditional, + sd_dhcp_lease_server_type what, + sd_dhcp6_lease *lease6, + bool conditional6, + int (*lease6_get_addr)(sd_dhcp6_lease*, const struct in6_addr**), + int (*lease6_get_fqdn)(sd_dhcp6_lease*, char ***)) { + int r; + + bool _space = false; + if (!space) + space = &_space; + + if (lvalue) + fprintf(f, "%s=", lvalue); + fputstrv(f, addresses, NULL, space); + + if (lease && conditional) { + const struct in_addr *lease_addresses; + + r = sd_dhcp_lease_get_servers(lease, what, &lease_addresses); + if (r > 0) + serialize_in_addrs(f, lease_addresses, r, space, in4_addr_is_non_local); + } + + if (lease6 && conditional6 && lease6_get_addr) { + const struct in6_addr *in6_addrs; + + r = lease6_get_addr(lease6, &in6_addrs); + if (r > 0) + serialize_in6_addrs(f, in6_addrs, r, space); + } + + if (lease6 && conditional6 && lease6_get_fqdn) { + char **in6_hosts; + + r = lease6_get_fqdn(lease6, &in6_hosts); + if (r > 0) + fputstrv(f, in6_hosts, NULL, space); + } + + if (lvalue) + fputc('\n', f); +} + +int link_save(Link *link) { + const char *admin_state, *oper_state, *carrier_state, *address_state; + _cleanup_free_ char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(link); + assert(link->state_file); + assert(link->lease_file); + assert(link->manager); + + if (link->state == LINK_STATE_LINGER) { + (void) unlink(link->state_file); + return 0; + } + + link_lldp_save(link); + + admin_state = link_state_to_string(link->state); + assert(admin_state); + + oper_state = link_operstate_to_string(link->operstate); + assert(oper_state); + + carrier_state = link_carrier_state_to_string(link->carrier_state); + assert(carrier_state); + + address_state = link_address_state_to_string(link->address_state); + assert(address_state); + + r = fopen_temporary(link->state_file, &f, &temp_path); + if (r < 0) + goto fail; + + (void) fchmod(fileno(f), 0644); + + fprintf(f, + "# This is private data. Do not parse.\n" + "ADMIN_STATE=%s\n" + "OPER_STATE=%s\n" + "CARRIER_STATE=%s\n" + "ADDRESS_STATE=%s\n", + admin_state, oper_state, carrier_state, address_state); + + if (link->network) { + char **dhcp6_domains = NULL, **dhcp_domains = NULL; + const char *dhcp_domainname = NULL, *p; + bool space; + + fprintf(f, "REQUIRED_FOR_ONLINE=%s\n", + yes_no(link->network->required_for_online)); + + LinkOperationalStateRange st = link->network->required_operstate_for_online; + fprintf(f, "REQUIRED_OPER_STATE_FOR_ONLINE=%s%s%s\n", + strempty(link_operstate_to_string(st.min)), + st.max != LINK_OPERSTATE_RANGE_DEFAULT.max ? ":" : "", + st.max != LINK_OPERSTATE_RANGE_DEFAULT.max ? strempty(link_operstate_to_string(st.max)) : ""); + + fprintf(f, "NETWORK_FILE=%s\n", link->network->filename); + + /************************************************************/ + + fputs("DNS=", f); + space = false; + if (link->n_dns != (unsigned) -1) + link_save_dns(link, f, link->dns, link->n_dns, &space); + else + link_save_dns(link, f, link->network->dns, link->network->n_dns, &space); + + serialize_addresses(f, NULL, &space, + NULL, + link->dhcp_lease, + link->network->dhcp_use_dns, + SD_DHCP_LEASE_DNS, + link->dhcp6_lease, + link->network->dhcp6_use_dns, + sd_dhcp6_lease_get_dns, + NULL); + + /* Make sure to flush out old entries before we use the NDisc data */ + ndisc_vacuum(link); + + if (link->network->ipv6_accept_ra_use_dns && link->ndisc_rdnss) { + NDiscRDNSS *dd; + + SET_FOREACH(dd, link->ndisc_rdnss) + serialize_in6_addrs(f, &dd->address, 1, &space); + } + + fputc('\n', f); + + /************************************************************/ + + serialize_addresses(f, "NTP", NULL, + link->ntp ?: link->network->ntp, + link->dhcp_lease, + link->network->dhcp_use_ntp, + SD_DHCP_LEASE_NTP, + link->dhcp6_lease, + link->network->dhcp6_use_ntp, + sd_dhcp6_lease_get_ntp_addrs, + sd_dhcp6_lease_get_ntp_fqdn); + + serialize_addresses(f, "SIP", NULL, + NULL, + link->dhcp_lease, + link->network->dhcp_use_sip, + SD_DHCP_LEASE_SIP, + NULL, false, NULL, NULL); + + /************************************************************/ + + if (link->network->dhcp_use_domains != DHCP_USE_DOMAINS_NO) { + if (link->dhcp_lease) { + (void) sd_dhcp_lease_get_domainname(link->dhcp_lease, &dhcp_domainname); + (void) sd_dhcp_lease_get_search_domains(link->dhcp_lease, &dhcp_domains); + } + if (link->dhcp6_lease) + (void) sd_dhcp6_lease_get_domains(link->dhcp6_lease, &dhcp6_domains); + } + + fputs("DOMAINS=", f); + space = false; + ORDERED_SET_FOREACH(p, link->search_domains ?: link->network->search_domains) + fputs_with_space(f, p, NULL, &space); + + if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES) { + if (dhcp_domainname) + fputs_with_space(f, dhcp_domainname, NULL, &space); + if (dhcp_domains) + fputstrv(f, dhcp_domains, NULL, &space); + if (dhcp6_domains) + fputstrv(f, dhcp6_domains, NULL, &space); + } + + if (link->network->ipv6_accept_ra_use_domains == DHCP_USE_DOMAINS_YES) { + NDiscDNSSL *dd; + + SET_FOREACH(dd, link->ndisc_dnssl) + fputs_with_space(f, NDISC_DNSSL_DOMAIN(dd), NULL, &space); + } + + fputc('\n', f); + + /************************************************************/ + + fputs("ROUTE_DOMAINS=", f); + space = false; + ORDERED_SET_FOREACH(p, link->route_domains ?: link->network->route_domains) + fputs_with_space(f, p, NULL, &space); + + if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_ROUTE) { + if (dhcp_domainname) + fputs_with_space(f, dhcp_domainname, NULL, &space); + if (dhcp_domains) + fputstrv(f, dhcp_domains, NULL, &space); + if (dhcp6_domains) + fputstrv(f, dhcp6_domains, NULL, &space); + } + + if (link->network->ipv6_accept_ra_use_domains == DHCP_USE_DOMAINS_ROUTE) { + NDiscDNSSL *dd; + + SET_FOREACH(dd, link->ndisc_dnssl) + fputs_with_space(f, NDISC_DNSSL_DOMAIN(dd), NULL, &space); + } + + fputc('\n', f); + + /************************************************************/ + + fprintf(f, "LLMNR=%s\n", + resolve_support_to_string(link->llmnr >= 0 ? link->llmnr : link->network->llmnr)); + + /************************************************************/ + + fprintf(f, "MDNS=%s\n", + resolve_support_to_string(link->mdns >= 0 ? link->mdns : link->network->mdns)); + + /************************************************************/ + + int dns_default_route = + link->dns_default_route >= 0 ? link->dns_default_route : + link->network->dns_default_route; + if (dns_default_route >= 0) + fprintf(f, "DNS_DEFAULT_ROUTE=%s\n", yes_no(dns_default_route)); + + /************************************************************/ + + DnsOverTlsMode dns_over_tls_mode = + link->dns_over_tls_mode != _DNS_OVER_TLS_MODE_INVALID ? link->dns_over_tls_mode : + link->network->dns_over_tls_mode; + if (dns_over_tls_mode != _DNS_OVER_TLS_MODE_INVALID) + fprintf(f, "DNS_OVER_TLS=%s\n", dns_over_tls_mode_to_string(dns_over_tls_mode)); + + /************************************************************/ + + DnssecMode dnssec_mode = + link->dnssec_mode != _DNSSEC_MODE_INVALID ? link->dnssec_mode : + link->network->dnssec_mode; + if (dnssec_mode != _DNSSEC_MODE_INVALID) + fprintf(f, "DNSSEC=%s\n", dnssec_mode_to_string(dnssec_mode)); + + /************************************************************/ + + Set *nta_anchors = link->dnssec_negative_trust_anchors; + if (set_isempty(nta_anchors)) + nta_anchors = link->network->dnssec_negative_trust_anchors; + + if (!set_isempty(nta_anchors)) { + const char *n; + + fputs("DNSSEC_NTA=", f); + space = false; + SET_FOREACH(n, nta_anchors) + fputs_with_space(f, n, NULL, &space); + fputc('\n', f); + } + + /************************************************************/ + + r = link_serialize_addresses(link, f); + if (r < 0) + goto fail; + + /************************************************************/ + + r = link_serialize_routes(link, f); + if (r < 0) + goto fail; + } + + print_link_hashmap(f, "CARRIER_BOUND_TO=", link->bound_to_links); + print_link_hashmap(f, "CARRIER_BOUND_BY=", link->bound_by_links); + + if (link->dhcp_lease) { + r = dhcp_lease_save(link->dhcp_lease, link->lease_file); + if (r < 0) + goto fail; + + fprintf(f, + "DHCP_LEASE=%s\n", + link->lease_file); + } else + (void) unlink(link->lease_file); + + r = link_serialize_ipv4ll(link, f); + if (r < 0) + goto fail; + + r = link_serialize_dhcp6_client(link, f); + if (r < 0) + goto fail; + + r = fflush_and_check(f); + if (r < 0) + goto fail; + + if (rename(temp_path, link->state_file) < 0) { + r = -errno; + goto fail; + } + + return 0; + +fail: + (void) unlink(link->state_file); + if (temp_path) + (void) unlink(temp_path); + + return log_link_error_errno(link, r, "Failed to save link data to %s: %m", link->state_file); +} + +/* The serialized state in /run is no longer up-to-date. */ +void link_dirty(Link *link) { + int r; + + assert(link); + + /* mark manager dirty as link is dirty */ + manager_dirty(link->manager); + + r = set_ensure_put(&link->manager->dirty_links, NULL, link); + if (r <= 0) + /* Ignore allocation errors and don't take another ref if the link was already dirty */ + return; + link_ref(link); +} + +/* The serialized state in /run is up-to-date */ +void link_clean(Link *link) { + assert(link); + assert(link->manager); + + link_unref(set_remove(link->manager->dirty_links, link)); +} + +int link_save_and_clean(Link *link) { + int r; + + r = link_save(link); + if (r < 0) + return r; + + link_clean(link); + return 0; +} + +static const char* const link_state_table[_LINK_STATE_MAX] = { + [LINK_STATE_PENDING] = "pending", + [LINK_STATE_INITIALIZED] = "initialized", + [LINK_STATE_CONFIGURING] = "configuring", + [LINK_STATE_CONFIGURED] = "configured", + [LINK_STATE_UNMANAGED] = "unmanaged", + [LINK_STATE_FAILED] = "failed", + [LINK_STATE_LINGER] = "linger", +}; + +DEFINE_STRING_TABLE_LOOKUP(link_state, LinkState); + +int log_link_message_full_errno(Link *link, sd_netlink_message *m, int level, int err, const char *msg) { + const char *err_msg = NULL; + + (void) sd_netlink_message_read_string(m, NLMSGERR_ATTR_MSG, &err_msg); + return log_link_full_errno(link, level, err, + "%s: %s%s%s%m", + msg, + strempty(err_msg), + err_msg && !endswith(err_msg, ".") ? "." : "", + err_msg ? " " : ""); +} diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h new file mode 100644 index 0000000..cd54192 --- /dev/null +++ b/src/network/networkd-link.h @@ -0,0 +1,249 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <endian.h> +#include <linux/nl80211.h> + +#include "sd-bus.h" +#include "sd-device.h" +#include "sd-dhcp-client.h" +#include "sd-dhcp-server.h" +#include "sd-dhcp6-client.h" +#include "sd-ipv4acd.h" +#include "sd-ipv4ll.h" +#include "sd-lldp.h" +#include "sd-ndisc.h" +#include "sd-radv.h" +#include "sd-netlink.h" + +#include "ether-addr-util.h" +#include "log-link.h" +#include "network-util.h" +#include "networkd-util.h" +#include "ordered-set.h" +#include "resolve-util.h" +#include "set.h" + +typedef enum LinkState { + LINK_STATE_PENDING, /* udev has not initialized the link */ + LINK_STATE_INITIALIZED, /* udev has initialized the link */ + LINK_STATE_CONFIGURING, /* configuring addresses, routes, etc. */ + LINK_STATE_CONFIGURED, /* everything is configured */ + LINK_STATE_UNMANAGED, /* Unmanaged=yes is set */ + LINK_STATE_FAILED, /* at least one configuration process failed */ + LINK_STATE_LINGER, /* RTM_DELLINK for the link has been received */ + _LINK_STATE_MAX, + _LINK_STATE_INVALID = -1 +} LinkState; + +typedef struct Manager Manager; +typedef struct Network Network; +typedef struct Address Address; +typedef struct DUID DUID; + +typedef struct Link { + Manager *manager; + + unsigned n_ref; + + int ifindex; + int master_ifindex; + char *ifname; + char **alternative_names; + char *kind; + unsigned short iftype; + char *state_file; + hw_addr_data hw_addr; + hw_addr_data bcast_addr; + struct ether_addr permanent_mac; + struct in6_addr ipv6ll_address; + uint32_t mtu; + sd_device *sd_device; + char *driver; + + /* wlan */ + enum nl80211_iftype wlan_iftype; + char *ssid; + struct ether_addr bssid; + + unsigned flags; + uint8_t kernel_operstate; + + Network *network; + + LinkState state; + LinkOperationalState operstate; + LinkCarrierState carrier_state; + LinkAddressState address_state; + + unsigned address_messages; + unsigned address_remove_messages; + unsigned address_label_messages; + unsigned neighbor_messages; + unsigned route_messages; + unsigned nexthop_messages; + unsigned routing_policy_rule_messages; + unsigned routing_policy_rule_remove_messages; + unsigned tc_messages; + unsigned sr_iov_messages; + unsigned enslaving; + unsigned bridge_mdb_messages; + + Set *addresses; + Set *addresses_foreign; + Set *pool_addresses; + Set *static_addresses; + Set *neighbors; + Set *neighbors_foreign; + Set *routes; + Set *routes_foreign; + Set *nexthops; + Set *nexthops_foreign; + + sd_dhcp_client *dhcp_client; + sd_dhcp_lease *dhcp_lease; + Address *dhcp_address, *dhcp_address_old; + Set *dhcp_routes, *dhcp_routes_old; + char *lease_file; + uint32_t original_mtu; + unsigned dhcp4_messages; + unsigned dhcp4_remove_messages; + sd_ipv4acd *dhcp_acd; + bool dhcp4_route_failed:1; + bool dhcp4_route_retrying:1; + bool dhcp4_configured:1; + bool dhcp4_address_bind:1; + + sd_ipv4ll *ipv4ll; + bool ipv4ll_address_configured:1; + + bool request_static_addresses:1; + bool addresses_configured:1; + bool addresses_ready:1; + bool neighbors_configured:1; + bool static_routes_configured:1; + bool static_nexthops_configured:1; + bool routing_policy_rules_configured:1; + bool tc_configured:1; + bool sr_iov_configured:1; + bool setting_mtu:1; + bool setting_genmode:1; + bool ipv6_mtu_set:1; + bool bridge_mdb_configured:1; + + sd_dhcp_server *dhcp_server; + + sd_ndisc *ndisc; + Set *ndisc_rdnss; + Set *ndisc_dnssl; + Set *ndisc_addresses; + Set *ndisc_routes; + unsigned ndisc_addresses_messages; + unsigned ndisc_routes_messages; + bool ndisc_addresses_configured:1; + bool ndisc_routes_configured:1; + + sd_radv *radv; + + sd_dhcp6_client *dhcp6_client; + sd_dhcp6_lease *dhcp6_lease; + Set *dhcp6_addresses, *dhcp6_addresses_old; + Set *dhcp6_routes, *dhcp6_routes_old; + Set *dhcp6_pd_addresses, *dhcp6_pd_addresses_old; + Set *dhcp6_pd_routes, *dhcp6_pd_routes_old; + unsigned dhcp6_address_messages; + unsigned dhcp6_route_messages; + unsigned dhcp6_pd_address_messages; + unsigned dhcp6_pd_route_messages; + bool dhcp6_address_configured:1; + bool dhcp6_route_configured:1; + bool dhcp6_pd_address_configured:1; + bool dhcp6_pd_route_configured:1; + bool dhcp6_pd_prefixes_assigned:1; + + /* This is about LLDP reception */ + sd_lldp *lldp; + char *lldp_file; + + /* This is about LLDP transmission */ + unsigned lldp_tx_fast; /* The LLDP txFast counter (See 802.1ab-2009, section 9.2.5.18) */ + sd_event_source *lldp_emit_event_source; + + Hashmap *bound_by_links; + Hashmap *bound_to_links; + Set *slaves; + + /* For speed meter */ + struct rtnl_link_stats64 stats_old, stats_new; + bool stats_updated; + + /* All kinds of DNS configuration the user configured via D-Bus */ + struct in_addr_full **dns; + unsigned n_dns; + OrderedSet *search_domains, *route_domains; + + int dns_default_route; + ResolveSupport llmnr; + ResolveSupport mdns; + DnssecMode dnssec_mode; + DnsOverTlsMode dns_over_tls_mode; + Set *dnssec_negative_trust_anchors; + + /* Similar, but NTP server configuration */ + char **ntp; +} Link; + +typedef int (*link_netlink_message_handler_t)(sd_netlink*, sd_netlink_message*, Link*); + +void link_ntp_settings_clear(Link *link); +void link_dns_settings_clear(Link *link); +Link *link_unref(Link *link); +Link *link_ref(Link *link); +DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_unref); +DEFINE_TRIVIAL_DESTRUCTOR(link_netlink_destroy_callback, Link, link_unref); + +int link_get(Manager *m, int ifindex, Link **ret); +int link_add(Manager *manager, sd_netlink_message *message, Link **ret); +void link_drop(Link *link); + +int link_down(Link *link, link_netlink_message_handler_t callback); + +void link_enter_failed(Link *link); +int link_initialized(Link *link, sd_device *device); + +void link_set_state(Link *link, LinkState state); +void link_check_ready(Link *link); + +void link_update_operstate(Link *link, bool also_update_bond_master); +int link_update(Link *link, sd_netlink_message *message); + +void link_dirty(Link *link); +void link_clean(Link *link); +int link_save(Link *link); +int link_save_and_clean(Link *link); + +int link_carrier_reset(Link *link); +bool link_has_carrier(Link *link); + +bool link_ipv6_enabled(Link *link); +bool link_ipv6ll_enabled(Link *link); +int link_ipv6ll_gained(Link *link, const struct in6_addr *address); + +int link_set_mtu(Link *link, uint32_t mtu); + +bool link_ipv4ll_enabled(Link *link, AddressFamily mask); + +int link_stop_engines(Link *link, bool may_keep_dhcp); + +const char* link_state_to_string(LinkState s) _const_; +LinkState link_state_from_string(const char *s) _pure_; + +int link_configure(Link *link); +int link_reconfigure(Link *link, bool force); + +int log_link_message_full_errno(Link *link, sd_netlink_message *m, int level, int err, const char *msg); +#define log_link_message_error_errno(link, m, err, msg) log_link_message_full_errno(link, m, LOG_ERR, err, msg) +#define log_link_message_warning_errno(link, m, err, msg) log_link_message_full_errno(link, m, LOG_WARNING, err, msg) +#define log_link_message_notice_errno(link, m, err, msg) log_link_message_full_errno(link, m, LOG_NOTICE, err, msg) +#define log_link_message_info_errno(link, m, err, msg) log_link_message_full_errno(link, m, LOG_INFO, err, msg) +#define log_link_message_debug_errno(link, m, err, msg) log_link_message_full_errno(link, m, LOG_DEBUG, err, msg) diff --git a/src/network/networkd-lldp-rx.c b/src/network/networkd-lldp-rx.c new file mode 100644 index 0000000..c22852f --- /dev/null +++ b/src/network/networkd-lldp-rx.c @@ -0,0 +1,205 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <net/if.h> +#include <net/if_arp.h> +#include <unistd.h> + +#include "fd-util.h" +#include "fileio.h" +#include "networkd-link.h" +#include "networkd-lldp-rx.h" +#include "networkd-lldp-tx.h" +#include "networkd-manager.h" +#include "networkd-network.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "tmpfile-util.h" + +DEFINE_CONFIG_PARSE_ENUM(config_parse_lldp_mode, lldp_mode, LLDPMode, "Failed to parse LLDP= setting."); + +static const char* const lldp_mode_table[_LLDP_MODE_MAX] = { + [LLDP_MODE_NO] = "no", + [LLDP_MODE_YES] = "yes", + [LLDP_MODE_ROUTERS_ONLY] = "routers-only", +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(lldp_mode, LLDPMode, LLDP_MODE_YES); + +static bool link_lldp_rx_enabled(Link *link) { + assert(link); + + if (link->flags & IFF_LOOPBACK) + return false; + + if (link->iftype != ARPHRD_ETHER) + return false; + + if (!link->network) + return false; + + /* LLDP should be handled on bridge and bond slaves as those have a direct connection to their peers, + * not on the bridge/bond master. Linux doesn't even (by default) forward lldp packets to the bridge + * master.*/ + if (link->kind && STR_IN_SET(link->kind, "bridge", "bond")) + return false; + + return link->network->lldp_mode != LLDP_MODE_NO; +} + +static void lldp_handler(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n, void *userdata) { + Link *link = userdata; + int r; + + assert(link); + + (void) link_lldp_save(link); + + if (link_lldp_emit_enabled(link) && event == SD_LLDP_EVENT_ADDED) { + /* If we received information about a new neighbor, restart the LLDP "fast" logic */ + + log_link_debug(link, "Received LLDP datagram from previously unknown neighbor, restarting 'fast' LLDP transmission."); + + r = link_lldp_emit_start(link); + if (r < 0) + log_link_warning_errno(link, r, "Failed to restart LLDP transmission: %m"); + } +} + +int link_lldp_rx_configure(Link *link) { + int r; + + if (!link_lldp_rx_enabled(link)) + return 0; + + if (!link->lldp) { + r = sd_lldp_new(&link->lldp); + if (r < 0) + return r; + + r = sd_lldp_attach_event(link->lldp, link->manager->event, 0); + if (r < 0) + return r; + } + + r = sd_lldp_set_ifindex(link->lldp, link->ifindex); + if (r < 0) + return r; + + r = sd_lldp_match_capabilities(link->lldp, + link->network->lldp_mode == LLDP_MODE_ROUTERS_ONLY ? + SD_LLDP_SYSTEM_CAPABILITIES_ALL_ROUTERS : + SD_LLDP_SYSTEM_CAPABILITIES_ALL); + if (r < 0) + return r; + + r = sd_lldp_set_filter_address(link->lldp, &link->hw_addr.addr.ether); + if (r < 0) + return r; + + r = sd_lldp_set_callback(link->lldp, lldp_handler, link); + if (r < 0) + return r; + + r = link_update_lldp(link); + if (r < 0) + return r; + + return 0; +} + +int link_update_lldp(Link *link) { + int r; + + assert(link); + + if (!link->lldp) + return 0; + + if (link->flags & IFF_UP) { + r = sd_lldp_start(link->lldp); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to start LLDP: %m"); + if (r > 0) + log_link_debug(link, "Started LLDP."); + } else { + r = sd_lldp_stop(link->lldp); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to stop LLDP: %m"); + if (r > 0) + log_link_debug(link, "Stopped LLDP."); + } + + return r; +} + +int link_lldp_save(Link *link) { + _cleanup_free_ char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; + sd_lldp_neighbor **l = NULL; + int n = 0, r, i; + + assert(link); + assert(link->lldp_file); + + if (!link->lldp) { + (void) unlink(link->lldp_file); + return 0; + } + + r = sd_lldp_get_neighbors(link->lldp, &l); + if (r < 0) + goto finish; + if (r == 0) { + (void) unlink(link->lldp_file); + goto finish; + } + + n = r; + + r = fopen_temporary(link->lldp_file, &f, &temp_path); + if (r < 0) + goto finish; + + fchmod(fileno(f), 0644); + + for (i = 0; i < n; i++) { + const void *p; + le64_t u; + size_t sz; + + r = sd_lldp_neighbor_get_raw(l[i], &p, &sz); + if (r < 0) + goto finish; + + u = htole64(sz); + (void) fwrite(&u, 1, sizeof(u), f); + (void) fwrite(p, 1, sz, f); + } + + r = fflush_and_check(f); + if (r < 0) + goto finish; + + if (rename(temp_path, link->lldp_file) < 0) { + r = -errno; + goto finish; + } + +finish: + if (r < 0) { + (void) unlink(link->lldp_file); + if (temp_path) + (void) unlink(temp_path); + + log_link_error_errno(link, r, "Failed to save LLDP data to %s: %m", link->lldp_file); + } + + if (l) { + for (i = 0; i < n; i++) + sd_lldp_neighbor_unref(l[i]); + free(l); + } + + return r; +} diff --git a/src/network/networkd-lldp-rx.h b/src/network/networkd-lldp-rx.h new file mode 100644 index 0000000..78c5228 --- /dev/null +++ b/src/network/networkd-lldp-rx.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "conf-parser.h" + +typedef struct Link Link; + +typedef enum LLDPMode { + LLDP_MODE_NO = 0, + LLDP_MODE_YES = 1, + LLDP_MODE_ROUTERS_ONLY = 2, + _LLDP_MODE_MAX, + _LLDP_MODE_INVALID = -1, +} LLDPMode; + +int link_lldp_rx_configure(Link *link); +int link_update_lldp(Link *link); +int link_lldp_save(Link *link); + +const char* lldp_mode_to_string(LLDPMode m) _const_; +LLDPMode lldp_mode_from_string(const char *s) _pure_; + +CONFIG_PARSER_PROTOTYPE(config_parse_lldp_mode); diff --git a/src/network/networkd-lldp-tx.c b/src/network/networkd-lldp-tx.c new file mode 100644 index 0000000..b03d948 --- /dev/null +++ b/src/network/networkd-lldp-tx.c @@ -0,0 +1,493 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <endian.h> +#include <inttypes.h> +#include <net/if.h> +#include <net/if_arp.h> + +#include "alloc-util.h" +#include "env-file.h" +#include "escape.h" +#include "fd-util.h" +#include "hostname-util.h" +#include "missing_network.h" +#include "networkd-link.h" +#include "networkd-lldp-tx.h" +#include "networkd-manager.h" +#include "parse-util.h" +#include "random-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "strv.h" +#include "unaligned.h" +#include "web-util.h" + +/* The LLDP spec calls this "txFastInit", see 9.2.5.19 */ +#define LLDP_TX_FAST_INIT 4U + +/* The LLDP spec calls this "msgTxHold", see 9.2.5.6 */ +#define LLDP_TX_HOLD 4U + +/* The jitter range to add, see 9.2.2. */ +#define LLDP_JITTER_USEC (400U * USEC_PER_MSEC) + +/* The LLDP spec calls this msgTxInterval, but we subtract half the jitter off it. */ +#define LLDP_TX_INTERVAL_USEC (30U * USEC_PER_SEC - LLDP_JITTER_USEC / 2) + +/* The LLDP spec calls this msgFastTx, but we subtract half the jitter off it. */ +#define LLDP_FAST_TX_USEC (1U * USEC_PER_SEC - LLDP_JITTER_USEC / 2) + +static const struct ether_addr lldp_multicast_addr[_LLDP_EMIT_MAX] = { + [LLDP_EMIT_NEAREST_BRIDGE] = {{ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e }}, + [LLDP_EMIT_NON_TPMR_BRIDGE] = {{ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03 }}, + [LLDP_EMIT_CUSTOMER_BRIDGE] = {{ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 }}, +}; + +bool link_lldp_emit_enabled(Link *link) { + assert(link); + + if (link->flags & IFF_LOOPBACK) + return false; + + if (link->iftype != ARPHRD_ETHER) + return false; + + if (!link->network) + return false; + + if (link->kind && STR_IN_SET(link->kind, "bridge", "bond")) + return false; + + return link->network->lldp_emit != LLDP_EMIT_NO; +} + +static int lldp_write_tlv_header(uint8_t **p, uint8_t id, size_t sz) { + assert(p); + + if (id > 127) + return -EBADMSG; + if (sz > 511) + return -ENOBUFS; + + (*p)[0] = (id << 1) | !!(sz & 256); + (*p)[1] = sz & 255; + + *p = *p + 2; + return 0; +} + +static int lldp_make_packet( + LLDPEmit mode, + const struct ether_addr *hwaddr, + const char *machine_id, + const char *ifname, + uint16_t ttl, + const char *port_description, + const char *hostname, + const char *pretty_hostname, + uint16_t system_capabilities, + uint16_t enabled_capabilities, + char *mud, + void **ret, size_t *sz) { + + size_t machine_id_length, ifname_length, port_description_length = 0, hostname_length = 0, + pretty_hostname_length = 0, mud_length = 0; + _cleanup_free_ void *packet = NULL; + struct ether_header *h; + uint8_t *p; + size_t l; + int r; + + assert(mode > LLDP_EMIT_NO); + assert(mode < _LLDP_EMIT_MAX); + assert(hwaddr); + assert(machine_id); + assert(ifname); + assert(ret); + assert(sz); + + machine_id_length = strlen(machine_id); + ifname_length = strlen(ifname); + + if (port_description) + port_description_length = strlen(port_description); + + if (hostname) + hostname_length = strlen(hostname); + + if (pretty_hostname) + pretty_hostname_length = strlen(pretty_hostname); + + if (mud) + mud_length = strlen(mud); + + l = sizeof(struct ether_header) + + /* Chassis ID */ + 2 + 1 + machine_id_length + + /* Port ID */ + 2 + 1 + ifname_length + + /* TTL */ + 2 + 2 + + /* System Capabilities */ + 2 + 4 + + /* End */ + 2; + + /* Port Description */ + if (port_description) + l += 2 + port_description_length; + + /* System Name */ + if (hostname) + l += 2 + hostname_length; + + /* System Description */ + if (pretty_hostname) + l += 2 + pretty_hostname_length; + + /* MUD URL */ + if (mud) + l += 2 + sizeof(SD_LLDP_OUI_MUD) + 1 + mud_length; + + packet = malloc(l); + if (!packet) + return -ENOMEM; + + h = (struct ether_header*) packet; + h->ether_type = htobe16(ETHERTYPE_LLDP); + memcpy(h->ether_dhost, lldp_multicast_addr + mode, ETH_ALEN); + memcpy(h->ether_shost, hwaddr, ETH_ALEN); + + p = (uint8_t*) packet + sizeof(struct ether_header); + + r = lldp_write_tlv_header(&p, SD_LLDP_TYPE_CHASSIS_ID, 1 + machine_id_length); + if (r < 0) + return r; + *(p++) = SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED; + p = mempcpy(p, machine_id, machine_id_length); + + r = lldp_write_tlv_header(&p, SD_LLDP_TYPE_PORT_ID, 1 + ifname_length); + if (r < 0) + return r; + *(p++) = SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME; + p = mempcpy(p, ifname, ifname_length); + + r = lldp_write_tlv_header(&p, SD_LLDP_TYPE_TTL, 2); + if (r < 0) + return r; + unaligned_write_be16(p, ttl); + p += 2; + + if (port_description) { + r = lldp_write_tlv_header(&p, SD_LLDP_TYPE_PORT_DESCRIPTION, port_description_length); + if (r < 0) + return r; + p = mempcpy(p, port_description, port_description_length); + } + + if (hostname) { + r = lldp_write_tlv_header(&p, SD_LLDP_TYPE_SYSTEM_NAME, hostname_length); + if (r < 0) + return r; + p = mempcpy(p, hostname, hostname_length); + } + + if (pretty_hostname) { + r = lldp_write_tlv_header(&p, SD_LLDP_TYPE_SYSTEM_DESCRIPTION, pretty_hostname_length); + if (r < 0) + return r; + p = mempcpy(p, pretty_hostname, pretty_hostname_length); + } + + if (mud) { + uint8_t oui_mud[sizeof(SD_LLDP_OUI_MUD)] = {0x00, 0x00, 0x5E}; + /* + * +--------+--------+----------+---------+-------------- + * |TLV Type| len | OUI |subtype | MUDString + * | =127 | |= 00 00 5E| = 1 | + * |(7 bits)|(9 bits)|(3 octets)|(1 octet)|(1-255 octets) + * +--------+--------+----------+---------+-------------- + * where: + + * o TLV Type = 127 indicates a vendor-specific TLV + * o len = indicates the TLV string length + * o OUI = 00 00 5E is the organizationally unique identifier of IANA + * o subtype = 1 (as assigned by IANA for the MUDstring) + * o MUDstring = the length MUST NOT exceed 255 octets + */ + + r = lldp_write_tlv_header(&p, SD_LLDP_TYPE_PRIVATE, sizeof(SD_LLDP_OUI_MUD) + 1 + mud_length); + if (r < 0) + return r; + + p = mempcpy(p, &oui_mud, sizeof(SD_LLDP_OUI_MUD)); + *(p++) = SD_LLDP_OUI_SUBTYPE_MUD_USAGE_DESCRIPTION; + p = mempcpy(p, mud, mud_length); + } + + r = lldp_write_tlv_header(&p, SD_LLDP_TYPE_SYSTEM_CAPABILITIES, 4); + if (r < 0) + return r; + unaligned_write_be16(p, system_capabilities); + p += 2; + unaligned_write_be16(p, enabled_capabilities); + p += 2; + + r = lldp_write_tlv_header(&p, SD_LLDP_TYPE_END, 0); + if (r < 0) + return r; + + assert(p == (uint8_t*) packet + l); + + *ret = TAKE_PTR(packet); + *sz = l; + + return 0; +} + +static int lldp_send_packet( + int ifindex, + const struct ether_addr *address, + const void *packet, + size_t packet_size) { + + union sockaddr_union sa = { + .ll.sll_family = AF_PACKET, + .ll.sll_protocol = htobe16(ETHERTYPE_LLDP), + .ll.sll_ifindex = ifindex, + .ll.sll_halen = ETH_ALEN, + }; + + _cleanup_close_ int fd = -1; + ssize_t l; + + assert(ifindex > 0); + assert(address); + assert(packet || packet_size <= 0); + + memcpy(sa.ll.sll_addr, address, ETH_ALEN); + + fd = socket(AF_PACKET, SOCK_RAW|SOCK_CLOEXEC, IPPROTO_RAW); + if (fd < 0) + return -errno; + + l = sendto(fd, packet, packet_size, MSG_NOSIGNAL, &sa.sa, sizeof(sa.ll)); + if (l < 0) + return -errno; + + if ((size_t) l != packet_size) + return -EIO; + + return 0; +} + +static int link_send_lldp(Link *link) { + char machine_id_string[SD_ID128_STRING_MAX]; + _cleanup_free_ char *hostname = NULL, *pretty_hostname = NULL; + _cleanup_free_ void *packet = NULL; + size_t packet_size = 0; + sd_id128_t machine_id; + uint16_t caps; + usec_t ttl; + int r; + + assert(link); + + if (!link->network || link->network->lldp_emit == LLDP_EMIT_NO) + return 0; + + assert(link->network->lldp_emit < _LLDP_EMIT_MAX); + + r = sd_id128_get_machine(&machine_id); + if (r < 0) + return r; + + (void) gethostname_strict(&hostname); + (void) parse_env_file(NULL, "/etc/machine-info", "PRETTY_HOSTNAME", &pretty_hostname); + + assert_cc(LLDP_TX_INTERVAL_USEC * LLDP_TX_HOLD + 1 <= (UINT16_MAX - 1) * USEC_PER_SEC); + ttl = DIV_ROUND_UP(LLDP_TX_INTERVAL_USEC * LLDP_TX_HOLD + 1, USEC_PER_SEC); + + caps = (link->network && link->network->ip_forward != ADDRESS_FAMILY_NO) ? + SD_LLDP_SYSTEM_CAPABILITIES_ROUTER : + SD_LLDP_SYSTEM_CAPABILITIES_STATION; + + r = lldp_make_packet(link->network->lldp_emit, + &link->hw_addr.addr.ether, + sd_id128_to_string(machine_id, machine_id_string), + link->ifname, + (uint16_t) ttl, + link->network ? link->network->description : NULL, + hostname, + pretty_hostname, + SD_LLDP_SYSTEM_CAPABILITIES_STATION|SD_LLDP_SYSTEM_CAPABILITIES_BRIDGE|SD_LLDP_SYSTEM_CAPABILITIES_ROUTER, + caps, + link->network ? link->network->lldp_mud : NULL, + &packet, &packet_size); + if (r < 0) + return r; + + return lldp_send_packet(link->ifindex, lldp_multicast_addr + link->network->lldp_emit, packet, packet_size); +} + +static int on_lldp_timer(sd_event_source *s, usec_t t, void *userdata) { + Link *link = userdata; + usec_t delay; + int r; + + assert(s); + assert(userdata); + + log_link_debug(link, "Sending LLDP packet..."); + + r = link_send_lldp(link); + if (r < 0) + log_link_debug_errno(link, r, "Failed to send LLDP packet, ignoring: %m"); + + if (link->lldp_tx_fast > 0) + link->lldp_tx_fast--; + + delay = link->lldp_tx_fast > 0 ? LLDP_FAST_TX_USEC : LLDP_TX_INTERVAL_USEC; + delay = usec_add(delay, (usec_t) random_u64() % LLDP_JITTER_USEC); + + r = sd_event_source_set_time_relative(s, delay); + if (r < 0) + return log_link_error_errno(link, r, "Failed to restart LLDP timer: %m"); + + r = sd_event_source_set_enabled(s, SD_EVENT_ONESHOT); + if (r < 0) + return log_link_error_errno(link, r, "Failed to enable LLDP timer: %m"); + + return 0; +} + +int link_lldp_emit_start(Link *link) { + usec_t next; + int r; + + assert(link); + + if (!link_lldp_emit_enabled(link)) { + link_lldp_emit_stop(link); + return 0; + } + + /* Starts the LLDP transmission in "fast" mode. If it is already started, turns "fast" mode back on again. */ + + link->lldp_tx_fast = LLDP_TX_FAST_INIT; + + next = usec_add(usec_add(now(clock_boottime_or_monotonic()), LLDP_FAST_TX_USEC), + (usec_t) random_u64() % LLDP_JITTER_USEC); + + if (link->lldp_emit_event_source) { + usec_t old; + + /* Lower the timeout, maybe */ + r = sd_event_source_get_time(link->lldp_emit_event_source, &old); + if (r < 0) + return r; + + if (old <= next) + return 0; + + return sd_event_source_set_time(link->lldp_emit_event_source, next); + } else { + r = sd_event_add_time( + link->manager->event, + &link->lldp_emit_event_source, + clock_boottime_or_monotonic(), + next, + 0, + on_lldp_timer, + link); + if (r < 0) + return r; + + (void) sd_event_source_set_description(link->lldp_emit_event_source, "lldp-tx"); + } + + return 0; +} + +void link_lldp_emit_stop(Link *link) { + assert(link); + + link->lldp_emit_event_source = sd_event_source_unref(link->lldp_emit_event_source); +} + +int config_parse_lldp_emit( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + LLDPEmit *emit = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) + *emit = LLDP_EMIT_NO; + else if (streq(rvalue, "nearest-bridge")) + *emit = LLDP_EMIT_NEAREST_BRIDGE; + else if (streq(rvalue, "non-tpmr-bridge")) + *emit = LLDP_EMIT_NON_TPMR_BRIDGE; + else if (streq(rvalue, "customer-bridge")) + *emit = LLDP_EMIT_CUSTOMER_BRIDGE; + else { + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Failed to parse LLDP emission setting, ignoring: %s", rvalue); + return 0; + } + + *emit = r ? LLDP_EMIT_NEAREST_BRIDGE : LLDP_EMIT_NO; + } + + return 0; +} + +int config_parse_lldp_mud( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_free_ char *unescaped = NULL; + Network *n = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = cunescape(rvalue, 0, &unescaped); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to Failed to unescape LLDP MUD URL, ignoring: %s", rvalue); + return 0; + } + + if (!http_url_is_valid(unescaped) || strlen(unescaped) > 255) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Failed to parse LLDP MUD URL '%s', ignoring: %m", rvalue); + + return 0; + } + + return free_and_replace(n->lldp_mud, unescaped); +} diff --git a/src/network/networkd-lldp-tx.h b/src/network/networkd-lldp-tx.h new file mode 100644 index 0000000..aae30cb --- /dev/null +++ b/src/network/networkd-lldp-tx.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <stdbool.h> + +#include "conf-parser.h" + +typedef struct Link Link; + +typedef enum LLDPEmit { + LLDP_EMIT_NO, + LLDP_EMIT_NEAREST_BRIDGE, + LLDP_EMIT_NON_TPMR_BRIDGE, + LLDP_EMIT_CUSTOMER_BRIDGE, + _LLDP_EMIT_MAX, +} LLDPEmit; + +bool link_lldp_emit_enabled(Link *link); +int link_lldp_emit_start(Link *link); +void link_lldp_emit_stop(Link *link); + +CONFIG_PARSER_PROTOTYPE(config_parse_lldp_emit); +CONFIG_PARSER_PROTOTYPE(config_parse_lldp_mud); diff --git a/src/network/networkd-manager-bus.c b/src/network/networkd-manager-bus.c new file mode 100644 index 0000000..a0ac8b5 --- /dev/null +++ b/src/network/networkd-manager-bus.c @@ -0,0 +1,274 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <net/if.h> +#include <netinet/in.h> +#include <sys/capability.h> + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-message-util.h" +#include "bus-polkit.h" +#include "networkd-link-bus.h" +#include "networkd-link.h" +#include "networkd-manager-bus.h" +#include "networkd-manager.h" +#include "path-util.h" +#include "socket-netlink.h" +#include "strv.h" +#include "user-util.h" + +static int method_list_links(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + Manager *manager = userdata; + Link *link; + int r; + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(iso)"); + if (r < 0) + return r; + + HASHMAP_FOREACH(link, manager->links) { + _cleanup_free_ char *path = NULL; + + path = link_bus_path(link); + if (!path) + return -ENOMEM; + + r = sd_bus_message_append( + reply, "(iso)", + link->ifindex, + link->ifname, + empty_to_root(path)); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +static int method_get_link_by_name(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *path = NULL; + Manager *manager = userdata; + const char *name; + int index, r; + Link *link; + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + index = resolve_ifname(&manager->rtnl, name); + if (index < 0) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_LINK, "Link %s cannot be resolved", name); + + link = hashmap_get(manager->links, INT_TO_PTR(index)); + if (!link) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_LINK, "Link %s not known", name); + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + path = link_bus_path(link); + if (!path) + return -ENOMEM; + + r = sd_bus_message_append(reply, "io", link->ifindex, empty_to_root(path)); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +static int method_get_link_by_index(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *path = NULL; + Manager *manager = userdata; + int ifindex, r; + Link *link; + + r = bus_message_read_ifindex(message, error, &ifindex); + if (r < 0) + return r; + + link = hashmap_get(manager->links, INT_TO_PTR(ifindex)); + if (!link) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_LINK, "Link %i not known", ifindex); + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + path = link_bus_path(link); + if (!path) + return -ENOMEM; + + r = sd_bus_message_append(reply, "so", link->ifname, empty_to_root(path)); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +static int call_link_method(Manager *m, sd_bus_message *message, sd_bus_message_handler_t handler, sd_bus_error *error) { + int ifindex, r; + Link *l; + + assert(m); + assert(message); + assert(handler); + + r = bus_message_read_ifindex(message, error, &ifindex); + if (r < 0) + return r; + + l = hashmap_get(m->links, INT_TO_PTR(ifindex)); + if (!l) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_LINK, "Link %i not known", ifindex); + + return handler(message, l, error); +} + +static int bus_method_set_link_ntp_servers(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_ntp_servers, error); +} + +static int bus_method_set_link_dns_servers(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_dns_servers, error); +} + +static int bus_method_set_link_dns_servers_ex(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_dns_servers_ex, error); +} + +static int bus_method_set_link_domains(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_domains, error); +} + +static int bus_method_set_link_default_route(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_default_route, error); +} + +static int bus_method_set_link_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_llmnr, error); +} + +static int bus_method_set_link_mdns(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_mdns, error); +} + +static int bus_method_set_link_dns_over_tls(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_dns_over_tls, error); +} + +static int bus_method_set_link_dnssec(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_dnssec, error); +} + +static int bus_method_set_link_dnssec_negative_trust_anchors(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_dnssec_negative_trust_anchors, error); +} + +static int bus_method_revert_link_ntp(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_revert_ntp, error); +} + +static int bus_method_revert_link_dns(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_revert_dns, error); +} + +static int bus_method_renew_link(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_renew, error); +} + +static int bus_method_force_renew_link(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_force_renew, error); +} + +static int bus_method_reconfigure_link(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_reconfigure, error); +} + +static int bus_method_reload(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *manager = userdata; + Link *link; + int r; + + r = bus_verify_polkit_async(message, CAP_NET_ADMIN, + "org.freedesktop.network1.reload", + NULL, true, UID_INVALID, + &manager->polkit_registry, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Polkit will call us back */ + + r = netdev_load(manager, true); + if (r < 0) + return r; + + r = network_reload(manager); + if (r < 0) + return r; + + HASHMAP_FOREACH(link, manager->links) { + r = link_reconfigure(link, false); + if (r < 0) + return r; + } + + return sd_bus_reply_method_return(message, NULL); +} + +const sd_bus_vtable manager_vtable[] = { + SD_BUS_VTABLE_START(0), + + SD_BUS_PROPERTY("OperationalState", "s", property_get_operational_state, offsetof(Manager, operational_state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("CarrierState", "s", property_get_carrier_state, offsetof(Manager, carrier_state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("AddressState", "s", property_get_address_state, offsetof(Manager, address_state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + + SD_BUS_METHOD("ListLinks", NULL, "a(iso)", method_list_links, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetLinkByName", "s", "io", method_get_link_by_name, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetLinkByIndex", "i", "so", method_get_link_by_index, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetLinkNTP", "ias", NULL, bus_method_set_link_ntp_servers, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetLinkDNS", "ia(iay)", NULL, bus_method_set_link_dns_servers, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetLinkDNSEx", "ia(iayqs)", NULL, bus_method_set_link_dns_servers_ex, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetLinkDomains", "ia(sb)", NULL, bus_method_set_link_domains, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetLinkDefaultRoute", "ib", NULL, bus_method_set_link_default_route, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetLinkLLMNR", "is", NULL, bus_method_set_link_llmnr, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetLinkMulticastDNS", "is", NULL, bus_method_set_link_mdns, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetLinkDNSOverTLS", "is", NULL, bus_method_set_link_dns_over_tls, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetLinkDNSSEC", "is", NULL, bus_method_set_link_dnssec, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetLinkDNSSECNegativeTrustAnchors", "ias", NULL, bus_method_set_link_dnssec_negative_trust_anchors, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("RevertLinkNTP", "i", NULL, bus_method_revert_link_ntp, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("RevertLinkDNS", "i", NULL, bus_method_revert_link_dns, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("RenewLink", "i", NULL, bus_method_renew_link, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ForceRenewLink", "i", NULL, bus_method_force_renew_link, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ReconfigureLink", "i", NULL, bus_method_reconfigure_link, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Reload", NULL, NULL, bus_method_reload, SD_BUS_VTABLE_UNPRIVILEGED), + + SD_BUS_VTABLE_END +}; + +int manager_send_changed_strv(Manager *manager, char **properties) { + assert(manager); + assert(properties); + + if (!manager->bus) + return 0; + + return sd_bus_emit_properties_changed_strv( + manager->bus, + "/org/freedesktop/network1", + "org.freedesktop.network1.Manager", + properties); +} diff --git a/src/network/networkd-manager-bus.h b/src/network/networkd-manager-bus.h new file mode 100644 index 0000000..08ddfbd --- /dev/null +++ b/src/network/networkd-manager-bus.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-bus.h" + +typedef struct Manager Manager; + +extern const sd_bus_vtable manager_vtable[]; + +int manager_send_changed_strv(Manager *m, char **properties); diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c new file mode 100644 index 0000000..19c3cc6 --- /dev/null +++ b/src/network/networkd-manager.c @@ -0,0 +1,1254 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <netinet/in.h> +#include <sys/socket.h> +#include <unistd.h> +#include <linux/if.h> +#include <linux/fib_rules.h> +#include <linux/nexthop.h> + +#include "sd-daemon.h" +#include "sd-netlink.h" + +#include "alloc-util.h" +#include "bus-log-control-api.h" +#include "bus-polkit.h" +#include "bus-util.h" +#include "conf-parser.h" +#include "def.h" +#include "device-private.h" +#include "device-util.h" +#include "dns-domain.h" +#include "fd-util.h" +#include "fileio.h" +#include "local-addresses.h" +#include "netlink-util.h" +#include "network-internal.h" +#include "networkd-address-pool.h" +#include "networkd-dhcp-server-bus.h" +#include "networkd-dhcp6.h" +#include "networkd-link-bus.h" +#include "networkd-manager-bus.h" +#include "networkd-manager.h" +#include "networkd-neighbor.h" +#include "networkd-network-bus.h" +#include "networkd-nexthop.h" +#include "networkd-routing-policy-rule.h" +#include "networkd-speed-meter.h" +#include "ordered-set.h" +#include "path-lookup.h" +#include "path-util.h" +#include "selinux-util.h" +#include "set.h" +#include "signal-util.h" +#include "stat-util.h" +#include "strv.h" +#include "sysctl-util.h" +#include "tmpfile-util.h" +#include "udev-util.h" + +/* use 128 MB for receive socket kernel queue. */ +#define RCVBUF_SIZE (128*1024*1024) + +static int manager_reset_all(Manager *m) { + Link *link; + int r; + + assert(m); + + HASHMAP_FOREACH(link, m->links) { + r = link_carrier_reset(link); + if (r < 0) + log_link_warning_errno(link, r, "Could not reset carrier: %m"); + } + + return 0; +} + +static int match_prepare_for_sleep(sd_bus_message *message, void *userdata, sd_bus_error *ret_error) { + Manager *m = userdata; + int b, r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + if (b) + return 0; + + log_debug("Coming back from suspend, resetting all connections..."); + + (void) manager_reset_all(m); + + return 0; +} + +static int on_connected(sd_bus_message *message, void *userdata, sd_bus_error *ret_error) { + Manager *m = userdata; + + assert(message); + assert(m); + + /* Did we get a timezone or transient hostname from DHCP while D-Bus wasn't up yet? */ + if (m->dynamic_hostname) + (void) manager_set_hostname(m, m->dynamic_hostname); + if (m->dynamic_timezone) + (void) manager_set_timezone(m, m->dynamic_timezone); + if (m->links_requesting_uuid) + (void) manager_request_product_uuid(m, NULL); + + return 0; +} + +int manager_connect_bus(Manager *m) { + int r; + + assert(m); + + if (m->bus) + return 0; + + r = bus_open_system_watch_bind_with_description(&m->bus, "bus-api-network"); + if (r < 0) + return log_error_errno(r, "Failed to connect to bus: %m"); + + r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/network1", "org.freedesktop.network1.Manager", manager_vtable, m); + if (r < 0) + return log_error_errno(r, "Failed to add manager object vtable: %m"); + + r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/network1/link", "org.freedesktop.network1.Link", link_vtable, link_object_find, m); + if (r < 0) + return log_error_errno(r, "Failed to add link object vtable: %m"); + + r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/network1/link", "org.freedesktop.network1.DHCPServer", dhcp_server_vtable, link_object_find, m); + if (r < 0) + return log_error_errno(r, "Failed to add link object vtable: %m"); + + r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/network1/link", link_node_enumerator, m); + if (r < 0) + return log_error_errno(r, "Failed to add link enumerator: %m"); + + r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/network1/network", "org.freedesktop.network1.Network", network_vtable, network_object_find, m); + if (r < 0) + return log_error_errno(r, "Failed to add network object vtable: %m"); + + r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/network1/network", network_node_enumerator, m); + if (r < 0) + return log_error_errno(r, "Failed to add network enumerator: %m"); + + r = bus_log_control_api_register(m->bus); + if (r < 0) + return r; + + r = sd_bus_request_name_async(m->bus, NULL, "org.freedesktop.network1", 0, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to request name: %m"); + + r = sd_bus_attach_event(m->bus, m->event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + r = sd_bus_match_signal_async( + m->bus, + NULL, + "org.freedesktop.DBus.Local", + NULL, + "org.freedesktop.DBus.Local", + "Connected", + on_connected, NULL, m); + if (r < 0) + return log_error_errno(r, "Failed to request match on Connected signal: %m"); + + r = sd_bus_match_signal_async( + m->bus, + NULL, + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "PrepareForSleep", + match_prepare_for_sleep, NULL, m); + if (r < 0) + log_warning_errno(r, "Failed to request match for PrepareForSleep, ignoring: %m"); + + return 0; +} + +static int manager_udev_process_link(sd_device_monitor *monitor, sd_device *device, void *userdata) { + Manager *m = userdata; + DeviceAction action; + Link *link = NULL; + int r, ifindex; + + assert(m); + assert(device); + + r = device_get_action(device, &action); + if (r < 0) { + log_device_debug_errno(device, r, "Failed to get udev action, ignoring device: %m"); + return 0; + } + + /* Ignore the "remove" uevent — let's remove a device only if rtnetlink says so. All other uevents + * are "positive" events in some form, i.e. inform us about a changed or new network interface, that + * still exists — and we are interested in that. */ + if (action == DEVICE_ACTION_REMOVE) + return 0; + + r = sd_device_get_ifindex(device, &ifindex); + if (r < 0) { + log_device_debug_errno(device, r, "Ignoring udev %s event for device without ifindex or with invalid ifindex: %m", + device_action_to_string(action)); + return 0; + } + + r = device_is_renaming(device); + if (r < 0) { + log_device_error_errno(device, r, "Failed to determine the device is renamed or not, ignoring '%s' uevent: %m", + device_action_to_string(action)); + return 0; + } + if (r > 0) { + log_device_debug(device, "Interface is under renaming, wait for the interface to be renamed."); + return 0; + } + + r = link_get(m, ifindex, &link); + if (r < 0) { + if (r != -ENODEV) + log_debug_errno(r, "Failed to get link from ifindex %i, ignoring: %m", ifindex); + return 0; + } + + (void) link_initialized(link, device); + + return 0; +} + +static int manager_connect_udev(Manager *m) { + int r; + + /* udev does not initialize devices inside containers, so we rely on them being already + * initialized before entering the container. */ + if (path_is_read_only_fs("/sys") > 0) + return 0; + + r = sd_device_monitor_new(&m->device_monitor); + if (r < 0) + return log_error_errno(r, "Failed to initialize device monitor: %m"); + + r = sd_device_monitor_set_receive_buffer_size(m->device_monitor, RCVBUF_SIZE); + if (r < 0) + log_warning_errno(r, "Failed to increase buffer size for device monitor, ignoring: %m"); + + r = sd_device_monitor_filter_add_match_subsystem_devtype(m->device_monitor, "net", NULL); + if (r < 0) + return log_error_errno(r, "Could not add device monitor filter: %m"); + + r = sd_device_monitor_attach_event(m->device_monitor, m->event); + if (r < 0) + return log_error_errno(r, "Failed to attach event to device monitor: %m"); + + r = sd_device_monitor_start(m->device_monitor, manager_udev_process_link, m); + if (r < 0) + return log_error_errno(r, "Failed to start device monitor: %m"); + + return 0; +} + +static int manager_rtnl_process_link(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) { + Link *link = NULL; + NetDev *netdev = NULL; + uint16_t type; + const char *name; + int r, ifindex; + + assert(rtnl); + assert(message); + assert(m); + + if (sd_netlink_message_is_error(message)) { + r = sd_netlink_message_get_errno(message); + if (r < 0) + log_message_warning_errno(message, r, "rtnl: Could not receive link message, ignoring"); + + return 0; + } + + r = sd_netlink_message_get_type(message, &type); + if (r < 0) { + log_warning_errno(r, "rtnl: Could not get message type, ignoring: %m"); + return 0; + } else if (!IN_SET(type, RTM_NEWLINK, RTM_DELLINK)) { + log_warning("rtnl: Received unexpected message type %u when processing link, ignoring.", type); + return 0; + } + + r = sd_rtnl_message_link_get_ifindex(message, &ifindex); + if (r < 0) { + log_warning_errno(r, "rtnl: Could not get ifindex from link message, ignoring: %m"); + return 0; + } else if (ifindex <= 0) { + log_warning("rtnl: received link message with invalid ifindex %d, ignoring.", ifindex); + return 0; + } + + r = sd_netlink_message_read_string(message, IFLA_IFNAME, &name); + if (r < 0) { + log_warning_errno(r, "rtnl: Received link message without ifname, ignoring: %m"); + return 0; + } + + (void) link_get(m, ifindex, &link); + (void) netdev_get(m, name, &netdev); + + switch (type) { + case RTM_NEWLINK: + if (!link) { + /* link is new, so add it */ + r = link_add(m, message, &link); + if (r < 0) { + log_warning_errno(r, "Could not process new link message, ignoring: %m"); + return 0; + } + } + + if (netdev) { + /* netdev exists, so make sure the ifindex matches */ + r = netdev_set_ifindex(netdev, message); + if (r < 0) { + log_warning_errno(r, "Could not process new link message for netdev, ignoring: %m"); + return 0; + } + } + + r = link_update(link, message); + if (r < 0) { + log_warning_errno(r, "Could not process link message, ignoring: %m"); + return 0; + } + + break; + + case RTM_DELLINK: + link_drop(link); + netdev_drop(netdev); + + break; + + default: + assert_not_reached("Received link message with invalid RTNL message type."); + } + + return 1; +} + +static int systemd_netlink_fd(void) { + int n, fd, rtnl_fd = -EINVAL; + + n = sd_listen_fds(true); + if (n <= 0) + return -EINVAL; + + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd ++) { + if (sd_is_socket(fd, AF_NETLINK, SOCK_RAW, -1) > 0) { + if (rtnl_fd >= 0) + return -EINVAL; + + rtnl_fd = fd; + } + } + + return rtnl_fd; +} + +static int manager_connect_genl(Manager *m) { + int r; + + assert(m); + + r = sd_genl_socket_open(&m->genl); + if (r < 0) + return r; + + r = sd_netlink_inc_rcvbuf(m->genl, RCVBUF_SIZE); + if (r < 0) + log_warning_errno(r, "Failed to increase receive buffer size for general netlink socket, ignoring: %m"); + + r = sd_netlink_attach_event(m->genl, m->event, 0); + if (r < 0) + return r; + + return 0; +} + +static int manager_connect_rtnl(Manager *m) { + int fd, r; + + assert(m); + + fd = systemd_netlink_fd(); + if (fd < 0) + r = sd_netlink_open(&m->rtnl); + else + r = sd_netlink_open_fd(&m->rtnl, fd); + if (r < 0) + return r; + + /* Bump receiver buffer, but only if we are not called via socket activation, as in that + * case systemd sets the receive buffer size for us, and the value in the .socket unit + * should take full effect. */ + if (fd < 0) { + r = sd_netlink_inc_rcvbuf(m->rtnl, RCVBUF_SIZE); + if (r < 0) + log_warning_errno(r, "Failed to increase receive buffer size for rtnl socket, ignoring: %m"); + } + + r = sd_netlink_attach_event(m->rtnl, m->event, 0); + if (r < 0) + return r; + + r = netlink_add_match(m->rtnl, NULL, RTM_NEWLINK, &manager_rtnl_process_link, NULL, m, "network-rtnl_process_link"); + if (r < 0) + return r; + + r = netlink_add_match(m->rtnl, NULL, RTM_DELLINK, &manager_rtnl_process_link, NULL, m, "network-rtnl_process_link"); + if (r < 0) + return r; + + r = netlink_add_match(m->rtnl, NULL, RTM_NEWADDR, &manager_rtnl_process_address, NULL, m, "network-rtnl_process_address"); + if (r < 0) + return r; + + r = netlink_add_match(m->rtnl, NULL, RTM_DELADDR, &manager_rtnl_process_address, NULL, m, "network-rtnl_process_address"); + if (r < 0) + return r; + + r = netlink_add_match(m->rtnl, NULL, RTM_NEWNEIGH, &manager_rtnl_process_neighbor, NULL, m, "network-rtnl_process_neighbor"); + if (r < 0) + return r; + + r = netlink_add_match(m->rtnl, NULL, RTM_DELNEIGH, &manager_rtnl_process_neighbor, NULL, m, "network-rtnl_process_neighbor"); + if (r < 0) + return r; + + r = netlink_add_match(m->rtnl, NULL, RTM_NEWROUTE, &manager_rtnl_process_route, NULL, m, "network-rtnl_process_route"); + if (r < 0) + return r; + + r = netlink_add_match(m->rtnl, NULL, RTM_DELROUTE, &manager_rtnl_process_route, NULL, m, "network-rtnl_process_route"); + if (r < 0) + return r; + + r = netlink_add_match(m->rtnl, NULL, RTM_NEWRULE, &manager_rtnl_process_rule, NULL, m, "network-rtnl_process_rule"); + if (r < 0) + return r; + + r = netlink_add_match(m->rtnl, NULL, RTM_DELRULE, &manager_rtnl_process_rule, NULL, m, "network-rtnl_process_rule"); + if (r < 0) + return r; + + r = netlink_add_match(m->rtnl, NULL, RTM_NEWNEXTHOP, &manager_rtnl_process_nexthop, NULL, m, "network-rtnl_process_nexthop"); + if (r < 0) + return r; + + r = netlink_add_match(m->rtnl, NULL, RTM_DELNEXTHOP, &manager_rtnl_process_nexthop, NULL, m, "network-rtnl_process_nexthop"); + if (r < 0) + return r; + + return 0; +} + +static int ordered_set_put_dns_server(OrderedSet *s, int ifindex, struct in_addr_full *dns) { + const char *p; + int r; + + assert(s); + assert(dns); + + if (dns->ifindex != 0 && dns->ifindex != ifindex) + return 0; + + p = in_addr_full_to_string(dns); + if (!p) + return 0; + + r = ordered_set_put_strdup(s, p); + if (r == -EEXIST) + return 0; + + return r; +} + +static int ordered_set_put_dns_servers(OrderedSet *s, int ifindex, struct in_addr_full **dns, unsigned n) { + int r, c = 0; + unsigned i; + + assert(s); + assert(dns || n == 0); + + for (i = 0; i < n; i++) { + r = ordered_set_put_dns_server(s, ifindex, dns[i]); + if (r < 0) + return r; + + c += r; + } + + return c; +} + +static int ordered_set_put_in4_addr(OrderedSet *s, const struct in_addr *address) { + char *p; + int r; + + assert(s); + assert(address); + + r = in_addr_to_string(AF_INET, (const union in_addr_union*) address, &p); + if (r < 0) + return r; + + r = ordered_set_consume(s, p); + if (r == -EEXIST) + return 0; + + return r; +} + +static int ordered_set_put_in4_addrv(OrderedSet *s, + const struct in_addr *addresses, + size_t n, + bool (*predicate)(const struct in_addr *addr)) { + int r, c = 0; + size_t i; + + assert(s); + assert(n == 0 || addresses); + + for (i = 0; i < n; i++) { + if (predicate && !predicate(&addresses[i])) + continue; + r = ordered_set_put_in4_addr(s, addresses+i); + if (r < 0) + return r; + + c += r; + } + + return c; +} + +static int manager_save(Manager *m) { + _cleanup_ordered_set_free_free_ OrderedSet *dns = NULL, *ntp = NULL, *sip = NULL, *search_domains = NULL, *route_domains = NULL; + const char *operstate_str, *carrier_state_str, *address_state_str; + LinkOperationalState operstate = LINK_OPERSTATE_OFF; + LinkCarrierState carrier_state = LINK_CARRIER_STATE_OFF; + LinkAddressState address_state = LINK_ADDRESS_STATE_OFF; + _cleanup_free_ char *temp_path = NULL; + _cleanup_strv_free_ char **p = NULL; + _cleanup_fclose_ FILE *f = NULL; + Link *link; + int r; + + assert(m); + assert(m->state_file); + + /* We add all NTP and DNS server to a set, to filter out duplicates */ + dns = ordered_set_new(&string_hash_ops); + if (!dns) + return -ENOMEM; + + ntp = ordered_set_new(&string_hash_ops); + if (!ntp) + return -ENOMEM; + + sip = ordered_set_new(&string_hash_ops); + if (!sip) + return -ENOMEM; + + search_domains = ordered_set_new(&dns_name_hash_ops); + if (!search_domains) + return -ENOMEM; + + route_domains = ordered_set_new(&dns_name_hash_ops); + if (!route_domains) + return -ENOMEM; + + HASHMAP_FOREACH(link, m->links) { + const struct in_addr *addresses; + + if (link->flags & IFF_LOOPBACK) + continue; + + if (link->operstate > operstate) + operstate = link->operstate; + + if (link->carrier_state > carrier_state) + carrier_state = link->carrier_state; + + if (link->address_state > address_state) + address_state = link->address_state; + + if (!link->network) + continue; + + /* First add the static configured entries */ + if (link->n_dns != (unsigned) -1) + r = ordered_set_put_dns_servers(dns, link->ifindex, link->dns, link->n_dns); + else + r = ordered_set_put_dns_servers(dns, link->ifindex, link->network->dns, link->network->n_dns); + if (r < 0) + return r; + + r = ordered_set_put_strdupv(ntp, link->ntp ?: link->network->ntp); + if (r < 0) + return r; + + r = ordered_set_put_string_set(search_domains, link->search_domains ?: link->network->search_domains); + if (r < 0) + return r; + + r = ordered_set_put_string_set(route_domains, link->route_domains ?: link->network->route_domains); + if (r < 0) + return r; + + if (!link->dhcp_lease) + continue; + + /* Secondly, add the entries acquired via DHCP */ + if (link->network->dhcp_use_dns) { + r = sd_dhcp_lease_get_dns(link->dhcp_lease, &addresses); + if (r > 0) { + r = ordered_set_put_in4_addrv(dns, addresses, r, in4_addr_is_non_local); + if (r < 0) + return r; + } else if (r < 0 && r != -ENODATA) + return r; + } + + if (link->network->dhcp_use_ntp) { + r = sd_dhcp_lease_get_ntp(link->dhcp_lease, &addresses); + if (r > 0) { + r = ordered_set_put_in4_addrv(ntp, addresses, r, in4_addr_is_non_local); + if (r < 0) + return r; + } else if (r < 0 && r != -ENODATA) + return r; + } + + if (link->network->dhcp_use_sip) { + r = sd_dhcp_lease_get_sip(link->dhcp_lease, &addresses); + if (r > 0) { + r = ordered_set_put_in4_addrv(sip, addresses, r, in4_addr_is_non_local); + if (r < 0) + return r; + } else if (r < 0 && r != -ENODATA) + return r; + } + + if (link->network->dhcp_use_domains != DHCP_USE_DOMAINS_NO) { + const char *domainname; + char **domains = NULL; + + OrderedSet *target_domains = (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES) ? search_domains : route_domains; + r = sd_dhcp_lease_get_domainname(link->dhcp_lease, &domainname); + if (r >= 0) { + r = ordered_set_put_strdup(target_domains, domainname); + if (r < 0) + return r; + } else if (r != -ENODATA) + return r; + + r = sd_dhcp_lease_get_search_domains(link->dhcp_lease, &domains); + if (r >= 0) { + r = ordered_set_put_strdupv(target_domains, domains); + if (r < 0) + return r; + } else if (r != -ENODATA) + return r; + } + } + + if (carrier_state >= LINK_CARRIER_STATE_ENSLAVED) + carrier_state = LINK_CARRIER_STATE_CARRIER; + + operstate_str = link_operstate_to_string(operstate); + assert(operstate_str); + + carrier_state_str = link_carrier_state_to_string(carrier_state); + assert(carrier_state_str); + + address_state_str = link_address_state_to_string(address_state); + assert(address_state_str); + + r = fopen_temporary(m->state_file, &f, &temp_path); + if (r < 0) + return r; + + (void) fchmod(fileno(f), 0644); + + fprintf(f, + "# This is private data. Do not parse.\n" + "OPER_STATE=%s\n" + "CARRIER_STATE=%s\n" + "ADDRESS_STATE=%s\n", + operstate_str, carrier_state_str, address_state_str); + + ordered_set_print(f, "DNS=", dns); + ordered_set_print(f, "NTP=", ntp); + ordered_set_print(f, "SIP=", sip); + ordered_set_print(f, "DOMAINS=", search_domains); + ordered_set_print(f, "ROUTE_DOMAINS=", route_domains); + + r = routing_policy_serialize_rules(m->rules, f); + if (r < 0) + goto fail; + + r = fflush_and_check(f); + if (r < 0) + goto fail; + + if (rename(temp_path, m->state_file) < 0) { + r = -errno; + goto fail; + } + + if (m->operational_state != operstate) { + m->operational_state = operstate; + if (strv_extend(&p, "OperationalState") < 0) + log_oom(); + } + + if (m->carrier_state != carrier_state) { + m->carrier_state = carrier_state; + if (strv_extend(&p, "CarrierState") < 0) + log_oom(); + } + + if (m->address_state != address_state) { + m->address_state = address_state; + if (strv_extend(&p, "AddressState") < 0) + log_oom(); + } + + if (p) { + r = manager_send_changed_strv(m, p); + if (r < 0) + log_error_errno(r, "Could not emit changed properties: %m"); + } + + m->dirty = false; + + return 0; + +fail: + (void) unlink(m->state_file); + (void) unlink(temp_path); + + return log_error_errno(r, "Failed to save network state to %s: %m", m->state_file); +} + +static int manager_dirty_handler(sd_event_source *s, void *userdata) { + Manager *m = userdata; + Link *link; + + assert(m); + + if (m->dirty) + manager_save(m); + + SET_FOREACH(link, m->dirty_links) + (void) link_save_and_clean(link); + + return 1; +} + +static int signal_terminate_callback(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + Manager *m = userdata; + + assert(m); + m->restarting = false; + + log_debug("Terminate operation initiated."); + + return sd_event_exit(sd_event_source_get_event(s), 0); +} + +static int signal_restart_callback(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + Manager *m = userdata; + + assert(m); + m->restarting = true; + + log_debug("Restart operation initiated."); + + return sd_event_exit(sd_event_source_get_event(s), 0); +} + +int manager_new(Manager **ret) { + _cleanup_(manager_freep) Manager *m = NULL; + int r; + + m = new(Manager, 1); + if (!m) + return -ENOMEM; + + *m = (Manager) { + .speed_meter_interval_usec = SPEED_METER_DEFAULT_TIME_INTERVAL, + .manage_foreign_routes = true, + .ethtool_fd = -1, + }; + + m->state_file = strdup("/run/systemd/netif/state"); + if (!m->state_file) + return -ENOMEM; + + r = sd_event_default(&m->event); + if (r < 0) + return r; + + assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, SIGUSR2, -1) >= 0); + + (void) sd_event_set_watchdog(m->event, true); + (void) sd_event_add_signal(m->event, NULL, SIGTERM, signal_terminate_callback, m); + (void) sd_event_add_signal(m->event, NULL, SIGINT, signal_terminate_callback, m); + (void) sd_event_add_signal(m->event, NULL, SIGUSR2, signal_restart_callback, m); + + r = sd_event_add_post(m->event, NULL, manager_dirty_handler, m); + if (r < 0) + return r; + + r = manager_connect_rtnl(m); + if (r < 0) + return r; + + r = manager_connect_genl(m); + if (r < 0) + return r; + + r = manager_connect_udev(m); + if (r < 0) + return r; + + r = sd_resolve_default(&m->resolve); + if (r < 0) + return r; + + r = sd_resolve_attach_event(m->resolve, m->event, 0); + if (r < 0) + return r; + + r = address_pool_setup_default(m); + if (r < 0) + return r; + + m->duid.type = DUID_TYPE_EN; + + (void) routing_policy_load_rules(m->state_file, &m->rules_saved); + + *ret = TAKE_PTR(m); + + return 0; +} + +void manager_free(Manager *m) { + Link *link; + + if (!m) + return; + + free(m->state_file); + + HASHMAP_FOREACH(link, m->links) + (void) link_stop_engines(link, true); + + m->dhcp6_prefixes = hashmap_free_with_destructor(m->dhcp6_prefixes, dhcp6_pd_free); + m->dhcp6_pd_prefixes = set_free_with_destructor(m->dhcp6_pd_prefixes, dhcp6_pd_free); + + m->dirty_links = set_free_with_destructor(m->dirty_links, link_unref); + m->links_requesting_uuid = set_free_with_destructor(m->links_requesting_uuid, link_unref); + m->links = hashmap_free_with_destructor(m->links, link_unref); + + m->duids_requesting_uuid = set_free(m->duids_requesting_uuid); + m->networks = ordered_hashmap_free_with_destructor(m->networks, network_unref); + + m->netdevs = hashmap_free_with_destructor(m->netdevs, netdev_unref); + + ordered_set_free_free(m->address_pools); + + /* routing_policy_rule_free() access m->rules and m->rules_foreign. + * So, it is necessary to set NULL after the sets are freed. */ + m->rules = set_free(m->rules); + m->rules_foreign = set_free(m->rules_foreign); + set_free(m->rules_saved); + + sd_netlink_unref(m->rtnl); + sd_netlink_unref(m->genl); + sd_resolve_unref(m->resolve); + + /* reject (e.g. unreachable) type routes are managed by Manager, but may be referenced by a + * link. E.g., DHCP6 with prefix delegation creates unreachable routes, and they are referenced + * by the upstream link. And the links may be referenced by netlink slots. Hence, two + * set_free() must be called after the above sd_netlink_unref(). */ + m->routes = set_free(m->routes); + m->routes_foreign = set_free(m->routes_foreign); + + sd_event_source_unref(m->speed_meter_event_source); + sd_event_unref(m->event); + + sd_device_monitor_unref(m->device_monitor); + + bus_verify_polkit_async_registry_free(m->polkit_registry); + sd_bus_flush_close_unref(m->bus); + + free(m->dynamic_timezone); + free(m->dynamic_hostname); + + safe_close(m->ethtool_fd); + + free(m); +} + +int manager_start(Manager *m) { + Link *link; + int r; + + assert(m); + + r = manager_start_speed_meter(m); + if (r < 0) + return log_error_errno(r, "Failed to initialize speed meter: %m"); + + /* The dirty handler will deal with future serialization, but the first one + must be done explicitly. */ + + manager_save(m); + + HASHMAP_FOREACH(link, m->links) + (void) link_save(link); + + return 0; +} + +int manager_load_config(Manager *m) { + int r; + + /* update timestamp */ + paths_check_timestamp(NETWORK_DIRS, &m->network_dirs_ts_usec, true); + + r = netdev_load(m, false); + if (r < 0) + return r; + + r = network_load(m, &m->networks); + if (r < 0) + return r; + + return 0; +} + +bool manager_should_reload(Manager *m) { + return paths_check_timestamp(NETWORK_DIRS, &m->network_dirs_ts_usec, false); +} + +static int manager_enumerate_internal( + Manager *m, + sd_netlink_message *req, + int (*process)(sd_netlink *, sd_netlink_message *, Manager *), + const char *name) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *reply = NULL; + int r; + + assert(m); + assert(m->rtnl); + assert(req); + assert(process); + + r = sd_netlink_message_request_dump(req, true); + if (r < 0) + return r; + + r = sd_netlink_call(m->rtnl, req, 0, &reply); + if (r < 0) { + if (name && (r == -EOPNOTSUPP || (r == -EINVAL && mac_selinux_enforcing()))) { + log_debug_errno(r, "%s are not supported by the kernel. Ignoring.", name); + return 0; + } + + return r; + } + + for (sd_netlink_message *reply_one = reply; reply_one; reply_one = sd_netlink_message_next(reply_one)) { + int k; + + m->enumerating = true; + + k = process(m->rtnl, reply_one, m); + if (k < 0 && r >= 0) + r = k; + + m->enumerating = false; + } + + return r; +} + +static int manager_enumerate_links(Manager *m) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(m); + assert(m->rtnl); + + r = sd_rtnl_message_new_link(m->rtnl, &req, RTM_GETLINK, 0); + if (r < 0) + return r; + + return manager_enumerate_internal(m, req, manager_rtnl_process_link, NULL); +} + +static int manager_enumerate_addresses(Manager *m) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(m); + assert(m->rtnl); + + r = sd_rtnl_message_new_addr(m->rtnl, &req, RTM_GETADDR, 0, 0); + if (r < 0) + return r; + + return manager_enumerate_internal(m, req, manager_rtnl_process_address, NULL); +} + +static int manager_enumerate_neighbors(Manager *m) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(m); + assert(m->rtnl); + + r = sd_rtnl_message_new_neigh(m->rtnl, &req, RTM_GETNEIGH, 0, AF_UNSPEC); + if (r < 0) + return r; + + return manager_enumerate_internal(m, req, manager_rtnl_process_neighbor, NULL); +} + +static int manager_enumerate_routes(Manager *m) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(m); + assert(m->rtnl); + + if (!m->manage_foreign_routes) + return 0; + + r = sd_rtnl_message_new_route(m->rtnl, &req, RTM_GETROUTE, 0, 0); + if (r < 0) + return r; + + return manager_enumerate_internal(m, req, manager_rtnl_process_route, NULL); +} + +static int manager_enumerate_rules(Manager *m) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(m); + assert(m->rtnl); + + r = sd_rtnl_message_new_routing_policy_rule(m->rtnl, &req, RTM_GETRULE, 0); + if (r < 0) + return r; + + return manager_enumerate_internal(m, req, manager_rtnl_process_rule, "Routing policy rules"); +} + +static int manager_enumerate_nexthop(Manager *m) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(m); + assert(m->rtnl); + + r = sd_rtnl_message_new_nexthop(m->rtnl, &req, RTM_GETNEXTHOP, 0, 0); + if (r < 0) + return r; + + return manager_enumerate_internal(m, req, manager_rtnl_process_nexthop, "Nexthop rules"); +} + +int manager_enumerate(Manager *m) { + int r; + + r = manager_enumerate_links(m); + if (r < 0) + return log_error_errno(r, "Could not enumerate links: %m"); + + r = manager_enumerate_addresses(m); + if (r < 0) + return log_error_errno(r, "Could not enumerate addresses: %m"); + + r = manager_enumerate_neighbors(m); + if (r < 0) + return log_error_errno(r, "Could not enumerate neighbors: %m"); + + r = manager_enumerate_routes(m); + if (r < 0) + return log_error_errno(r, "Could not enumerate routes: %m"); + + r = manager_enumerate_rules(m); + if (r < 0) + return log_error_errno(r, "Could not enumerate routing policy rules: %m"); + + r = manager_enumerate_nexthop(m); + if (r < 0) + return log_error_errno(r, "Could not enumerate nexthop rules: %m"); + + return 0; +} + +Link* manager_find_uplink(Manager *m, Link *exclude) { + _cleanup_free_ struct local_address *gateways = NULL; + int n, i; + + assert(m); + + /* Looks for a suitable "uplink", via black magic: an + * interface that is up and where the default route with the + * highest priority points to. */ + + n = local_gateways(m->rtnl, 0, AF_UNSPEC, &gateways); + if (n < 0) { + log_warning_errno(n, "Failed to determine list of default gateways: %m"); + return NULL; + } + + for (i = 0; i < n; i++) { + Link *link; + + link = hashmap_get(m->links, INT_TO_PTR(gateways[i].ifindex)); + if (!link) { + log_debug("Weird, found a gateway for a link we don't know. Ignoring."); + continue; + } + + if (link == exclude) + continue; + + if (link->operstate < LINK_OPERSTATE_ROUTABLE) + continue; + + return link; + } + + return NULL; +} + +void manager_dirty(Manager *manager) { + assert(manager); + + /* the serialized state in /run is no longer up-to-date */ + manager->dirty = true; +} + +static int set_hostname_handler(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + _unused_ Manager *manager = userdata; + const sd_bus_error *e; + + assert(m); + assert(manager); + + e = sd_bus_message_get_error(m); + if (e) + log_warning_errno(sd_bus_error_get_errno(e), "Could not set hostname: %s", e->message); + + return 1; +} + +int manager_set_hostname(Manager *m, const char *hostname) { + int r; + + log_debug("Setting transient hostname: '%s'", strna(hostname)); + + if (free_and_strdup(&m->dynamic_hostname, hostname) < 0) + return log_oom(); + + if (!m->bus || sd_bus_is_ready(m->bus) <= 0) { + log_debug("Not connected to system bus, setting hostname later."); + return 0; + } + + r = sd_bus_call_method_async( + m->bus, + NULL, + "org.freedesktop.hostname1", + "/org/freedesktop/hostname1", + "org.freedesktop.hostname1", + "SetHostname", + set_hostname_handler, + m, + "sb", + hostname, + false); + + if (r < 0) + return log_error_errno(r, "Could not set transient hostname: %m"); + + return 0; +} + +static int set_timezone_handler(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + _unused_ Manager *manager = userdata; + const sd_bus_error *e; + + assert(m); + assert(manager); + + e = sd_bus_message_get_error(m); + if (e) + log_warning_errno(sd_bus_error_get_errno(e), "Could not set timezone: %s", e->message); + + return 1; +} + +int manager_set_timezone(Manager *m, const char *tz) { + int r; + + assert(m); + assert(tz); + + log_debug("Setting system timezone: '%s'", tz); + if (free_and_strdup(&m->dynamic_timezone, tz) < 0) + return log_oom(); + + if (!m->bus || sd_bus_is_ready(m->bus) <= 0) { + log_debug("Not connected to system bus, setting timezone later."); + return 0; + } + + r = sd_bus_call_method_async( + m->bus, + NULL, + "org.freedesktop.timedate1", + "/org/freedesktop/timedate1", + "org.freedesktop.timedate1", + "SetTimezone", + set_timezone_handler, + m, + "sb", + tz, + false); + if (r < 0) + return log_error_errno(r, "Could not set timezone: %m"); + + return 0; +} diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h new file mode 100644 index 0000000..b67116b --- /dev/null +++ b/src/network/networkd-manager.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-bus.h" +#include "sd-device.h" +#include "sd-event.h" +#include "sd-id128.h" +#include "sd-netlink.h" +#include "sd-resolve.h" + +#include "dhcp-identifier.h" +#include "hashmap.h" +#include "networkd-link.h" +#include "networkd-network.h" +#include "ordered-set.h" +#include "set.h" +#include "time-util.h" + +struct Manager { + sd_netlink *rtnl; + /* lazy initialized */ + sd_netlink *genl; + sd_event *event; + sd_resolve *resolve; + sd_bus *bus; + sd_device_monitor *device_monitor; + Hashmap *polkit_registry; + int ethtool_fd; + + bool enumerating:1; + bool dirty:1; + bool restarting:1; + bool manage_foreign_routes; + + Set *dirty_links; + + char *state_file; + LinkOperationalState operational_state; + LinkCarrierState carrier_state; + LinkAddressState address_state; + + Hashmap *links; + Hashmap *netdevs; + OrderedHashmap *networks; + Hashmap *dhcp6_prefixes; + Set *dhcp6_pd_prefixes; + OrderedSet *address_pools; + + usec_t network_dirs_ts_usec; + + DUID duid; + sd_id128_t product_uuid; + bool has_product_uuid; + Set *links_requesting_uuid; + Set *duids_requesting_uuid; + + char* dynamic_hostname; + char* dynamic_timezone; + + Set *rules; + Set *rules_foreign; + Set *rules_saved; + + /* Manager stores routes without RTA_OIF attribute. */ + Set *routes; + Set *routes_foreign; + + /* For link speed meter*/ + bool use_speed_meter; + sd_event_source *speed_meter_event_source; + usec_t speed_meter_interval_usec; + usec_t speed_meter_usec_new; + usec_t speed_meter_usec_old; + + bool dhcp4_prefix_root_cannot_set_table:1; + bool bridge_mdb_on_master_not_supported:1; +}; + +int manager_new(Manager **ret); +void manager_free(Manager *m); + +int manager_connect_bus(Manager *m); +int manager_start(Manager *m); + +int manager_load_config(Manager *m); +bool manager_should_reload(Manager *m); + +int manager_enumerate(Manager *m); + +void manager_dirty(Manager *m); + +Link* manager_find_uplink(Manager *m, Link *exclude); + +int manager_set_hostname(Manager *m, const char *hostname); +int manager_set_timezone(Manager *m, const char *timezone); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); diff --git a/src/network/networkd-mdb.c b/src/network/networkd-mdb.c new file mode 100644 index 0000000..0300dce --- /dev/null +++ b/src/network/networkd-mdb.c @@ -0,0 +1,365 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <net/if.h> + +#include "netlink-util.h" +#include "networkd-link.h" +#include "networkd-manager.h" +#include "networkd-mdb.h" +#include "networkd-network.h" +#include "string-util.h" +#include "vlan-util.h" + +#define STATIC_MDB_ENTRIES_PER_NETWORK_MAX 1024U + +/* remove MDB entry. */ +MdbEntry *mdb_entry_free(MdbEntry *mdb_entry) { + if (!mdb_entry) + return NULL; + + if (mdb_entry->network) { + assert(mdb_entry->section); + hashmap_remove(mdb_entry->network->mdb_entries_by_section, mdb_entry->section); + } + + network_config_section_free(mdb_entry->section); + + return mfree(mdb_entry); +} + +DEFINE_NETWORK_SECTION_FUNCTIONS(MdbEntry, mdb_entry_free); + +/* create a new MDB entry or get an existing one. */ +static int mdb_entry_new_static( + Network *network, + const char *filename, + unsigned section_line, + MdbEntry **ret) { + + _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; + _cleanup_(mdb_entry_freep) MdbEntry *mdb_entry = NULL; + int r; + + assert(network); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = network_config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + /* search entry in hashmap first. */ + mdb_entry = hashmap_get(network->mdb_entries_by_section, n); + if (mdb_entry) { + *ret = TAKE_PTR(mdb_entry); + return 0; + } + + if (hashmap_size(network->mdb_entries_by_section) >= STATIC_MDB_ENTRIES_PER_NETWORK_MAX) + return -E2BIG; + + /* allocate space for an MDB entry. */ + mdb_entry = new(MdbEntry, 1); + if (!mdb_entry) + return -ENOMEM; + + /* init MDB structure. */ + *mdb_entry = (MdbEntry) { + .network = network, + .section = TAKE_PTR(n), + }; + + r = hashmap_ensure_allocated(&network->mdb_entries_by_section, &network_config_hash_ops); + if (r < 0) + return r; + + r = hashmap_put(network->mdb_entries_by_section, mdb_entry->section, mdb_entry); + if (r < 0) + return r; + + /* return allocated MDB structure. */ + *ret = TAKE_PTR(mdb_entry); + return 0; +} + +static int set_mdb_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + assert(link->bridge_mdb_messages > 0); + + link->bridge_mdb_messages--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r == -EINVAL && streq_ptr(link->kind, "bridge") && (!link->network || !link->network->bridge)) { + /* To configure bridge MDB entries on bridge master, 1bc844ee0faa1b92e3ede00bdd948021c78d7088 (v5.4) is required. */ + if (!link->manager->bridge_mdb_on_master_not_supported) { + log_link_warning_errno(link, r, "Kernel seems not to support configuring bridge MDB entries on bridge master, ignoring: %m"); + link->manager->bridge_mdb_on_master_not_supported = true; + } + } else if (r < 0 && r != -EEXIST) { + log_link_message_warning_errno(link, m, r, "Could not add MDB entry"); + link_enter_failed(link); + return 1; + } + + if (link->bridge_mdb_messages == 0) { + link->bridge_mdb_configured = true; + link_check_ready(link); + } + + return 1; +} + +static int link_get_bridge_master_ifindex(Link *link) { + assert(link); + + if (link->network && link->network->bridge) + return link->network->bridge->ifindex; + + if (streq_ptr(link->kind, "bridge")) + return link->ifindex; + + return 0; +} + +/* send a request to the kernel to add an MDB entry */ +static int mdb_entry_configure(Link *link, MdbEntry *mdb_entry) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + struct br_mdb_entry entry; + int master, r; + + assert(link); + assert(link->network); + assert(link->manager); + assert(mdb_entry); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *a = NULL; + + (void) in_addr_to_string(mdb_entry->family, &mdb_entry->group_addr, &a); + log_link_debug(link, "Configuring bridge MDB entry: MulticastGroupAddress=%s, VLANId=%u", + strna(a), mdb_entry->vlan_id); + } + + master = link_get_bridge_master_ifindex(link); + if (master <= 0) + return log_link_error_errno(link, SYNTHETIC_ERRNO(EINVAL), "Invalid bridge master ifindex %i", master); + + entry = (struct br_mdb_entry) { + /* If MDB entry is added on bridge master, then the state must be MDB_TEMPORARY. + * See br_mdb_add_group() in net/bridge/br_mdb.c of kernel. */ + .state = master == link->ifindex ? MDB_TEMPORARY : MDB_PERMANENT, + .ifindex = link->ifindex, + .vid = mdb_entry->vlan_id, + }; + + /* create new RTM message */ + r = sd_rtnl_message_new_mdb(link->manager->rtnl, &req, RTM_NEWMDB, master); + if (r < 0) + return log_link_error_errno(link, r, "Could not create RTM_NEWMDB message: %m"); + + switch (mdb_entry->family) { + case AF_INET: + entry.addr.u.ip4 = mdb_entry->group_addr.in.s_addr; + entry.addr.proto = htobe16(ETH_P_IP); + break; + + case AF_INET6: + entry.addr.u.ip6 = mdb_entry->group_addr.in6; + entry.addr.proto = htobe16(ETH_P_IPV6); + break; + + default: + assert_not_reached("Invalid address family"); + } + + r = sd_netlink_message_append_data(req, MDBA_SET_ENTRY, &entry, sizeof(entry)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append MDBA_SET_ENTRY attribute: %m"); + + r = netlink_call_async(link->manager->rtnl, NULL, req, set_mdb_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + return 1; +} + +int link_set_bridge_mdb(Link *link) { + MdbEntry *mdb_entry; + int r; + + assert(link); + assert(link->manager); + + link->bridge_mdb_configured = false; + + if (!link->network) + return 0; + + if (hashmap_isempty(link->network->mdb_entries_by_section)) + goto finish; + + if (!link_has_carrier(link)) + return log_link_debug(link, "Link does not have carrier yet, setting MDB entries later."); + + if (link->network->bridge) { + Link *master; + + r = link_get(link->manager, link->network->bridge->ifindex, &master); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get Link object for Bridge=%s", link->network->bridge->ifname); + + if (!link_has_carrier(master)) + return log_link_debug(link, "Bridge interface %s does not have carrier yet, setting MDB entries later.", link->network->bridge->ifname); + + } else if (!streq_ptr(link->kind, "bridge")) { + log_link_warning(link, "Link is neither a bridge master nor a bridge port, ignoring [BridgeMDB] sections."); + goto finish; + } else if (link->manager->bridge_mdb_on_master_not_supported) { + log_link_debug(link, "Kernel seems not to support configuring bridge MDB entries on bridge master, ignoring [BridgeMDB] sections."); + goto finish; + } + + HASHMAP_FOREACH(mdb_entry, link->network->mdb_entries_by_section) { + r = mdb_entry_configure(link, mdb_entry); + if (r < 0) + return log_link_error_errno(link, r, "Failed to add MDB entry to multicast group database: %m"); + + link->bridge_mdb_messages++; + } + +finish: + if (link->bridge_mdb_messages == 0) { + link->bridge_mdb_configured = true; + link_check_ready(link); + } + + return 0; +} + +static int mdb_entry_verify(MdbEntry *mdb_entry) { + if (section_is_invalid(mdb_entry->section)) + return -EINVAL; + + if (mdb_entry->family == AF_UNSPEC) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: [BridgeMDB] section without MulticastGroupAddress= field configured. " + "Ignoring [BridgeMDB] section from line %u.", + mdb_entry->section->filename, mdb_entry->section->line); + + if (!in_addr_is_multicast(mdb_entry->family, &mdb_entry->group_addr)) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: MulticastGroupAddress= is not a multicast address. " + "Ignoring [BridgeMDB] section from line %u.", + mdb_entry->section->filename, mdb_entry->section->line); + + if (mdb_entry->family == AF_INET) { + if (in4_addr_is_local_multicast(&mdb_entry->group_addr.in)) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: MulticastGroupAddress= is a local multicast address. " + "Ignoring [BridgeMDB] section from line %u.", + mdb_entry->section->filename, mdb_entry->section->line); + } else { + if (in6_addr_is_link_local_all_nodes(&mdb_entry->group_addr.in6)) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: MulticastGroupAddress= is the multicast all nodes address. " + "Ignoring [BridgeMDB] section from line %u.", + mdb_entry->section->filename, mdb_entry->section->line); + } + + return 0; +} + +void network_drop_invalid_mdb_entries(Network *network) { + MdbEntry *mdb_entry; + + assert(network); + + HASHMAP_FOREACH(mdb_entry, network->mdb_entries_by_section) + if (mdb_entry_verify(mdb_entry) < 0) + mdb_entry_free(mdb_entry); +} + +/* parse the VLAN Id from config files. */ +int config_parse_mdb_vlan_id( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(mdb_entry_free_or_set_invalidp) MdbEntry *mdb_entry = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = mdb_entry_new_static(network, filename, section_line, &mdb_entry); + if (r < 0) + return log_oom(); + + r = config_parse_vlanid(unit, filename, line, section, + section_line, lvalue, ltype, + rvalue, &mdb_entry->vlan_id, userdata); + if (r < 0) + return r; + + mdb_entry = NULL; + + return 0; +} + +/* parse the multicast group from config files. */ +int config_parse_mdb_group_address( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(mdb_entry_free_or_set_invalidp) MdbEntry *mdb_entry = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = mdb_entry_new_static(network, filename, section_line, &mdb_entry); + if (r < 0) + return log_oom(); + + r = in_addr_from_string_auto(rvalue, &mdb_entry->family, &mdb_entry->group_addr); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Cannot parse multicast group address: %m"); + return 0; + } + + mdb_entry = NULL; + + return 0; +} diff --git a/src/network/networkd-mdb.h b/src/network/networkd-mdb.h new file mode 100644 index 0000000..ea88412 --- /dev/null +++ b/src/network/networkd-mdb.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <inttypes.h> + +#include "conf-parser.h" +#include "in-addr-util.h" +#include "networkd-util.h" + +typedef struct Network Network; +typedef struct Link Link; + +typedef struct MdbEntry { + Network *network; + NetworkConfigSection *section; + + int family; + union in_addr_union group_addr; + uint16_t vlan_id; +} MdbEntry; + +MdbEntry *mdb_entry_free(MdbEntry *mdb_entry); + +void network_drop_invalid_mdb_entries(Network *network); + +int link_set_bridge_mdb(Link *link); + +CONFIG_PARSER_PROTOTYPE(config_parse_mdb_group_address); +CONFIG_PARSER_PROTOTYPE(config_parse_mdb_vlan_id); diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c new file mode 100644 index 0000000..d2aa3db --- /dev/null +++ b/src/network/networkd-ndisc.c @@ -0,0 +1,1516 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2014 Intel Corporation. All rights reserved. +***/ + +#include <arpa/inet.h> +#include <netinet/icmp6.h> +#include <net/if_arp.h> +#include <linux/if.h> + +#include "sd-ndisc.h" + +#include "missing_network.h" +#include "networkd-address.h" +#include "networkd-dhcp6.h" +#include "networkd-manager.h" +#include "networkd-ndisc.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" + +#define NDISC_DNSSL_MAX 64U +#define NDISC_RDNSS_MAX 64U +#define NDISC_PREFIX_LFT_MIN 7200U + +#define DAD_CONFLICTS_IDGEN_RETRIES_RFC7217 3 + +/* https://tools.ietf.org/html/rfc5453 */ +/* https://www.iana.org/assignments/ipv6-interface-ids/ipv6-interface-ids.xml */ + +#define SUBNET_ROUTER_ANYCAST_ADDRESS_RFC4291 ((struct in6_addr) { .s6_addr = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }) +#define SUBNET_ROUTER_ANYCAST_PREFIXLEN 8 +#define RESERVED_IPV6_INTERFACE_IDENTIFIERS_ADDRESS_RFC4291 ((struct in6_addr) { .s6_addr = { 0x02, 0x00, 0x5E, 0xFF, 0xFE } }) +#define RESERVED_IPV6_INTERFACE_IDENTIFIERS_PREFIXLEN 5 +#define RESERVED_SUBNET_ANYCAST_ADDRESSES_RFC4291 ((struct in6_addr) { .s6_addr = { 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } }) +#define RESERVED_SUBNET_ANYCAST_PREFIXLEN 7 + +#define NDISC_APP_ID SD_ID128_MAKE(13,ac,81,a7,d5,3f,49,78,92,79,5d,0c,29,3a,bc,7e) + +bool link_ipv6_accept_ra_enabled(Link *link) { + assert(link); + + if (!socket_ipv6_is_supported()) + return false; + + if (link->flags & IFF_LOOPBACK) + return false; + + if (!link->network) + return false; + + if (!link_ipv6ll_enabled(link)) + return false; + + assert(link->network->ipv6_accept_ra >= 0); + return link->network->ipv6_accept_ra; +} + +void network_adjust_ipv6_accept_ra(Network *network) { + assert(network); + + if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6)) { + if (network->ipv6_accept_ra > 0) + log_warning("%s: IPv6AcceptRA= is enabled but IPv6 link local addressing is disabled or not supported. " + "Disabling IPv6AcceptRA=.", network->filename); + network->ipv6_accept_ra = false; + } + + if (network->ipv6_accept_ra < 0) + /* default to accept RA if ip_forward is disabled and ignore RA if ip_forward is enabled */ + network->ipv6_accept_ra = !FLAGS_SET(network->ip_forward, ADDRESS_FAMILY_IPV6); +} + +static int ndisc_remove_old_one(Link *link, const struct in6_addr *router, bool force); + +static int ndisc_address_callback(Address *address) { + struct in6_addr router = {}; + NDiscAddress *n; + + assert(address); + assert(address->link); + + SET_FOREACH(n, address->link->ndisc_addresses) + if (n->address == address) { + router = n->router; + break; + } + + if (IN6_IS_ADDR_UNSPECIFIED(&router)) { + _cleanup_free_ char *buf = NULL; + + (void) in_addr_to_string(address->family, &address->in_addr, &buf); + log_link_debug(address->link, "%s is called for %s/%u, but it is already removed, ignoring.", + __func__, strna(buf), address->prefixlen); + return 0; + } + + /* Make this called only once */ + SET_FOREACH(n, address->link->ndisc_addresses) + if (IN6_ARE_ADDR_EQUAL(&n->router, &router)) + n->address->callback = NULL; + + return ndisc_remove_old_one(address->link, &router, true); +} + +static int ndisc_remove_old_one(Link *link, const struct in6_addr *router, bool force) { + NDiscAddress *na; + NDiscRoute *nr; + NDiscDNSSL *dnssl; + NDiscRDNSS *rdnss; + int k, r = 0; + + assert(link); + assert(router); + + if (!force) { + bool set_callback = false; + + if (!link->ndisc_addresses_configured || !link->ndisc_routes_configured) + return 0; + + SET_FOREACH(na, link->ndisc_addresses) + if (!na->marked && IN6_ARE_ADDR_EQUAL(&na->router, router)) { + set_callback = true; + break; + } + + if (set_callback) + SET_FOREACH(na, link->ndisc_addresses) + if (!na->marked && address_is_ready(na->address)) { + set_callback = false; + break; + } + + if (set_callback) { + SET_FOREACH(na, link->ndisc_addresses) + if (!na->marked && IN6_ARE_ADDR_EQUAL(&na->router, router)) + na->address->callback = ndisc_address_callback; + + if (DEBUG_LOGGING) { + _cleanup_free_ char *buf = NULL; + + (void) in_addr_to_string(AF_INET6, (union in_addr_union *) router, &buf); + log_link_debug(link, "No SLAAC address obtained from %s is ready. " + "The old NDisc information will be removed later.", + strna(buf)); + } + return 0; + } + } + + if (DEBUG_LOGGING) { + _cleanup_free_ char *buf = NULL; + + (void) in_addr_to_string(AF_INET6, (union in_addr_union *) router, &buf); + log_link_debug(link, "Removing old NDisc information obtained from %s.", strna(buf)); + } + + link_dirty(link); + + SET_FOREACH(na, link->ndisc_addresses) + if (na->marked && IN6_ARE_ADDR_EQUAL(&na->router, router)) { + k = address_remove(na->address, link, NULL); + if (k < 0) + r = k; + } + + SET_FOREACH(nr, link->ndisc_routes) + if (nr->marked && IN6_ARE_ADDR_EQUAL(&nr->router, router)) { + k = route_remove(nr->route, NULL, link, NULL); + if (k < 0) + r = k; + } + + SET_FOREACH(rdnss, link->ndisc_rdnss) + if (rdnss->marked && IN6_ARE_ADDR_EQUAL(&rdnss->router, router)) + free(set_remove(link->ndisc_rdnss, rdnss)); + + SET_FOREACH(dnssl, link->ndisc_dnssl) + if (dnssl->marked && IN6_ARE_ADDR_EQUAL(&dnssl->router, router)) + free(set_remove(link->ndisc_dnssl, dnssl)); + + return r; +} + +static int ndisc_remove_old(Link *link) { + _cleanup_set_free_free_ Set *routers = NULL; + _cleanup_free_ struct in6_addr *router = NULL; + struct in6_addr *a; + NDiscAddress *na; + NDiscRoute *nr; + NDiscDNSSL *dnssl; + NDiscRDNSS *rdnss; + int k, r; + + assert(link); + + routers = set_new(&in6_addr_hash_ops); + if (!routers) + return -ENOMEM; + + SET_FOREACH(na, link->ndisc_addresses) + if (!set_contains(routers, &na->router)) { + router = newdup(struct in6_addr, &na->router, 1); + if (!router) + return -ENOMEM; + + r = set_put(routers, router); + if (r < 0) + return r; + + assert(r > 0); + TAKE_PTR(router); + } + + SET_FOREACH(nr, link->ndisc_routes) + if (!set_contains(routers, &nr->router)) { + router = newdup(struct in6_addr, &nr->router, 1); + if (!router) + return -ENOMEM; + + r = set_put(routers, router); + if (r < 0) + return r; + + assert(r > 0); + TAKE_PTR(router); + } + + SET_FOREACH(rdnss, link->ndisc_rdnss) + if (!set_contains(routers, &rdnss->router)) { + router = newdup(struct in6_addr, &rdnss->router, 1); + if (!router) + return -ENOMEM; + + r = set_put(routers, router); + if (r < 0) + return r; + + assert(r > 0); + TAKE_PTR(router); + } + + SET_FOREACH(dnssl, link->ndisc_dnssl) + if (!set_contains(routers, &dnssl->router)) { + router = newdup(struct in6_addr, &dnssl->router, 1); + if (!router) + return -ENOMEM; + + r = set_put(routers, router); + if (r < 0) + return r; + + assert(r > 0); + TAKE_PTR(router); + } + + r = 0; + SET_FOREACH(a, routers) { + k = ndisc_remove_old_one(link, a, false); + if (k < 0) + r = k; + } + + return r; +} + +static void ndisc_route_hash_func(const NDiscRoute *x, struct siphash *state) { + route_hash_func(x->route, state); +} + +static int ndisc_route_compare_func(const NDiscRoute *a, const NDiscRoute *b) { + return route_compare_func(a->route, b->route); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + ndisc_route_hash_ops, + NDiscRoute, + ndisc_route_hash_func, + ndisc_route_compare_func, + free); + +static int ndisc_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + assert(link->ndisc_routes_messages > 0); + + link->ndisc_routes_messages--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EEXIST) { + log_link_message_error_errno(link, m, r, "Could not set NDisc route"); + link_enter_failed(link); + return 1; + } + + if (link->ndisc_routes_messages == 0) { + log_link_debug(link, "NDisc routes set."); + link->ndisc_routes_configured = true; + + r = ndisc_remove_old(link); + if (r < 0) { + link_enter_failed(link); + return 1; + } + + link_check_ready(link); + } + + return 1; +} + +static int ndisc_route_configure(Route *route, Link *link, sd_ndisc_router *rt) { + _cleanup_free_ NDiscRoute *nr = NULL; + NDiscRoute *nr_exist; + struct in6_addr router; + Route *ret; + int r; + + assert(route); + assert(link); + assert(rt); + + r = route_configure(route, link, ndisc_route_handler, &ret); + if (r < 0) + return log_link_error_errno(link, r, "Failed to set NDisc route: %m"); + + link->ndisc_routes_messages++; + + r = sd_ndisc_router_get_address(rt, &router); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get router address from RA: %m"); + + nr = new(NDiscRoute, 1); + if (!nr) + return log_oom(); + + *nr = (NDiscRoute) { + .router = router, + .route = ret, + }; + + nr_exist = set_get(link->ndisc_routes, nr); + if (nr_exist) { + nr_exist->marked = false; + nr_exist->router = router; + return 0; + } + + r = set_ensure_put(&link->ndisc_routes, &ndisc_route_hash_ops, nr); + if (r < 0) + return log_link_error_errno(link, r, "Failed to store NDisc SLAAC route: %m"); + assert(r > 0); + TAKE_PTR(nr); + + return 0; +} + +static void ndisc_address_hash_func(const NDiscAddress *x, struct siphash *state) { + address_hash_func(x->address, state); +} + +static int ndisc_address_compare_func(const NDiscAddress *a, const NDiscAddress *b) { + return address_compare_func(a->address, b->address); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + ndisc_address_hash_ops, + NDiscAddress, + ndisc_address_hash_func, + ndisc_address_compare_func, + free); + +static int ndisc_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + assert(link->ndisc_addresses_messages > 0); + + link->ndisc_addresses_messages--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EEXIST) { + log_link_message_error_errno(link, m, r, "Could not set NDisc address"); + link_enter_failed(link); + return 1; + } else if (r >= 0) + (void) manager_rtnl_process_address(rtnl, m, link->manager); + + if (link->ndisc_addresses_messages == 0) { + log_link_debug(link, "NDisc SLAAC addresses set."); + link->ndisc_addresses_configured = true; + + r = ndisc_remove_old(link); + if (r < 0) { + link_enter_failed(link); + return 1; + } + + r = link_set_routes(link); + if (r < 0) { + link_enter_failed(link); + return 1; + } + } + + return 1; +} + +static int ndisc_address_configure(Address *address, Link *link, sd_ndisc_router *rt) { + _cleanup_free_ NDiscAddress *na = NULL; + NDiscAddress *na_exist; + struct in6_addr router; + Address *ret; + int r; + + assert(address); + assert(link); + assert(rt); + + r = address_configure(address, link, ndisc_address_handler, true, &ret); + if (r < 0) + return log_link_error_errno(link, r, "Failed to set NDisc SLAAC address: %m"); + + link->ndisc_addresses_messages++; + + r = sd_ndisc_router_get_address(rt, &router); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get router address from RA: %m"); + + na = new(NDiscAddress, 1); + if (!na) + return log_oom(); + + *na = (NDiscAddress) { + .router = router, + .address = ret, + }; + + na_exist = set_get(link->ndisc_addresses, na); + if (na_exist) { + na_exist->marked = false; + na_exist->router = router; + return 0; + } + + r = set_ensure_put(&link->ndisc_addresses, &ndisc_address_hash_ops, na); + if (r < 0) + return log_link_error_errno(link, r, "Failed to store NDisc SLAAC address: %m"); + assert(r > 0); + TAKE_PTR(na); + + return 0; +} + +static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) { + _cleanup_(route_freep) Route *route = NULL; + union in_addr_union gateway; + uint16_t lifetime; + unsigned preference; + uint32_t table, mtu; + usec_t time_now; + int r; + + assert(link); + assert(rt); + + r = sd_ndisc_router_get_lifetime(rt, &lifetime); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get gateway lifetime from RA: %m"); + + if (lifetime == 0) /* not a default router */ + return 0; + + r = sd_ndisc_router_get_address(rt, &gateway.in6); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get gateway address from RA: %m"); + + if (address_exists(link, AF_INET6, &gateway)) { + if (DEBUG_LOGGING) { + _cleanup_free_ char *buffer = NULL; + + (void) in_addr_to_string(AF_INET6, &gateway, &buffer); + log_link_debug(link, "No NDisc route added, gateway %s matches local address", + strnull(buffer)); + } + return 0; + } + + r = sd_ndisc_router_get_preference(rt, &preference); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get default router preference from RA: %m"); + + r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get RA timestamp: %m"); + + r = sd_ndisc_router_get_mtu(rt, &mtu); + if (r == -ENODATA) + mtu = 0; + else if (r < 0) + return log_link_error_errno(link, r, "Failed to get default router MTU from RA: %m"); + + table = link_get_ipv6_accept_ra_route_table(link); + + r = route_new(&route); + if (r < 0) + return log_oom(); + + route->family = AF_INET6; + route->table = table; + route->priority = link->network->dhcp6_route_metric; + route->protocol = RTPROT_RA; + route->pref = preference; + route->gw_family = AF_INET6; + route->gw = gateway; + route->lifetime = time_now + lifetime * USEC_PER_SEC; + route->mtu = mtu; + + r = ndisc_route_configure(route, link, rt); + if (r < 0) + return log_link_error_errno(link, r, "Could not set default route: %m"); + + Route *route_gw; + HASHMAP_FOREACH(route_gw, link->network->routes_by_section) { + if (!route_gw->gateway_from_dhcp_or_ra) + continue; + + if (route_gw->gw_family != AF_INET6) + continue; + + route_gw->gw = gateway; + if (!route_gw->table_set) + route_gw->table = table; + if (!route_gw->priority_set) + route_gw->priority = link->network->dhcp6_route_metric; + if (!route_gw->protocol_set) + route_gw->protocol = RTPROT_RA; + if (!route_gw->pref_set) + route->pref = preference; + route_gw->lifetime = time_now + lifetime * USEC_PER_SEC; + if (route_gw->mtu == 0) + route_gw->mtu = mtu; + + r = ndisc_route_configure(route_gw, link, rt); + if (r < 0) + return log_link_error_errno(link, r, "Could not set gateway: %m"); + } + + return 0; +} + +static bool stableprivate_address_is_valid(const struct in6_addr *addr) { + assert(addr); + + /* According to rfc4291, generated address should not be in the following ranges. */ + + if (memcmp(addr, &SUBNET_ROUTER_ANYCAST_ADDRESS_RFC4291, SUBNET_ROUTER_ANYCAST_PREFIXLEN) == 0) + return false; + + if (memcmp(addr, &RESERVED_IPV6_INTERFACE_IDENTIFIERS_ADDRESS_RFC4291, RESERVED_IPV6_INTERFACE_IDENTIFIERS_PREFIXLEN) == 0) + return false; + + if (memcmp(addr, &RESERVED_SUBNET_ANYCAST_ADDRESSES_RFC4291, RESERVED_SUBNET_ANYCAST_PREFIXLEN) == 0) + return false; + + return true; +} + +static int make_stableprivate_address(Link *link, const struct in6_addr *prefix, uint8_t prefix_len, uint8_t dad_counter, struct in6_addr **ret) { + _cleanup_free_ struct in6_addr *addr = NULL; + sd_id128_t secret_key; + struct siphash state; + uint64_t rid; + size_t l; + int r; + + /* According to rfc7217 section 5.1 + * RID = F(Prefix, Net_Iface, Network_ID, DAD_Counter, secret_key) */ + + r = sd_id128_get_machine_app_specific(NDISC_APP_ID, &secret_key); + if (r < 0) + return log_error_errno(r, "Failed to generate key: %m"); + + siphash24_init(&state, secret_key.bytes); + + l = MAX(DIV_ROUND_UP(prefix_len, 8), 8); + siphash24_compress(prefix, l, &state); + siphash24_compress_string(link->ifname, &state); + /* Only last 8 bytes of IB MAC are stable */ + if (link->iftype == ARPHRD_INFINIBAND) + siphash24_compress(&link->hw_addr.addr.infiniband[12], 8, &state); + else + siphash24_compress(link->hw_addr.addr.bytes, link->hw_addr.length, &state); + siphash24_compress(&dad_counter, sizeof(uint8_t), &state); + + rid = htole64(siphash24_finalize(&state)); + + addr = new(struct in6_addr, 1); + if (!addr) + return log_oom(); + + memcpy(addr->s6_addr, prefix->s6_addr, l); + memcpy(addr->s6_addr + l, &rid, 16 - l); + + if (!stableprivate_address_is_valid(addr)) { + *ret = NULL; + return 0; + } + + *ret = TAKE_PTR(addr); + return 1; +} + +static int ndisc_router_generate_addresses(Link *link, struct in6_addr *address, uint8_t prefixlen, Set **ret) { + _cleanup_set_free_free_ Set *addresses = NULL; + IPv6Token *j; + int r; + + assert(link); + assert(address); + assert(ret); + + addresses = set_new(&in6_addr_hash_ops); + if (!addresses) + return log_oom(); + + ORDERED_SET_FOREACH(j, link->network->ipv6_tokens) { + _cleanup_free_ struct in6_addr *new_address = NULL; + + if (j->address_generation_type == IPV6_TOKEN_ADDRESS_GENERATION_PREFIXSTABLE + && (IN6_IS_ADDR_UNSPECIFIED(&j->prefix) || IN6_ARE_ADDR_EQUAL(&j->prefix, address))) { + /* While this loop uses dad_counter and a retry limit as specified in RFC 7217, the loop + does not actually attempt Duplicate Address Detection; the counter will be incremented + only when the address generation algorithm produces an invalid address, and the loop + may exit with an address which ends up being unusable due to duplication on the link. + */ + for (; j->dad_counter < DAD_CONFLICTS_IDGEN_RETRIES_RFC7217; j->dad_counter++) { + r = make_stableprivate_address(link, address, prefixlen, j->dad_counter, &new_address); + if (r < 0) + return r; + if (r > 0) + break; + } + } else if (j->address_generation_type == IPV6_TOKEN_ADDRESS_GENERATION_STATIC) { + new_address = new(struct in6_addr, 1); + if (!new_address) + return log_oom(); + + memcpy(new_address->s6_addr, address->s6_addr, 8); + memcpy(new_address->s6_addr + 8, j->prefix.s6_addr + 8, 8); + } + + if (new_address) { + r = set_put(addresses, new_address); + if (r < 0) + return log_link_error_errno(link, r, "Failed to store SLAAC address: %m"); + else if (r == 0) + log_link_debug_errno(link, r, "Generated SLAAC address is duplicated, ignoring."); + else + TAKE_PTR(new_address); + } + } + + /* fall back to EUI-64 if no tokens provided addresses */ + if (set_isempty(addresses)) { + _cleanup_free_ struct in6_addr *new_address = NULL; + + new_address = newdup(struct in6_addr, address, 1); + if (!new_address) + return log_oom(); + + r = generate_ipv6_eui_64_address(link, new_address); + if (r < 0) + return log_link_error_errno(link, r, "Failed to generate EUI64 address: %m"); + + r = set_put(addresses, new_address); + if (r < 0) + return log_link_error_errno(link, r, "Failed to store SLAAC address: %m"); + + TAKE_PTR(new_address); + } + + *ret = TAKE_PTR(addresses); + + return 0; +} + +static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *rt) { + uint32_t lifetime_valid, lifetime_preferred, lifetime_remaining; + _cleanup_set_free_free_ Set *addresses = NULL; + _cleanup_(address_freep) Address *address = NULL; + struct in6_addr addr, *a; + unsigned prefixlen; + usec_t time_now; + int r; + + assert(link); + assert(rt); + + r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get RA timestamp: %m"); + + r = sd_ndisc_router_prefix_get_prefixlen(rt, &prefixlen); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get prefix length: %m"); + + r = sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime_valid); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get prefix valid lifetime: %m"); + + r = sd_ndisc_router_prefix_get_preferred_lifetime(rt, &lifetime_preferred); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get prefix preferred lifetime: %m"); + + /* The preferred lifetime is never greater than the valid lifetime */ + if (lifetime_preferred > lifetime_valid) + return 0; + + r = sd_ndisc_router_prefix_get_address(rt, &addr); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get prefix address: %m"); + + r = ndisc_router_generate_addresses(link, &addr, prefixlen, &addresses); + if (r < 0) + return r; + + r = address_new(&address); + if (r < 0) + return log_oom(); + + address->family = AF_INET6; + address->prefixlen = prefixlen; + address->flags = IFA_F_NOPREFIXROUTE|IFA_F_MANAGETEMPADDR; + address->cinfo.ifa_prefered = lifetime_preferred; + + SET_FOREACH(a, addresses) { + Address *existing_address; + + address->in_addr.in6 = *a; + + /* see RFC4862 section 5.5.3.e */ + r = address_get(link, address, &existing_address); + if (r > 0) { + lifetime_remaining = existing_address->cinfo.tstamp / 100 + existing_address->cinfo.ifa_valid - time_now / USEC_PER_SEC; + if (lifetime_valid > NDISC_PREFIX_LFT_MIN || lifetime_valid > lifetime_remaining) + address->cinfo.ifa_valid = lifetime_valid; + else if (lifetime_remaining <= NDISC_PREFIX_LFT_MIN) + address->cinfo.ifa_valid = lifetime_remaining; + else + address->cinfo.ifa_valid = NDISC_PREFIX_LFT_MIN; + } else if (lifetime_valid > 0) + address->cinfo.ifa_valid = lifetime_valid; + else + continue; /* see RFC4862 section 5.5.3.d */ + + if (address->cinfo.ifa_valid == 0) + continue; + + r = ndisc_address_configure(address, link, rt); + if (r < 0) + return log_link_error_errno(link, r, "Could not set SLAAC address: %m"); + } + + return 0; +} + +static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) { + _cleanup_(route_freep) Route *route = NULL; + usec_t time_now; + uint32_t lifetime; + unsigned prefixlen; + int r; + + assert(link); + assert(rt); + + r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get RA timestamp: %m"); + + r = sd_ndisc_router_prefix_get_prefixlen(rt, &prefixlen); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get prefix length: %m"); + + r = sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get prefix lifetime: %m"); + + r = route_new(&route); + if (r < 0) + return log_oom(); + + route->family = AF_INET6; + route->table = link_get_ipv6_accept_ra_route_table(link); + route->priority = link->network->dhcp6_route_metric; + route->protocol = RTPROT_RA; + route->flags = RTM_F_PREFIX; + route->dst_prefixlen = prefixlen; + route->lifetime = time_now + lifetime * USEC_PER_SEC; + + r = sd_ndisc_router_prefix_get_address(rt, &route->dst.in6); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get prefix address: %m"); + + r = ndisc_route_configure(route, link, rt); + if (r < 0) + return log_link_error_errno(link, r, "Could not set prefix route: %m");; + + return 0; +} + +static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) { + _cleanup_(route_freep) Route *route = NULL; + struct in6_addr gateway; + uint32_t lifetime; + unsigned preference, prefixlen; + usec_t time_now; + int r; + + assert(link); + + r = sd_ndisc_router_route_get_lifetime(rt, &lifetime); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get gateway lifetime from RA: %m"); + + if (lifetime == 0) + return 0; + + r = sd_ndisc_router_get_address(rt, &gateway); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get gateway address from RA: %m"); + + r = sd_ndisc_router_route_get_prefixlen(rt, &prefixlen); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get route prefix length: %m"); + + r = sd_ndisc_router_route_get_preference(rt, &preference); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get default router preference from RA: %m"); + + r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get RA timestamp: %m"); + + r = route_new(&route); + if (r < 0) + return log_oom(); + + route->family = AF_INET6; + route->table = link_get_ipv6_accept_ra_route_table(link); + route->priority = link->network->dhcp6_route_metric; + route->protocol = RTPROT_RA; + route->pref = preference; + route->gw.in6 = gateway; + route->gw_family = AF_INET6; + route->dst_prefixlen = prefixlen; + route->lifetime = time_now + lifetime * USEC_PER_SEC; + + r = sd_ndisc_router_route_get_address(rt, &route->dst.in6); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get route address: %m"); + + r = ndisc_route_configure(route, link, rt); + if (r < 0) + return log_link_error_errno(link, r, "Could not set additional route: %m"); + + return 0; +} + +static void ndisc_rdnss_hash_func(const NDiscRDNSS *x, struct siphash *state) { + siphash24_compress(&x->address, sizeof(x->address), state); +} + +static int ndisc_rdnss_compare_func(const NDiscRDNSS *a, const NDiscRDNSS *b) { + return memcmp(&a->address, &b->address, sizeof(a->address)); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + ndisc_rdnss_hash_ops, + NDiscRDNSS, + ndisc_rdnss_hash_func, + ndisc_rdnss_compare_func, + free); + +static int ndisc_router_process_rdnss(Link *link, sd_ndisc_router *rt) { + uint32_t lifetime; + const struct in6_addr *a; + struct in6_addr router; + NDiscRDNSS *rdnss; + usec_t time_now; + int n, r; + + assert(link); + assert(rt); + + r = sd_ndisc_router_get_address(rt, &router); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get router address from RA: %m"); + + r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get RA timestamp: %m"); + + r = sd_ndisc_router_rdnss_get_lifetime(rt, &lifetime); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get RDNSS lifetime: %m"); + + n = sd_ndisc_router_rdnss_get_addresses(rt, &a); + if (n < 0) + return log_link_error_errno(link, n, "Failed to get RDNSS addresses: %m"); + + SET_FOREACH(rdnss, link->ndisc_rdnss) + if (IN6_ARE_ADDR_EQUAL(&rdnss->router, &router)) + rdnss->marked = true; + + if (lifetime == 0) + return 0; + + if (n >= (int) NDISC_RDNSS_MAX) { + log_link_warning(link, "Too many RDNSS records per link. Only first %i records will be used.", NDISC_RDNSS_MAX); + n = NDISC_RDNSS_MAX; + } + + for (int j = 0; j < n; j++) { + _cleanup_free_ NDiscRDNSS *x = NULL; + NDiscRDNSS d = { + .address = a[j], + }; + + rdnss = set_get(link->ndisc_rdnss, &d); + if (rdnss) { + rdnss->marked = false; + rdnss->router = router; + rdnss->valid_until = time_now + lifetime * USEC_PER_SEC; + continue; + } + + x = new(NDiscRDNSS, 1); + if (!x) + return log_oom(); + + *x = (NDiscRDNSS) { + .address = a[j], + .router = router, + .valid_until = time_now + lifetime * USEC_PER_SEC, + }; + + r = set_ensure_consume(&link->ndisc_rdnss, &ndisc_rdnss_hash_ops, TAKE_PTR(x)); + if (r < 0) + return log_oom(); + assert(r > 0); + } + + return 0; +} + +static void ndisc_dnssl_hash_func(const NDiscDNSSL *x, struct siphash *state) { + siphash24_compress_string(NDISC_DNSSL_DOMAIN(x), state); +} + +static int ndisc_dnssl_compare_func(const NDiscDNSSL *a, const NDiscDNSSL *b) { + return strcmp(NDISC_DNSSL_DOMAIN(a), NDISC_DNSSL_DOMAIN(b)); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + ndisc_dnssl_hash_ops, + NDiscDNSSL, + ndisc_dnssl_hash_func, + ndisc_dnssl_compare_func, + free); + +static int ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) { + _cleanup_strv_free_ char **l = NULL; + struct in6_addr router; + uint32_t lifetime; + usec_t time_now; + NDiscDNSSL *dnssl; + char **j; + int r; + + assert(link); + assert(rt); + + r = sd_ndisc_router_get_address(rt, &router); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get router address from RA: %m"); + + r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get RA timestamp: %m"); + + r = sd_ndisc_router_dnssl_get_lifetime(rt, &lifetime); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get DNSSL lifetime: %m"); + + r = sd_ndisc_router_dnssl_get_domains(rt, &l); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get DNSSL addresses: %m"); + + SET_FOREACH(dnssl, link->ndisc_dnssl) + if (IN6_ARE_ADDR_EQUAL(&dnssl->router, &router)) + dnssl->marked = true; + + if (lifetime == 0) + return 0; + + if (strv_length(l) >= NDISC_DNSSL_MAX) { + log_link_warning(link, "Too many DNSSL records per link. Only first %i records will be used.", NDISC_DNSSL_MAX); + STRV_FOREACH(j, l + NDISC_DNSSL_MAX) + *j = mfree(*j); + } + + STRV_FOREACH(j, l) { + _cleanup_free_ NDiscDNSSL *s = NULL; + + s = malloc0(ALIGN(sizeof(NDiscDNSSL)) + strlen(*j) + 1); + if (!s) + return log_oom(); + + strcpy(NDISC_DNSSL_DOMAIN(s), *j); + + dnssl = set_get(link->ndisc_dnssl, s); + if (dnssl) { + dnssl->marked = false; + dnssl->router = router; + dnssl->valid_until = time_now + lifetime * USEC_PER_SEC; + continue; + } + + s->router = router; + s->valid_until = time_now + lifetime * USEC_PER_SEC; + + r = set_ensure_consume(&link->ndisc_dnssl, &ndisc_dnssl_hash_ops, TAKE_PTR(s)); + if (r < 0) + return log_oom(); + assert(r > 0); + } + + return 0; +} + +static int ndisc_router_process_options(Link *link, sd_ndisc_router *rt) { + assert(link); + assert(link->network); + assert(rt); + + for (int r = sd_ndisc_router_option_rewind(rt); ; r = sd_ndisc_router_option_next(rt)) { + uint8_t type; + + if (r < 0) + return log_link_error_errno(link, r, "Failed to iterate through options: %m"); + if (r == 0) /* EOF */ + return 0; + + r = sd_ndisc_router_option_get_type(rt, &type); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get RA option type: %m"); + + switch (type) { + + case SD_NDISC_OPTION_PREFIX_INFORMATION: { + union in_addr_union a; + uint8_t flags; + + r = sd_ndisc_router_prefix_get_address(rt, &a.in6); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get prefix address: %m"); + + if (set_contains(link->network->ndisc_deny_listed_prefix, &a.in6)) { + if (DEBUG_LOGGING) { + _cleanup_free_ char *b = NULL; + + (void) in_addr_to_string(AF_INET6, &a, &b); + log_link_debug(link, "Prefix '%s' is deny-listed, ignoring", strna(b)); + } + break; + } + + r = sd_ndisc_router_prefix_get_flags(rt, &flags); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get RA prefix flags: %m"); + + if (link->network->ipv6_accept_ra_use_onlink_prefix && + FLAGS_SET(flags, ND_OPT_PI_FLAG_ONLINK)) { + r = ndisc_router_process_onlink_prefix(link, rt); + if (r < 0) + return r; + } + + if (link->network->ipv6_accept_ra_use_autonomous_prefix && + FLAGS_SET(flags, ND_OPT_PI_FLAG_AUTO)) { + r = ndisc_router_process_autonomous_prefix(link, rt); + if (r < 0) + return r; + } + break; + } + + case SD_NDISC_OPTION_ROUTE_INFORMATION: + r = ndisc_router_process_route(link, rt); + if (r < 0) + return r; + break; + + case SD_NDISC_OPTION_RDNSS: + if (link->network->ipv6_accept_ra_use_dns) { + r = ndisc_router_process_rdnss(link, rt); + if (r < 0) + return r; + } + break; + + case SD_NDISC_OPTION_DNSSL: + if (link->network->ipv6_accept_ra_use_dns) { + r = ndisc_router_process_dnssl(link, rt); + if (r < 0) + return r; + } + break; + } + } +} + +static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) { + struct in6_addr router; + uint64_t flags; + NDiscAddress *na; + NDiscRoute *nr; + int r; + + assert(link); + assert(link->network); + assert(link->manager); + assert(rt); + + link->ndisc_addresses_configured = false; + link->ndisc_routes_configured = false; + + link_dirty(link); + + r = sd_ndisc_router_get_address(rt, &router); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get router address from RA: %m"); + + SET_FOREACH(na, link->ndisc_addresses) + if (IN6_ARE_ADDR_EQUAL(&na->router, &router)) + na->marked = true; + + SET_FOREACH(nr, link->ndisc_routes) + if (IN6_ARE_ADDR_EQUAL(&nr->router, &router)) + nr->marked = true; + + r = sd_ndisc_router_get_flags(rt, &flags); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get RA flags: %m"); + + if ((flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER) && + link->network->ipv6_accept_ra_start_dhcp6_client != IPV6_ACCEPT_RA_START_DHCP6_CLIENT_NO) || + link->network->ipv6_accept_ra_start_dhcp6_client == IPV6_ACCEPT_RA_START_DHCP6_CLIENT_ALWAYS) { + + if (flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER)) + /* (re)start DHCPv6 client in stateful or stateless mode according to RA flags */ + r = dhcp6_request_address(link, !(flags & ND_RA_FLAG_MANAGED)); + else + /* When IPv6AcceptRA.DHCPv6Client=always, start dhcp6 client in managed mode + * even if router does not have M or O flag. */ + r = dhcp6_request_address(link, false); + if (r < 0 && r != -EBUSY) + return log_link_error_errno(link, r, "Could not acquire DHCPv6 lease on NDisc request: %m"); + else + log_link_debug(link, "Acquiring DHCPv6 lease on NDisc request"); + } + + r = ndisc_router_process_default(link, rt); + if (r < 0) + return r; + r = ndisc_router_process_options(link, rt); + if (r < 0) + return r; + + if (link->ndisc_addresses_messages == 0) + link->ndisc_addresses_configured = true; + else { + log_link_debug(link, "Setting SLAAC addresses."); + + /* address_handler calls link_set_routes() and link_set_nexthop(). Before they are + * called, the related flags must be cleared. Otherwise, the link becomes configured + * state before routes are configured. */ + link->static_routes_configured = false; + link->static_nexthops_configured = false; + } + + if (link->ndisc_routes_messages == 0) + link->ndisc_routes_configured = true; + else + log_link_debug(link, "Setting NDisc routes."); + + r = ndisc_remove_old(link); + if (r < 0) + return r; + + if (link->ndisc_addresses_configured && link->ndisc_routes_configured) + link_check_ready(link); + else + link_set_state(link, LINK_STATE_CONFIGURING); + + return 0; +} + +static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event event, sd_ndisc_router *rt, void *userdata) { + Link *link = userdata; + int r; + + assert(link); + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return; + + switch (event) { + + case SD_NDISC_EVENT_ROUTER: + r = ndisc_router_handler(link, rt); + if (r < 0) { + link_enter_failed(link); + return; + } + break; + + case SD_NDISC_EVENT_TIMEOUT: + log_link_debug(link, "NDisc handler get timeout event"); + if (link->ndisc_addresses_messages == 0 && link->ndisc_routes_messages == 0) { + link->ndisc_addresses_configured = true; + link->ndisc_routes_configured = true; + link_check_ready(link); + } + break; + default: + assert_not_reached("Unknown NDisc event"); + } +} + +int ndisc_configure(Link *link) { + int r; + + assert(link); + + if (!link_ipv6_accept_ra_enabled(link)) + return 0; + + if (!link->ndisc) { + r = sd_ndisc_new(&link->ndisc); + if (r < 0) + return r; + + r = sd_ndisc_attach_event(link->ndisc, link->manager->event, 0); + if (r < 0) + return r; + } + + r = sd_ndisc_set_mac(link->ndisc, &link->hw_addr.addr.ether); + if (r < 0) + return r; + + r = sd_ndisc_set_ifindex(link->ndisc, link->ifindex); + if (r < 0) + return r; + + r = sd_ndisc_set_callback(link->ndisc, ndisc_handler, link); + if (r < 0) + return r; + + return 0; +} + +void ndisc_vacuum(Link *link) { + NDiscRDNSS *r; + NDiscDNSSL *d; + usec_t time_now; + bool updated = false; + + assert(link); + + /* Removes all RDNSS and DNSSL entries whose validity time has passed */ + + time_now = now(clock_boottime_or_monotonic()); + + SET_FOREACH(r, link->ndisc_rdnss) + if (r->valid_until < time_now) { + free(set_remove(link->ndisc_rdnss, r)); + updated = true; + } + + SET_FOREACH(d, link->ndisc_dnssl) + if (d->valid_until < time_now) { + free(set_remove(link->ndisc_dnssl, d)); + updated = true; + } + + if (updated) + link_dirty(link); +} + +void ndisc_flush(Link *link) { + assert(link); + + /* Removes all RDNSS and DNSSL entries, without exception */ + + link->ndisc_rdnss = set_free(link->ndisc_rdnss); + link->ndisc_dnssl = set_free(link->ndisc_dnssl); +} + +int ipv6token_new(IPv6Token **ret) { + IPv6Token *p; + + p = new(IPv6Token, 1); + if (!p) + return -ENOMEM; + + *p = (IPv6Token) { + .address_generation_type = IPV6_TOKEN_ADDRESS_GENERATION_NONE, + }; + + *ret = TAKE_PTR(p); + + return 0; +} + +static void ipv6_token_hash_func(const IPv6Token *p, struct siphash *state) { + siphash24_compress(&p->address_generation_type, sizeof(p->address_generation_type), state); + siphash24_compress(&p->prefix, sizeof(p->prefix), state); +} + +static int ipv6_token_compare_func(const IPv6Token *a, const IPv6Token *b) { + int r; + + r = CMP(a->address_generation_type, b->address_generation_type); + if (r != 0) + return r; + + return memcmp(&a->prefix, &b->prefix, sizeof(struct in6_addr)); +} + +DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( + ipv6_token_hash_ops, + IPv6Token, + ipv6_token_hash_func, + ipv6_token_compare_func, + free); + +int config_parse_ndisc_deny_listed_prefix( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = data; + const char *p; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + network->ndisc_deny_listed_prefix = set_free_free(network->ndisc_deny_listed_prefix); + return 0; + } + + for (p = rvalue;;) { + _cleanup_free_ char *n = NULL; + _cleanup_free_ struct in6_addr *a = NULL; + union in_addr_union ip; + + r = extract_first_word(&p, &n, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse NDisc deny-listed prefix, ignoring assignment: %s", + rvalue); + return 0; + } + if (r == 0) + return 0; + + r = in_addr_from_string(AF_INET6, n, &ip); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "NDisc deny-listed prefix is invalid, ignoring assignment: %s", n); + continue; + } + + if (set_contains(network->ndisc_deny_listed_prefix, &ip.in6)) + continue; + + a = newdup(struct in6_addr, &ip.in6, 1); + if (!a) + return log_oom(); + + r = set_ensure_consume(&network->ndisc_deny_listed_prefix, &in6_addr_hash_ops, TAKE_PTR(a)); + if (r < 0) + return log_oom(); + } +} + +int config_parse_address_generation_type( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_free_ IPv6Token *token = NULL; + union in_addr_union buffer; + Network *network = data; + const char *p; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + network->ipv6_tokens = ordered_set_free(network->ipv6_tokens); + return 0; + } + + r = ipv6token_new(&token); + if (r < 0) + return log_oom(); + + if ((p = startswith(rvalue, "prefixstable"))) { + token->address_generation_type = IPV6_TOKEN_ADDRESS_GENERATION_PREFIXSTABLE; + if (*p == ':') + p++; + else if (*p == '\0') + p = NULL; + else { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid IPv6 token mode in %s=, ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + } else { + token->address_generation_type = IPV6_TOKEN_ADDRESS_GENERATION_STATIC; + p = startswith(rvalue, "static:"); + if (!p) + p = rvalue; + } + + if (p) { + r = in_addr_from_string(AF_INET6, p, &buffer); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse IP address in %s=, ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + if (token->address_generation_type == IPV6_TOKEN_ADDRESS_GENERATION_STATIC && + in_addr_is_null(AF_INET6, &buffer)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "IPv6 address in %s= cannot be the ANY address, ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + token->prefix = buffer.in6; + } + + r = ordered_set_ensure_allocated(&network->ipv6_tokens, &ipv6_token_hash_ops); + if (r < 0) + return log_oom(); + + r = ordered_set_put(network->ipv6_tokens, token); + if (r == -EEXIST) + log_syntax(unit, LOG_DEBUG, filename, line, r, + "IPv6 token '%s' is duplicated, ignoring: %m", rvalue); + else if (r < 0) + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to store IPv6 token '%s', ignoring: %m", rvalue); + else + TAKE_PTR(token); + + return 0; +} + +DEFINE_CONFIG_PARSE_ENUM(config_parse_ipv6_accept_ra_start_dhcp6_client, ipv6_accept_ra_start_dhcp6_client, IPv6AcceptRAStartDHCP6Client, + "Failed to parse DHCPv6Client= setting") +static const char* const ipv6_accept_ra_start_dhcp6_client_table[_IPV6_ACCEPT_RA_START_DHCP6_CLIENT_MAX] = { + [IPV6_ACCEPT_RA_START_DHCP6_CLIENT_NO] = "no", + [IPV6_ACCEPT_RA_START_DHCP6_CLIENT_ALWAYS] = "always", + [IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES] = "yes", +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(ipv6_accept_ra_start_dhcp6_client, IPv6AcceptRAStartDHCP6Client, IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES); diff --git a/src/network/networkd-ndisc.h b/src/network/networkd-ndisc.h new file mode 100644 index 0000000..1562411 --- /dev/null +++ b/src/network/networkd-ndisc.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "conf-parser.h" +#include "networkd-address.h" +#include "networkd-link.h" +#include "networkd-route.h" +#include "time-util.h" + +typedef struct IPv6Token IPv6Token; + +typedef enum IPv6TokenAddressGeneration { + IPV6_TOKEN_ADDRESS_GENERATION_NONE, + IPV6_TOKEN_ADDRESS_GENERATION_STATIC, + IPV6_TOKEN_ADDRESS_GENERATION_PREFIXSTABLE, + _IPV6_TOKEN_ADDRESS_GENERATION_MAX, + _IPV6_TOKEN_ADDRESS_GENERATION_INVALID = -1, +} IPv6TokenAddressGeneration; + +typedef enum IPv6AcceptRAStartDHCP6Client { + IPV6_ACCEPT_RA_START_DHCP6_CLIENT_NO, + IPV6_ACCEPT_RA_START_DHCP6_CLIENT_ALWAYS, + IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES, + _IPV6_ACCEPT_RA_START_DHCP6_CLIENT_MAX, + _IPV6_ACCEPT_RA_START_DHCP6_CLIENT_INVALID = -1, +} IPv6AcceptRAStartDHCP6Client; + +typedef struct NDiscAddress { + /* Used when GC'ing old DNS servers when configuration changes. */ + bool marked; + struct in6_addr router; + Address *address; +} NDiscAddress; + +typedef struct NDiscRoute { + /* Used when GC'ing old DNS servers when configuration changes. */ + bool marked; + struct in6_addr router; + Route *route; +} NDiscRoute; + +typedef struct NDiscRDNSS { + /* Used when GC'ing old DNS servers when configuration changes. */ + bool marked; + struct in6_addr router; + usec_t valid_until; + struct in6_addr address; +} NDiscRDNSS; + +typedef struct NDiscDNSSL { + /* Used when GC'ing old domains when configuration changes. */ + bool marked; + struct in6_addr router; + usec_t valid_until; + /* The domain name follows immediately. */ +} NDiscDNSSL; + +struct IPv6Token { + IPv6TokenAddressGeneration address_generation_type; + + uint8_t dad_counter; + struct in6_addr prefix; +}; + +int ipv6token_new(IPv6Token **ret); +DEFINE_TRIVIAL_CLEANUP_FUNC(IPv6Token *, freep); + +static inline char* NDISC_DNSSL_DOMAIN(const NDiscDNSSL *n) { + return ((char*) n) + ALIGN(sizeof(NDiscDNSSL)); +} + +bool link_ipv6_accept_ra_enabled(Link *link); + +void network_adjust_ipv6_accept_ra(Network *network); + +int ndisc_configure(Link *link); +void ndisc_vacuum(Link *link); +void ndisc_flush(Link *link); + +CONFIG_PARSER_PROTOTYPE(config_parse_ndisc_deny_listed_prefix); +CONFIG_PARSER_PROTOTYPE(config_parse_address_generation_type); +CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_accept_ra_start_dhcp6_client); + +const char* ipv6_accept_ra_start_dhcp6_client_to_string(IPv6AcceptRAStartDHCP6Client i) _const_; +IPv6AcceptRAStartDHCP6Client ipv6_accept_ra_start_dhcp6_client_from_string(const char *s) _pure_; diff --git a/src/network/networkd-neighbor.c b/src/network/networkd-neighbor.c new file mode 100644 index 0000000..c805d52 --- /dev/null +++ b/src/network/networkd-neighbor.c @@ -0,0 +1,725 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "hashmap.h" +#include "netlink-util.h" +#include "networkd-link.h" +#include "networkd-manager.h" +#include "networkd-neighbor.h" +#include "networkd-network.h" +#include "set.h" + +Neighbor *neighbor_free(Neighbor *neighbor) { + if (!neighbor) + return NULL; + + if (neighbor->network) { + assert(neighbor->section); + hashmap_remove(neighbor->network->neighbors_by_section, neighbor->section); + } + + network_config_section_free(neighbor->section); + + if (neighbor->link) { + set_remove(neighbor->link->neighbors, neighbor); + set_remove(neighbor->link->neighbors_foreign, neighbor); + } + + return mfree(neighbor); +} + +DEFINE_NETWORK_SECTION_FUNCTIONS(Neighbor, neighbor_free); + +static int neighbor_new_static(Network *network, const char *filename, unsigned section_line, Neighbor **ret) { + _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; + _cleanup_(neighbor_freep) Neighbor *neighbor = NULL; + int r; + + assert(network); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = network_config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + neighbor = hashmap_get(network->neighbors_by_section, n); + if (neighbor) { + *ret = TAKE_PTR(neighbor); + return 0; + } + + neighbor = new(Neighbor, 1); + if (!neighbor) + return -ENOMEM; + + *neighbor = (Neighbor) { + .network = network, + .family = AF_UNSPEC, + .section = TAKE_PTR(n), + }; + + r = hashmap_ensure_allocated(&network->neighbors_by_section, &network_config_hash_ops); + if (r < 0) + return r; + + r = hashmap_put(network->neighbors_by_section, neighbor->section, neighbor); + if (r < 0) + return r; + + *ret = TAKE_PTR(neighbor); + return 0; +} + +static void neighbor_hash_func(const Neighbor *neighbor, struct siphash *state) { + assert(neighbor); + + siphash24_compress(&neighbor->family, sizeof(neighbor->family), state); + siphash24_compress(&neighbor->lladdr_size, sizeof(neighbor->lladdr_size), state); + + switch (neighbor->family) { + case AF_INET: + case AF_INET6: + /* Equality of neighbors are given by the pair (addr,lladdr) */ + siphash24_compress(&neighbor->in_addr, FAMILY_ADDRESS_SIZE(neighbor->family), state); + break; + default: + /* treat any other address family as AF_UNSPEC */ + break; + } + + siphash24_compress(&neighbor->lladdr, neighbor->lladdr_size, state); +} + +static int neighbor_compare_func(const Neighbor *a, const Neighbor *b) { + int r; + + r = CMP(a->family, b->family); + if (r != 0) + return r; + + r = CMP(a->lladdr_size, b->lladdr_size); + if (r != 0) + return r; + + switch (a->family) { + case AF_INET: + case AF_INET6: + r = memcmp(&a->in_addr, &b->in_addr, FAMILY_ADDRESS_SIZE(a->family)); + if (r != 0) + return r; + } + + return memcmp(&a->lladdr, &b->lladdr, a->lladdr_size); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(neighbor_hash_ops, Neighbor, neighbor_hash_func, neighbor_compare_func, neighbor_free); + +static int neighbor_get(Link *link, const Neighbor *in, Neighbor **ret) { + Neighbor *existing; + + assert(link); + assert(in); + + existing = set_get(link->neighbors, in); + if (existing) { + if (ret) + *ret = existing; + return 1; + } + + existing = set_get(link->neighbors_foreign, in); + if (existing) { + if (ret) + *ret = existing; + return 0; + } + + return -ENOENT; +} + +static int neighbor_add_internal(Link *link, Set **neighbors, const Neighbor *in, Neighbor **ret) { + _cleanup_(neighbor_freep) Neighbor *neighbor = NULL; + int r; + + assert(link); + assert(neighbors); + assert(in); + + neighbor = new(Neighbor, 1); + if (!neighbor) + return -ENOMEM; + + *neighbor = (Neighbor) { + .family = in->family, + .in_addr = in->in_addr, + .lladdr = in->lladdr, + .lladdr_size = in->lladdr_size, + }; + + r = set_ensure_put(neighbors, &neighbor_hash_ops, neighbor); + if (r < 0) + return r; + if (r == 0) + return -EEXIST; + + neighbor->link = link; + + if (ret) + *ret = neighbor; + + TAKE_PTR(neighbor); + return 0; +} + +static int neighbor_add(Link *link, const Neighbor *in, Neighbor **ret) { + Neighbor *neighbor; + int r; + + r = neighbor_get(link, in, &neighbor); + if (r == -ENOENT) { + /* Neighbor doesn't exist, make a new one */ + r = neighbor_add_internal(link, &link->neighbors, in, &neighbor); + if (r < 0) + return r; + } else if (r == 0) { + /* Neighbor is foreign, claim it as recognized */ + r = set_ensure_put(&link->neighbors, &neighbor_hash_ops, neighbor); + if (r < 0) + return r; + + set_remove(link->neighbors_foreign, neighbor); + } else if (r == 1) { + /* Neighbor already exists */ + } else + return r; + + if (ret) + *ret = neighbor; + return 0; +} + +static int neighbor_add_foreign(Link *link, const Neighbor *in, Neighbor **ret) { + return neighbor_add_internal(link, &link->neighbors_foreign, in, ret); +} + +static bool neighbor_equal(const Neighbor *n1, const Neighbor *n2) { + if (n1 == n2) + return true; + + if (!n1 || !n2) + return false; + + return neighbor_compare_func(n1, n2) == 0; +} + +static int neighbor_configure_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(m); + assert(link); + assert(link->neighbor_messages > 0); + + link->neighbor_messages--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EEXIST) + /* Neighbor may not exist yet. So, do not enter failed state here. */ + log_link_message_warning_errno(link, m, r, "Could not set neighbor, ignoring"); + + if (link->neighbor_messages == 0) { + log_link_debug(link, "Neighbors set"); + link->neighbors_configured = true; + link_check_ready(link); + } + + return 1; +} + +static int neighbor_configure(Neighbor *neighbor, Link *link) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(neighbor); + assert(link); + assert(link->ifindex > 0); + assert(link->manager); + assert(link->manager->rtnl); + + r = sd_rtnl_message_new_neigh(link->manager->rtnl, &req, RTM_NEWNEIGH, + link->ifindex, neighbor->family); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate RTM_NEWNEIGH message: %m"); + + r = sd_rtnl_message_neigh_set_state(req, NUD_PERMANENT); + if (r < 0) + return log_link_error_errno(link, r, "Could not set state: %m"); + + r = sd_netlink_message_set_flags(req, NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_REPLACE); + if (r < 0) + return log_link_error_errno(link, r, "Could not set flags: %m"); + + r = sd_netlink_message_append_data(req, NDA_LLADDR, &neighbor->lladdr, neighbor->lladdr_size); + if (r < 0) + return log_link_error_errno(link, r, "Could not append NDA_LLADDR attribute: %m"); + + r = netlink_message_append_in_addr_union(req, NDA_DST, neighbor->family, &neighbor->in_addr); + if (r < 0) + return log_link_error_errno(link, r, "Could not append NDA_DST attribute: %m"); + + r = netlink_call_async(link->manager->rtnl, NULL, req, neighbor_configure_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link->neighbor_messages++; + link_ref(link); + + r = neighbor_add(link, neighbor, NULL); + if (r < 0) + return log_link_error_errno(link, r, "Could not add neighbor: %m"); + + return 0; +} + +int link_set_neighbors(Link *link) { + Neighbor *neighbor; + int r; + + assert(link); + assert(link->network); + assert(link->state != _LINK_STATE_INVALID); + + link->neighbors_configured = false; + + HASHMAP_FOREACH(neighbor, link->network->neighbors_by_section) { + r = neighbor_configure(neighbor, link); + if (r < 0) + return log_link_warning_errno(link, r, "Could not set neighbor: %m"); + } + + if (link->neighbor_messages == 0) { + link->neighbors_configured = true; + link_check_ready(link); + } else { + log_link_debug(link, "Setting neighbors"); + link_set_state(link, LINK_STATE_CONFIGURING); + } + + return 0; +} + +static int neighbor_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(m); + assert(link); + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -ESRCH) + /* Neighbor may not exist because it already got deleted, ignore that. */ + log_link_message_warning_errno(link, m, r, "Could not remove neighbor"); + + return 1; +} + +static int neighbor_remove(Neighbor *neighbor, Link *link) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(neighbor); + assert(link); + assert(link->ifindex > 0); + assert(link->manager); + assert(link->manager->rtnl); + + r = sd_rtnl_message_new_neigh(link->manager->rtnl, &req, RTM_DELNEIGH, + link->ifindex, neighbor->family); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate RTM_DELNEIGH message: %m"); + + r = netlink_message_append_in_addr_union(req, NDA_DST, neighbor->family, &neighbor->in_addr); + if (r < 0) + return log_link_error_errno(link, r, "Could not append NDA_DST attribute: %m"); + + r = netlink_call_async(link->manager->rtnl, NULL, req, neighbor_remove_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + return 0; +} + +static bool link_is_neighbor_configured(Link *link, Neighbor *neighbor) { + Neighbor *net_neighbor; + + assert(link); + assert(neighbor); + + if (!link->network) + return false; + + HASHMAP_FOREACH(net_neighbor, link->network->neighbors_by_section) + if (neighbor_equal(net_neighbor, neighbor)) + return true; + + return false; +} + +int link_drop_foreign_neighbors(Link *link) { + Neighbor *neighbor; + int r; + + assert(link); + + SET_FOREACH(neighbor, link->neighbors_foreign) + if (link_is_neighbor_configured(link, neighbor)) { + r = neighbor_add(link, neighbor, NULL); + if (r < 0) + return r; + } else { + r = neighbor_remove(neighbor, link); + if (r < 0) + return r; + } + + return 0; +} + +int link_drop_neighbors(Link *link) { + Neighbor *neighbor; + int k, r = 0; + + assert(link); + + SET_FOREACH(neighbor, link->neighbors) { + k = neighbor_remove(neighbor, link); + if (k < 0 && r >= 0) + r = k; + } + + return r; +} + +static int manager_rtnl_process_neighbor_lladdr(sd_netlink_message *message, union lladdr_union *lladdr, size_t *size, char **str) { + int r; + + assert(message); + assert(lladdr); + assert(size); + assert(str); + + *str = NULL; + + r = sd_netlink_message_read(message, NDA_LLADDR, sizeof(lladdr->ip.in6), &lladdr->ip.in6); + if (r >= 0) { + *size = sizeof(lladdr->ip.in6); + if (in_addr_to_string(AF_INET6, &lladdr->ip, str) < 0) + log_warning_errno(r, "Could not print lower address: %m"); + return r; + } + + r = sd_netlink_message_read(message, NDA_LLADDR, sizeof(lladdr->mac), &lladdr->mac); + if (r >= 0) { + *size = sizeof(lladdr->mac); + *str = new(char, ETHER_ADDR_TO_STRING_MAX); + if (!*str) { + log_oom(); + return r; + } + ether_addr_to_string(&lladdr->mac, *str); + return r; + } + + r = sd_netlink_message_read(message, NDA_LLADDR, sizeof(lladdr->ip.in), &lladdr->ip.in); + if (r >= 0) { + *size = sizeof(lladdr->ip.in); + if (in_addr_to_string(AF_INET, &lladdr->ip, str) < 0) + log_warning_errno(r, "Could not print lower address: %m"); + return r; + } + + return r; +} + +int manager_rtnl_process_neighbor(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) { + _cleanup_(neighbor_freep) Neighbor *tmp = NULL; + _cleanup_free_ char *addr_str = NULL, *lladdr_str = NULL; + Neighbor *neighbor = NULL; + uint16_t type, state; + int ifindex, r; + Link *link; + + assert(rtnl); + assert(message); + assert(m); + + if (sd_netlink_message_is_error(message)) { + r = sd_netlink_message_get_errno(message); + if (r < 0) + log_message_warning_errno(message, r, "rtnl: failed to receive neighbor message, ignoring"); + + return 0; + } + + r = sd_netlink_message_get_type(message, &type); + if (r < 0) { + log_warning_errno(r, "rtnl: could not get message type, ignoring: %m"); + return 0; + } else if (!IN_SET(type, RTM_NEWNEIGH, RTM_DELNEIGH)) { + log_warning("rtnl: received unexpected message type %u when processing neighbor, ignoring.", type); + return 0; + } + + r = sd_rtnl_message_neigh_get_state(message, &state); + if (r < 0) { + log_warning_errno(r, "rtnl: received neighbor message with invalid state, ignoring: %m"); + return 0; + } else if (!FLAGS_SET(state, NUD_PERMANENT)) { + log_debug("rtnl: received non-static neighbor, ignoring."); + return 0; + } + + r = sd_rtnl_message_neigh_get_ifindex(message, &ifindex); + if (r < 0) { + log_warning_errno(r, "rtnl: could not get ifindex from message, ignoring: %m"); + return 0; + } else if (ifindex <= 0) { + log_warning("rtnl: received neighbor message with invalid ifindex %d, ignoring.", ifindex); + return 0; + } + + r = link_get(m, ifindex, &link); + if (r < 0 || !link) { + /* when enumerating we might be out of sync, but we will get the neighbor again, so just + * ignore it */ + if (!m->enumerating) + log_warning("rtnl: received neighbor for link '%d' we don't know about, ignoring.", ifindex); + return 0; + } + + tmp = new0(Neighbor, 1); + + r = sd_rtnl_message_neigh_get_family(message, &tmp->family); + if (r < 0) { + log_link_warning(link, "rtnl: received neighbor message without family, ignoring."); + return 0; + } else if (!IN_SET(tmp->family, AF_INET, AF_INET6)) { + log_link_debug(link, "rtnl: received neighbor message with invalid family '%i', ignoring.", tmp->family); + return 0; + } + + r = netlink_message_read_in_addr_union(message, NDA_DST, tmp->family, &tmp->in_addr); + if (r < 0) { + log_link_warning_errno(link, r, "rtnl: received neighbor message without valid address, ignoring: %m"); + return 0; + } + + if (in_addr_to_string(tmp->family, &tmp->in_addr, &addr_str) < 0) + log_link_warning_errno(link, r, "Could not print address: %m"); + + r = manager_rtnl_process_neighbor_lladdr(message, &tmp->lladdr, &tmp->lladdr_size, &lladdr_str); + if (r < 0) { + log_link_warning_errno(link, r, "rtnl: received neighbor message with invalid lladdr, ignoring: %m"); + return 0; + } + + (void) neighbor_get(link, tmp, &neighbor); + + switch (type) { + case RTM_NEWNEIGH: + if (neighbor) + log_link_debug(link, "Received remembered neighbor: %s->%s", + strnull(addr_str), strnull(lladdr_str)); + else { + /* A neighbor appeared that we did not request */ + r = neighbor_add_foreign(link, tmp, NULL); + if (r < 0) { + log_link_warning_errno(link, r, "Failed to remember foreign neighbor %s->%s, ignoring: %m", + strnull(addr_str), strnull(lladdr_str)); + return 0; + } else + log_link_debug(link, "Remembering foreign neighbor: %s->%s", + strnull(addr_str), strnull(lladdr_str)); + } + + break; + + case RTM_DELNEIGH: + if (neighbor) { + log_link_debug(link, "Forgetting neighbor: %s->%s", + strnull(addr_str), strnull(lladdr_str)); + (void) neighbor_free(neighbor); + } else + log_link_debug(link, "Kernel removed a neighbor we don't remember: %s->%s, ignoring.", + strnull(addr_str), strnull(lladdr_str)); + + break; + + default: + assert_not_reached("Received invalid RTNL message type"); + } + + return 1; +} + +static int neighbor_section_verify(Neighbor *neighbor) { + if (section_is_invalid(neighbor->section)) + return -EINVAL; + + if (neighbor->family == AF_UNSPEC) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Neighbor section without Address= configured. " + "Ignoring [Neighbor] section from line %u.", + neighbor->section->filename, neighbor->section->line); + + if (neighbor->lladdr_size == 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Neighbor section without LinkLayerAddress= configured. " + "Ignoring [Neighbor] section from line %u.", + neighbor->section->filename, neighbor->section->line); + + return 0; +} + +void network_drop_invalid_neighbors(Network *network) { + Neighbor *neighbor; + + assert(network); + + HASHMAP_FOREACH(neighbor, network->neighbors_by_section) + if (neighbor_section_verify(neighbor) < 0) + neighbor_free(neighbor); +} + + +int config_parse_neighbor_address( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(neighbor_free_or_set_invalidp) Neighbor *n = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = neighbor_new_static(network, filename, section_line, &n); + if (r < 0) + return log_oom(); + + r = in_addr_from_string_auto(rvalue, &n->family, &n->in_addr); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Neighbor Address is invalid, ignoring assignment: %s", rvalue); + return 0; + } + + TAKE_PTR(n); + + return 0; +} + +int config_parse_neighbor_lladdr( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(neighbor_free_or_set_invalidp) Neighbor *n = NULL; + int family, r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = neighbor_new_static(network, filename, section_line, &n); + if (r < 0) + return log_oom(); + + r = ether_addr_from_string(rvalue, &n->lladdr.mac); + if (r >= 0) + n->lladdr_size = sizeof(n->lladdr.mac); + else { + r = in_addr_from_string_auto(rvalue, &family, &n->lladdr.ip); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Neighbor LinkLayerAddress= is invalid, ignoring assignment: %s", + rvalue); + return 0; + } + n->lladdr_size = family == AF_INET ? sizeof(n->lladdr.ip.in) : sizeof(n->lladdr.ip.in6); + } + + TAKE_PTR(n); + + return 0; +} + +int config_parse_neighbor_hwaddr( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(neighbor_free_or_set_invalidp) Neighbor *n = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = neighbor_new_static(network, filename, section_line, &n); + if (r < 0) + return log_oom(); + + r = ether_addr_from_string(rvalue, &n->lladdr.mac); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Neighbor MACAddress= is invalid, ignoring assignment: %s", rvalue); + return 0; + } + + n->lladdr_size = sizeof(n->lladdr.mac); + TAKE_PTR(n); + + return 0; +} diff --git a/src/network/networkd-neighbor.h b/src/network/networkd-neighbor.h new file mode 100644 index 0000000..8ad790b --- /dev/null +++ b/src/network/networkd-neighbor.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <stdbool.h> + +#include "sd-netlink.h" + +#include "conf-parser.h" +#include "ether-addr-util.h" +#include "in-addr-util.h" +#include "networkd-util.h" + +typedef Manager Manager; +typedef Network Network; +typedef Link Link; + +union lladdr_union { + struct ether_addr mac; + union in_addr_union ip; +}; + +typedef struct Neighbor { + Network *network; + Link *link; + NetworkConfigSection *section; + + int family; + union in_addr_union in_addr; + union lladdr_union lladdr; + size_t lladdr_size; +} Neighbor; + +Neighbor *neighbor_free(Neighbor *neighbor); + +void network_drop_invalid_neighbors(Network *network); + +int link_set_neighbors(Link *link); +int link_drop_neighbors(Link *link); +int link_drop_foreign_neighbors(Link *link); + +int manager_rtnl_process_neighbor(sd_netlink *rtnl, sd_netlink_message *message, Manager *m); + +CONFIG_PARSER_PROTOTYPE(config_parse_neighbor_address); +CONFIG_PARSER_PROTOTYPE(config_parse_neighbor_hwaddr); +CONFIG_PARSER_PROTOTYPE(config_parse_neighbor_lladdr); diff --git a/src/network/networkd-network-bus.c b/src/network/networkd-network-bus.c new file mode 100644 index 0000000..0e5f148 --- /dev/null +++ b/src/network/networkd-network-bus.c @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "ether-addr-util.h" +#include "networkd-manager.h" +#include "networkd-network-bus.h" +#include "string-util.h" +#include "strv.h" + +static int property_get_ether_addrs( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + char buf[ETHER_ADDR_TO_STRING_MAX]; + const struct ether_addr *p; + Set *s; + int r; + + assert(bus); + assert(reply); + assert(userdata); + + s = *(Set **) userdata; + + r = sd_bus_message_open_container(reply, 'a', "s"); + if (r < 0) + return r; + + SET_FOREACH(p, s) { + r = sd_bus_message_append(reply, "s", ether_addr_to_string(p, buf)); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +const sd_bus_vtable network_vtable[] = { + SD_BUS_VTABLE_START(0), + + SD_BUS_PROPERTY("Description", "s", NULL, offsetof(Network, description), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SourcePath", "s", NULL, offsetof(Network, filename), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("MatchMAC", "as", property_get_ether_addrs, offsetof(Network, match_mac), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("MatchPath", "as", NULL, offsetof(Network, match_path), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("MatchDriver", "as", NULL, offsetof(Network, match_driver), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("MatchType", "as", NULL, offsetof(Network, match_type), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("MatchName", "as", NULL, offsetof(Network, match_name), SD_BUS_VTABLE_PROPERTY_CONST), + + SD_BUS_VTABLE_END +}; + +static char *network_bus_path(Network *network) { + _cleanup_free_ char *name = NULL; + char *networkname, *d, *path; + int r; + + assert(network); + assert(network->filename); + + name = strdup(network->filename); + if (!name) + return NULL; + + networkname = basename(name); + + d = strrchr(networkname, '.'); + if (!d) + return NULL; + + assert(streq(d, ".network")); + + *d = '\0'; + + r = sd_bus_path_encode("/org/freedesktop/network1/network", networkname, &path); + if (r < 0) + return NULL; + + return path; +} + +int network_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { + _cleanup_strv_free_ char **l = NULL; + Manager *m = userdata; + Network *network; + int r; + + assert(bus); + assert(path); + assert(m); + assert(nodes); + + ORDERED_HASHMAP_FOREACH(network, m->networks) { + char *p; + + p = network_bus_path(network); + if (!p) + return -ENOMEM; + + r = strv_consume(&l, p); + if (r < 0) + return r; + } + + *nodes = TAKE_PTR(l); + + return 1; +} + +int network_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + Manager *m = userdata; + Network *network; + _cleanup_free_ char *name = NULL; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(m); + assert(found); + + r = sd_bus_path_decode(path, "/org/freedesktop/network1/network", &name); + if (r < 0) + return 0; + + r = network_get_by_name(m, name, &network); + if (r < 0) + return 0; + + *found = network; + + return 1; +} diff --git a/src/network/networkd-network-bus.h b/src/network/networkd-network-bus.h new file mode 100644 index 0000000..cca1e0a --- /dev/null +++ b/src/network/networkd-network-bus.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-bus.h" + +typedef struct Link Link; + +extern const sd_bus_vtable network_vtable[]; + +int network_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error); +int network_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf new file mode 100644 index 0000000..5cc9e3e --- /dev/null +++ b/src/network/networkd-network-gperf.gperf @@ -0,0 +1,482 @@ +%{ +#if __GNUC__ >= 7 +_Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"") +#endif +#include <stddef.h> +#include "conf-parser.h" +#include "netem.h" +#include "network-internal.h" +#include "networkd-address-label.h" +#include "networkd-address.h" +#include "networkd-can.h" +#include "networkd-conf.h" +#include "networkd-dhcp-common.h" +#include "networkd-dhcp-server.h" +#include "networkd-dhcp4.h" +#include "networkd-dhcp6.h" +#include "networkd-fdb.h" +#include "networkd-ipv4ll.h" +#include "networkd-ipv6-proxy-ndp.h" +#include "networkd-mdb.h" +#include "networkd-ndisc.h" +#include "networkd-network.h" +#include "networkd-neighbor.h" +#include "networkd-nexthop.h" +#include "networkd-radv.h" +#include "networkd-route.h" +#include "networkd-routing-policy-rule.h" +#include "networkd-sriov.h" +#include "qdisc.h" +#include "tclass.h" +#include "vlan-util.h" +%} +struct ConfigPerfItem; +%null_strings +%language=ANSI-C +%define slot-name section_and_lvalue +%define hash-function-name network_network_gperf_hash +%define lookup-function-name network_network_gperf_lookup +%readonly-tables +%omit-struct-type +%struct-type +%includes +%% +Match.MACAddress, config_parse_hwaddrs, 0, offsetof(Network, match_mac) +Match.PermanentMACAddress, config_parse_hwaddrs, 0, offsetof(Network, match_permanent_mac) +Match.Path, config_parse_match_strv, 0, offsetof(Network, match_path) +Match.Driver, config_parse_match_strv, 0, offsetof(Network, match_driver) +Match.Type, config_parse_match_strv, 0, offsetof(Network, match_type) +Match.WLANInterfaceType, config_parse_match_strv, 0, offsetof(Network, match_wlan_iftype) +Match.SSID, config_parse_match_strv, 0, offsetof(Network, match_ssid) +Match.BSSID, config_parse_hwaddrs, 0, offsetof(Network, match_bssid) +Match.Name, config_parse_match_ifnames, IFNAME_VALID_ALTERNATIVE, offsetof(Network, match_name) +Match.Property, config_parse_match_property, 0, offsetof(Network, match_property) +Match.Host, config_parse_net_condition, CONDITION_HOST, offsetof(Network, conditions) +Match.Virtualization, config_parse_net_condition, CONDITION_VIRTUALIZATION, offsetof(Network, conditions) +Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(Network, conditions) +Match.KernelVersion, config_parse_net_condition, CONDITION_KERNEL_VERSION, offsetof(Network, conditions) +Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(Network, conditions) +Link.MACAddress, config_parse_hwaddr, 0, offsetof(Network, mac) +Link.MTUBytes, config_parse_mtu, AF_UNSPEC, offsetof(Network, mtu) +Link.Group, config_parse_uint32, 0, offsetof(Network, group) +Link.ARP, config_parse_tristate, 0, offsetof(Network, arp) +Link.Multicast, config_parse_tristate, 0, offsetof(Network, multicast) +Link.AllMulticast, config_parse_tristate, 0, offsetof(Network, allmulticast) +Link.Unmanaged, config_parse_bool, 0, offsetof(Network, unmanaged) +Link.RequiredForOnline, config_parse_required_for_online, 0, 0 +SR-IOV.VirtualFunction, config_parse_sr_iov_uint32, 0, 0 +SR-IOV.VLANId, config_parse_sr_iov_uint32, 0, 0 +SR-IOV.QualityOfService, config_parse_sr_iov_uint32, 0, 0 +SR-IOV.VLANProtocol, config_parse_sr_iov_vlan_proto, 0, 0 +SR-IOV.MACSpoofCheck, config_parse_sr_iov_boolean, 0, 0 +SR-IOV.QueryReceiveSideScaling, config_parse_sr_iov_boolean, 0, 0 +SR-IOV.Trust, config_parse_sr_iov_boolean, 0, 0 +SR-IOV.LinkState, config_parse_sr_iov_link_state, 0, 0 +SR-IOV.MACAddress, config_parse_sr_iov_mac, 0, 0 +Network.Description, config_parse_string, 0, offsetof(Network, description) +Network.Bridge, config_parse_ifname, 0, offsetof(Network, bridge_name) +Network.Bond, config_parse_ifname, 0, offsetof(Network, bond_name) +Network.VLAN, config_parse_stacked_netdev, NETDEV_KIND_VLAN, offsetof(Network, stacked_netdev_names) +Network.MACVLAN, config_parse_stacked_netdev, NETDEV_KIND_MACVLAN, offsetof(Network, stacked_netdev_names) +Network.MACVTAP, config_parse_stacked_netdev, NETDEV_KIND_MACVTAP, offsetof(Network, stacked_netdev_names) +Network.IPVLAN, config_parse_stacked_netdev, NETDEV_KIND_IPVLAN, offsetof(Network, stacked_netdev_names) +Network.IPVTAP, config_parse_stacked_netdev, NETDEV_KIND_IPVTAP, offsetof(Network, stacked_netdev_names) +Network.VXLAN, config_parse_stacked_netdev, NETDEV_KIND_VXLAN, offsetof(Network, stacked_netdev_names) +Network.L2TP, config_parse_stacked_netdev, NETDEV_KIND_L2TP, offsetof(Network, stacked_netdev_names) +Network.MACsec, config_parse_stacked_netdev, NETDEV_KIND_MACSEC, offsetof(Network, stacked_netdev_names) +Network.Tunnel, config_parse_stacked_netdev, _NETDEV_KIND_TUNNEL, offsetof(Network, stacked_netdev_names) +Network.Xfrm, config_parse_stacked_netdev, NETDEV_KIND_XFRM, offsetof(Network, stacked_netdev_names) +Network.VRF, config_parse_ifname, 0, offsetof(Network, vrf_name) +Network.DHCP, config_parse_dhcp, 0, offsetof(Network, dhcp) +Network.DHCPServer, config_parse_bool, 0, offsetof(Network, dhcp_server) +Network.LinkLocalAddressing, config_parse_link_local_address_family, 0, offsetof(Network, link_local) +Network.IPv6LinkLocalAddressGenerationMode, config_parse_ipv6_link_local_address_gen_mode, 0, offsetof(Network, ipv6ll_address_gen_mode) +Network.IPv4LLRoute, config_parse_bool, 0, offsetof(Network, ipv4ll_route) +Network.DefaultRouteOnDevice, config_parse_bool, 0, offsetof(Network, default_route_on_device) +Network.IPv6Token, config_parse_address_generation_type, 0, 0 +Network.LLDP, config_parse_lldp_mode, 0, offsetof(Network, lldp_mode) +Network.EmitLLDP, config_parse_lldp_emit, 0, offsetof(Network, lldp_emit) +Network.Address, config_parse_address, 0, 0 +Network.Gateway, config_parse_gateway, 0, 0 +Network.Domains, config_parse_domains, 0, 0 +Network.DNS, config_parse_dns, 0, 0 +Network.DNSDefaultRoute, config_parse_tristate, 0, offsetof(Network, dns_default_route) +Network.LLMNR, config_parse_resolve_support, 0, offsetof(Network, llmnr) +Network.MulticastDNS, config_parse_resolve_support, 0, offsetof(Network, mdns) +Network.DNSOverTLS, config_parse_dns_over_tls_mode, 0, offsetof(Network, dns_over_tls_mode) +Network.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Network, dnssec_mode) +Network.DNSSECNegativeTrustAnchors, config_parse_dnssec_negative_trust_anchors, 0, 0 +Network.NTP, config_parse_ntp, 0, offsetof(Network, ntp) +Network.IPForward, config_parse_address_family_with_kernel, 0, offsetof(Network, ip_forward) +Network.IPMasquerade, config_parse_bool, 0, offsetof(Network, ip_masquerade) +Network.IPv6PrivacyExtensions, config_parse_ipv6_privacy_extensions, 0, offsetof(Network, ipv6_privacy_extensions) +Network.IPv6AcceptRA, config_parse_tristate, 0, offsetof(Network, ipv6_accept_ra) +Network.IPv6AcceptRouterAdvertisements, config_parse_tristate, 0, offsetof(Network, ipv6_accept_ra) +Network.IPv6DuplicateAddressDetection, config_parse_int, 0, offsetof(Network, ipv6_dad_transmits) +Network.IPv6HopLimit, config_parse_int, 0, offsetof(Network, ipv6_hop_limit) +Network.IPv6ProxyNDP, config_parse_tristate, 0, offsetof(Network, ipv6_proxy_ndp) +Network.IPv6MTUBytes, config_parse_mtu, AF_INET6, offsetof(Network, ipv6_mtu) +Network.IPv4AcceptLocal, config_parse_tristate, 0, offsetof(Network, ipv4_accept_local) +Network.ActiveSlave, config_parse_bool, 0, offsetof(Network, active_slave) +Network.PrimarySlave, config_parse_bool, 0, offsetof(Network, primary_slave) +Network.IPv4ProxyARP, config_parse_tristate, 0, offsetof(Network, proxy_arp) +Network.ProxyARP, config_parse_tristate, 0, offsetof(Network, proxy_arp) +Network.IPv6ProxyNDPAddress, config_parse_ipv6_proxy_ndp_address, 0, 0 +Network.BindCarrier, config_parse_strv, 0, offsetof(Network, bind_carrier) +Network.ConfigureWithoutCarrier, config_parse_bool, 0, offsetof(Network, configure_without_carrier) +Network.IgnoreCarrierLoss, config_parse_tristate, 0, offsetof(Network, ignore_carrier_loss) +Network.KeepConfiguration, config_parse_keep_configuration, 0, offsetof(Network, keep_configuration) +Network.IPv6SendRA, config_parse_router_prefix_delegation, 0, offsetof(Network, router_prefix_delegation) +Network.DHCPv6PrefixDelegation, config_parse_tristate, 0, offsetof(Network, dhcp6_pd) +Address.Address, config_parse_address, 0, 0 +Address.Peer, config_parse_address, 0, 0 +Address.Broadcast, config_parse_broadcast, 0, 0 +Address.Label, config_parse_label, 0, 0 +Address.PreferredLifetime, config_parse_lifetime, 0, 0 +Address.HomeAddress, config_parse_address_flags, IFA_F_HOMEADDRESS, 0 +Address.ManageTemporaryAddress, config_parse_address_flags, IFA_F_MANAGETEMPADDR, 0 +Address.PrefixRoute, config_parse_address_flags, IFA_F_NOPREFIXROUTE, 0 /* deprecated */ +Address.AddPrefixRoute, config_parse_address_flags, IFA_F_NOPREFIXROUTE, 0 +Address.AutoJoin, config_parse_address_flags, IFA_F_MCAUTOJOIN, 0 +Address.DuplicateAddressDetection, config_parse_duplicate_address_detection, 0, 0 +Address.Scope, config_parse_address_scope, 0, 0 +IPv6AddressLabel.Prefix, config_parse_address_label_prefix, 0, 0 +IPv6AddressLabel.Label, config_parse_address_label, 0, 0 +Neighbor.Address, config_parse_neighbor_address, 0, 0 +Neighbor.LinkLayerAddress, config_parse_neighbor_lladdr, 0, 0 +Neighbor.MACAddress, config_parse_neighbor_hwaddr, 0, 0 /* deprecated */ +RoutingPolicyRule.TypeOfService, config_parse_routing_policy_rule_tos, 0, 0 +RoutingPolicyRule.Priority, config_parse_routing_policy_rule_priority, 0, 0 +RoutingPolicyRule.Table, config_parse_routing_policy_rule_table, 0, 0 +RoutingPolicyRule.FirewallMark, config_parse_routing_policy_rule_fwmark_mask, 0, 0 +RoutingPolicyRule.From, config_parse_routing_policy_rule_prefix, 0, 0 +RoutingPolicyRule.To, config_parse_routing_policy_rule_prefix, 0, 0 +RoutingPolicyRule.IncomingInterface, config_parse_routing_policy_rule_device, 0, 0 +RoutingPolicyRule.OutgoingInterface, config_parse_routing_policy_rule_device, 0, 0 +RoutingPolicyRule.IPProtocol, config_parse_routing_policy_rule_ip_protocol, 0, 0 +RoutingPolicyRule.SourcePort, config_parse_routing_policy_rule_port_range, 0, 0 +RoutingPolicyRule.DestinationPort, config_parse_routing_policy_rule_port_range, 0, 0 +RoutingPolicyRule.InvertRule, config_parse_routing_policy_rule_invert, 0, 0 +RoutingPolicyRule.Family, config_parse_routing_policy_rule_family, 0, 0 +RoutingPolicyRule.User, config_parse_routing_policy_rule_uid_range, 0, 0 +RoutingPolicyRule.SuppressPrefixLength, config_parse_routing_policy_rule_suppress_prefixlen, 0, 0 +Route.Gateway, config_parse_gateway, 0, 0 +Route.Destination, config_parse_destination, 0, 0 +Route.Source, config_parse_destination, 0, 0 +Route.Metric, config_parse_route_priority, 0, 0 +Route.Scope, config_parse_route_scope, 0, 0 +Route.PreferredSource, config_parse_preferred_src, 0, 0 +Route.Table, config_parse_route_table, 0, 0 +Route.MTUBytes, config_parse_route_mtu, AF_UNSPEC, 0 +Route.GatewayOnLink, config_parse_route_boolean, 0, 0 +Route.GatewayOnlink, config_parse_route_boolean, 0, 0 +Route.IPv6Preference, config_parse_ipv6_route_preference, 0, 0 +Route.Protocol, config_parse_route_protocol, 0, 0 +Route.Type, config_parse_route_type, 0, 0 +Route.InitialCongestionWindow, config_parse_tcp_window, 0, 0 +Route.InitialAdvertisedReceiveWindow, config_parse_tcp_window, 0, 0 +Route.QuickAck, config_parse_route_boolean, 0, 0 +Route.FastOpenNoCookie, config_parse_route_boolean, 0, 0 +Route.TTLPropagate, config_parse_route_boolean, 0, 0 +Route.MultiPathRoute, config_parse_multipath_route, 0, 0 +NextHop.Id, config_parse_nexthop_id, 0, 0 +NextHop.Gateway, config_parse_nexthop_gateway, 0, 0 +DHCPv4.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier) +DHCPv4.UseDNS, config_parse_dhcp_use_dns, 0, 0 +DHCPv4.RoutesToDNS, config_parse_bool, 0, offsetof(Network, dhcp_routes_to_dns) +DHCPv4.UseNTP, config_parse_dhcp_use_ntp, 0, 0 +DHCPv4.UseSIP, config_parse_bool, 0, offsetof(Network, dhcp_use_sip) +DHCPv4.UseMTU, config_parse_bool, 0, offsetof(Network, dhcp_use_mtu) +DHCPv4.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp_use_hostname) +DHCPv4.UseDomains, config_parse_dhcp_use_domains, 0, offsetof(Network, dhcp_use_domains) +DHCPv4.UseRoutes, config_parse_bool, 0, offsetof(Network, dhcp_use_routes) +DHCPv4.UseGateway, config_parse_tristate, 0, offsetof(Network, dhcp_use_gateway) +DHCPv4.RequestOptions, config_parse_dhcp_request_options, AF_INET, 0 +DHCPv4.Anonymize, config_parse_bool, 0, offsetof(Network, dhcp_anonymize) +DHCPv4.SendHostname, config_parse_bool, 0, offsetof(Network, dhcp_send_hostname) +DHCPv4.Hostname, config_parse_hostname, 0, offsetof(Network, dhcp_hostname) +DHCPv4.RequestBroadcast, config_parse_bool, 0, offsetof(Network, dhcp_broadcast) +DHCPv4.VendorClassIdentifier, config_parse_string, 0, offsetof(Network, dhcp_vendor_class_identifier) +DHCPv4.MUDURL, config_parse_dhcp_mud_url, 0, 0 +DHCPv4.MaxAttempts, config_parse_dhcp_max_attempts, 0, 0 +DHCPv4.UserClass, config_parse_dhcp_user_class, AF_INET, offsetof(Network, dhcp_user_class) +DHCPv4.DUIDType, config_parse_duid_type, 0, offsetof(Network, duid) +DHCPv4.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Network, duid) +DHCPv4.RouteMetric, config_parse_dhcp_route_metric, 0, 0 +DHCPv4.RouteTable, config_parse_section_route_table, 0, 0 +DHCPv4.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_use_timezone) +DHCPv4.IAID, config_parse_iaid, 0, 0 +DHCPv4.ListenPort, config_parse_uint16, 0, offsetof(Network, dhcp_client_port) +DHCPv4.SendRelease, config_parse_bool, 0, offsetof(Network, dhcp_send_release) +DHCPv4.SendDecline, config_parse_bool, 0, offsetof(Network, dhcp_send_decline) +DHCPv4.DenyList, config_parse_dhcp_acl_ip_address, 0, 0 +DHCPv4.AllowList, config_parse_dhcp_acl_ip_address, 0, 0 +DHCPv4.IPServiceType, config_parse_dhcp_ip_service_type, 0, offsetof(Network, dhcp_ip_service_type) +DHCPv4.SendOption, config_parse_dhcp_send_option, AF_INET, offsetof(Network, dhcp_client_send_options) +DHCPv4.SendVendorOption, config_parse_dhcp_send_option, 0, offsetof(Network, dhcp_client_send_vendor_options) +DHCPv4.RouteMTUBytes, config_parse_mtu, AF_INET, offsetof(Network, dhcp_route_mtu) +DHCPv4.FallbackLeaseLifetimeSec, config_parse_dhcp_fallback_lease_lifetime, 0, 0 +DHCPv6.UseDNS, config_parse_dhcp_use_dns, 0, 0 +DHCPv6.UseNTP, config_parse_dhcp_use_ntp, 0, 0 +DHCPv6.RapidCommit, config_parse_bool, 0, offsetof(Network, dhcp6_rapid_commit) +DHCPv6.MUDURL, config_parse_dhcp6_mud_url, 0, 0 +DHCPv6.RequestOptions, config_parse_dhcp_request_options, AF_INET6, 0 +DHCPv6.UserClass, config_parse_dhcp_user_class, AF_INET6, offsetof(Network, dhcp6_user_class) +DHCPv6.VendorClass, config_parse_dhcp_vendor_class, 0, offsetof(Network, dhcp6_vendor_class) +DHCPv6.SendVendorOption, config_parse_dhcp_send_option, AF_INET6, offsetof(Network, dhcp6_client_send_vendor_options) +DHCPv6.ForceDHCPv6PDOtherInformation, config_parse_bool, 0, offsetof(Network, dhcp6_force_pd_other_information) +DHCPv6.PrefixDelegationHint, config_parse_dhcp6_pd_hint, 0, 0 +DHCPv6.WithoutRA, config_parse_dhcp6_client_start_mode, 0, offsetof(Network, dhcp6_without_ra) +DHCPv6.SendOption, config_parse_dhcp_send_option, AF_INET6, offsetof(Network, dhcp6_client_send_options) +DHCPv6.RouteMetric, config_parse_dhcp_route_metric, 0, 0 +IPv6AcceptRA.UseAutonomousPrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_autonomous_prefix) +IPv6AcceptRA.UseOnLinkPrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_onlink_prefix) +IPv6AcceptRA.UseDNS, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_dns) +IPv6AcceptRA.UseDomains, config_parse_dhcp_use_domains, 0, offsetof(Network, ipv6_accept_ra_use_domains) +IPv6AcceptRA.DHCPv6Client, config_parse_ipv6_accept_ra_start_dhcp6_client, 0, offsetof(Network, ipv6_accept_ra_start_dhcp6_client) +IPv6AcceptRA.RouteTable, config_parse_section_route_table, 0, 0 +IPv6AcceptRA.DenyList, config_parse_ndisc_deny_listed_prefix, 0, 0 +IPv6AcceptRA.BlackList, config_parse_ndisc_deny_listed_prefix, 0, 0 +DHCPServer.MaxLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_max_lease_time_usec) +DHCPServer.DefaultLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_default_lease_time_usec) +DHCPServer.EmitDNS, config_parse_bool, 0, offsetof(Network, dhcp_server_emit[SD_DHCP_LEASE_DNS].emit) +DHCPServer.DNS, config_parse_dhcp_server_emit, 0, offsetof(Network, dhcp_server_emit[SD_DHCP_LEASE_DNS]) +DHCPServer.EmitNTP, config_parse_bool, 0, offsetof(Network, dhcp_server_emit[SD_DHCP_LEASE_NTP].emit) +DHCPServer.NTP, config_parse_dhcp_server_emit, 0, offsetof(Network, dhcp_server_emit[SD_DHCP_LEASE_NTP]) +DHCPServer.EmitSIP, config_parse_bool, 0, offsetof(Network, dhcp_server_emit[SD_DHCP_LEASE_SIP].emit) +DHCPServer.SIP, config_parse_dhcp_server_emit, 0, offsetof(Network, dhcp_server_emit[SD_DHCP_LEASE_SIP]) +DHCPServer.EmitPOP3, config_parse_bool, 0, offsetof(Network, dhcp_server_emit[SD_DHCP_LEASE_POP3].emit) +DHCPServer.POP3, config_parse_dhcp_server_emit, 0, offsetof(Network, dhcp_server_emit[SD_DHCP_LEASE_POP3]) +DHCPServer.EmitSMTP, config_parse_bool, 0, offsetof(Network, dhcp_server_emit[SD_DHCP_LEASE_SMTP].emit) +DHCPServer.SMTP, config_parse_dhcp_server_emit, 0, offsetof(Network, dhcp_server_emit[SD_DHCP_LEASE_SMTP]) +DHCPServer.EmitLPR, config_parse_bool, 0, offsetof(Network, dhcp_server_emit[SD_DHCP_LEASE_LPR].emit) +DHCPServer.LPR, config_parse_dhcp_server_emit, 0, offsetof(Network, dhcp_server_emit[SD_DHCP_LEASE_LPR]) +DHCPServer.EmitRouter, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_router) +DHCPServer.EmitTimezone, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_timezone) +DHCPServer.Timezone, config_parse_timezone, 0, offsetof(Network, dhcp_server_timezone) +DHCPServer.PoolOffset, config_parse_uint32, 0, offsetof(Network, dhcp_server_pool_offset) +DHCPServer.PoolSize, config_parse_uint32, 0, offsetof(Network, dhcp_server_pool_size) +DHCPServer.SendVendorOption, config_parse_dhcp_send_option, 0, offsetof(Network, dhcp_server_send_vendor_options) +DHCPServer.SendOption, config_parse_dhcp_send_option, 0, offsetof(Network, dhcp_server_send_options) +Bridge.Cost, config_parse_uint32, 0, offsetof(Network, cost) +Bridge.UseBPDU, config_parse_tristate, 0, offsetof(Network, use_bpdu) +Bridge.HairPin, config_parse_tristate, 0, offsetof(Network, hairpin) +Bridge.FastLeave, config_parse_tristate, 0, offsetof(Network, fast_leave) +Bridge.AllowPortToBeRoot, config_parse_tristate, 0, offsetof(Network, allow_port_to_be_root) +Bridge.UnicastFlood, config_parse_tristate, 0, offsetof(Network, unicast_flood) +Bridge.MulticastFlood, config_parse_tristate, 0, offsetof(Network, multicast_flood) +Bridge.MulticastToUnicast, config_parse_tristate, 0, offsetof(Network, multicast_to_unicast) +Bridge.NeighborSuppression, config_parse_tristate, 0, offsetof(Network, neighbor_suppression) +Bridge.Learning, config_parse_tristate, 0, offsetof(Network, learning) +Bridge.ProxyARP, config_parse_tristate, 0, offsetof(Network, bridge_proxy_arp) +Bridge.ProxyARPWiFi, config_parse_tristate, 0, offsetof(Network, bridge_proxy_arp_wifi) +Bridge.Priority, config_parse_bridge_port_priority, 0, offsetof(Network, priority) +Bridge.MulticastRouter, config_parse_multicast_router, 0, offsetof(Network, multicast_router) +BridgeFDB.MACAddress, config_parse_fdb_hwaddr, 0, 0 +BridgeFDB.VLANId, config_parse_fdb_vlan_id, 0, 0 +BridgeFDB.Destination, config_parse_fdb_destination, 0, 0 +BridgeFDB.VNI, config_parse_fdb_vxlan_vni, 0, 0 +BridgeFDB.AssociatedWith, config_parse_fdb_ntf_flags, 0, 0 +BridgeMDB.MulticastGroupAddress, config_parse_mdb_group_address, 0, 0 +BridgeMDB.VLANId, config_parse_mdb_vlan_id, 0, 0 +BridgeVLAN.PVID, config_parse_brvlan_pvid, 0, 0 +BridgeVLAN.VLAN, config_parse_brvlan_vlan, 0, 0 +BridgeVLAN.EgressUntagged, config_parse_brvlan_untagged, 0, 0 +DHCPv6PrefixDelegation.SubnetId, config_parse_dhcp6_pd_subnet_id, 0, offsetof(Network, dhcp6_pd_subnet_id) +DHCPv6PrefixDelegation.Announce, config_parse_bool, 0, offsetof(Network, dhcp6_pd_announce) +DHCPv6PrefixDelegation.Assign, config_parse_bool, 0, offsetof(Network, dhcp6_pd_assign) +DHCPv6PrefixDelegation.Token, config_parse_dhcp6_pd_token, 0, offsetof(Network, dhcp6_pd_token) +IPv6SendRA.RouterLifetimeSec, config_parse_sec, 0, offsetof(Network, router_lifetime_usec) +IPv6SendRA.Managed, config_parse_bool, 0, offsetof(Network, router_managed) +IPv6SendRA.OtherInformation, config_parse_bool, 0, offsetof(Network, router_other_information) +IPv6SendRA.RouterPreference, config_parse_router_preference, 0, 0 +IPv6SendRA.EmitDNS, config_parse_bool, 0, offsetof(Network, router_emit_dns) +IPv6SendRA.DNS, config_parse_radv_dns, 0, 0 +IPv6SendRA.EmitDomains, config_parse_bool, 0, offsetof(Network, router_emit_domains) +IPv6SendRA.Domains, config_parse_radv_search_domains, 0, 0 +IPv6SendRA.DNSLifetimeSec, config_parse_sec, 0, offsetof(Network, router_dns_lifetime_usec) +IPv6Prefix.Prefix, config_parse_prefix, 0, 0 +IPv6Prefix.OnLink, config_parse_prefix_flags, 0, 0 +IPv6Prefix.AddressAutoconfiguration, config_parse_prefix_flags, 0, 0 +IPv6Prefix.ValidLifetimeSec, config_parse_prefix_lifetime, 0, 0 +IPv6Prefix.PreferredLifetimeSec, config_parse_prefix_lifetime, 0, 0 +IPv6Prefix.Assign, config_parse_prefix_assign, 0, 0 +IPv6RoutePrefix.Route, config_parse_route_prefix, 0, 0 +IPv6RoutePrefix.LifetimeSec, config_parse_route_prefix_lifetime, 0, 0 +LLDP.MUDURL, config_parse_lldp_mud, 0, 0 +CAN.BitRate, config_parse_can_bitrate, 0, offsetof(Network, can_bitrate) +CAN.SamplePoint, config_parse_permille, 0, offsetof(Network, can_sample_point) +CAN.DataBitRate, config_parse_can_bitrate, 0, offsetof(Network, can_data_bitrate) +CAN.DataSamplePoint, config_parse_permille, 0, offsetof(Network, can_data_sample_point) +CAN.FDMode, config_parse_tristate, 0, offsetof(Network, can_fd_mode) +CAN.FDNonISO, config_parse_tristate, 0, offsetof(Network, can_non_iso) +CAN.RestartSec, config_parse_sec, 0, offsetof(Network, can_restart_us) +CAN.TripleSampling, config_parse_tristate, 0, offsetof(Network, can_triple_sampling) +CAN.Termination, config_parse_tristate, 0, offsetof(Network, can_termination) +CAN.ListenOnly, config_parse_tristate, 0, offsetof(Network, can_listen_only) +QDisc.Parent, config_parse_qdisc_parent, _QDISC_KIND_INVALID, 0 +QDisc.Handle, config_parse_qdisc_handle, _QDISC_KIND_INVALID, 0 +BFIFO.Parent, config_parse_qdisc_parent, QDISC_KIND_BFIFO, 0 +BFIFO.Handle, config_parse_qdisc_handle, QDISC_KIND_BFIFO, 0 +BFIFO.LimitBytes, config_parse_bfifo_size, QDISC_KIND_BFIFO, 0 +CAKE.Parent, config_parse_qdisc_parent, QDISC_KIND_CAKE, 0 +CAKE.Handle, config_parse_qdisc_handle, QDISC_KIND_CAKE, 0 +CAKE.Bandwidth, config_parse_cake_bandwidth, QDISC_KIND_CAKE, 0 +CAKE.OverheadBytes, config_parse_cake_overhead, QDISC_KIND_CAKE, 0 +ControlledDelay.Parent, config_parse_qdisc_parent, QDISC_KIND_CODEL, 0 +ControlledDelay.Handle, config_parse_qdisc_handle, QDISC_KIND_CODEL, 0 +ControlledDelay.PacketLimit, config_parse_controlled_delay_u32, QDISC_KIND_CODEL, 0 +ControlledDelay.TargetSec, config_parse_controlled_delay_usec, QDISC_KIND_CODEL, 0 +ControlledDelay.IntervalSec, config_parse_controlled_delay_usec, QDISC_KIND_CODEL, 0 +ControlledDelay.CEThresholdSec, config_parse_controlled_delay_usec, QDISC_KIND_CODEL, 0 +ControlledDelay.ECN, config_parse_controlled_delay_bool, QDISC_KIND_CODEL, 0 +DeficitRoundRobinScheduler.Parent, config_parse_qdisc_parent, QDISC_KIND_DRR, 0 +DeficitRoundRobinScheduler.Handle, config_parse_qdisc_handle, QDISC_KIND_DRR, 0 +DeficitRoundRobinSchedulerClass.Parent, config_parse_tclass_parent, TCLASS_KIND_DRR, 0 +DeficitRoundRobinSchedulerClass.ClassId, config_parse_tclass_classid, TCLASS_KIND_DRR, 0 +DeficitRoundRobinSchedulerClass.QuantumBytes, config_parse_drr_size, TCLASS_KIND_DRR, 0 +EnhancedTransmissionSelection.Parent, config_parse_qdisc_parent, QDISC_KIND_ETS, 0 +EnhancedTransmissionSelection.Handle, config_parse_qdisc_handle, QDISC_KIND_ETS, 0 +EnhancedTransmissionSelection.Bands, config_parse_ets_u8, QDISC_KIND_ETS, 0 +EnhancedTransmissionSelection.StrictBands, config_parse_ets_u8, QDISC_KIND_ETS, 0 +EnhancedTransmissionSelection.QuantumBytes, config_parse_ets_quanta, QDISC_KIND_ETS, 0 +EnhancedTransmissionSelection.PriorityMap, config_parse_ets_prio, QDISC_KIND_ETS, 0 +PFIFO.Parent, config_parse_qdisc_parent, QDISC_KIND_PFIFO, 0 +PFIFO.Handle, config_parse_qdisc_handle, QDISC_KIND_PFIFO, 0 +PFIFO.PacketLimit, config_parse_pfifo_size, QDISC_KIND_PFIFO, 0 +PFIFOFast.Parent, config_parse_qdisc_parent, QDISC_KIND_PFIFO_FAST, 0 +PFIFOFast.Handle, config_parse_qdisc_handle, QDISC_KIND_PFIFO_FAST, 0 +PFIFOHeadDrop.Parent, config_parse_qdisc_parent, QDISC_KIND_PFIFO_HEAD_DROP, 0 +PFIFOHeadDrop.Handle, config_parse_qdisc_handle, QDISC_KIND_PFIFO_HEAD_DROP, 0 +PFIFOHeadDrop.PacketLimit, config_parse_pfifo_size, QDISC_KIND_PFIFO_HEAD_DROP, 0 +QuickFairQueueing.Parent, config_parse_qdisc_parent, QDISC_KIND_QFQ, 0 +QuickFairQueueing.Handle, config_parse_qdisc_handle, QDISC_KIND_QFQ, 0 +QuickFairQueueingClass.Parent, config_parse_tclass_parent, TCLASS_KIND_QFQ, 0 +QuickFairQueueingClass.ClassId, config_parse_tclass_classid, TCLASS_KIND_QFQ, 0 +QuickFairQueueingClass.Weight, config_parse_quick_fair_queueing_weight, TCLASS_KIND_QFQ, 0 +QuickFairQueueingClass.MaxPacketBytes, config_parse_quick_fair_queueing_max_packet, TCLASS_KIND_QFQ, 0 +FairQueueing.Parent, config_parse_qdisc_parent, QDISC_KIND_FQ, 0 +FairQueueing.Handle, config_parse_qdisc_handle, QDISC_KIND_FQ, 0 +FairQueueing.PacketLimit, config_parse_fair_queueing_u32, QDISC_KIND_FQ, 0 +FairQueueing.FlowLimit, config_parse_fair_queueing_u32, QDISC_KIND_FQ, 0 +FairQueueing.QuantumBytes, config_parse_fair_queueing_size, QDISC_KIND_FQ, 0 +FairQueueing.InitialQuantumBytes, config_parse_fair_queueing_size, QDISC_KIND_FQ, 0 +FairQueueing.MaximumRate, config_parse_fair_queueing_max_rate, QDISC_KIND_FQ, 0 +FairQueueing.Buckets, config_parse_fair_queueing_u32, QDISC_KIND_FQ, 0 +FairQueueing.OrphanMask, config_parse_fair_queueing_u32, QDISC_KIND_FQ, 0 +FairQueueing.Pacing, config_parse_fair_queueing_bool, QDISC_KIND_FQ, 0 +FairQueueing.CEThresholdSec, config_parse_fair_queueing_usec, QDISC_KIND_FQ, 0 +FairQueueingControlledDelay.Parent, config_parse_qdisc_parent, QDISC_KIND_FQ_CODEL, 0 +FairQueueingControlledDelay.Handle, config_parse_qdisc_handle, QDISC_KIND_FQ_CODEL, 0 +FairQueueingControlledDelay.PacketLimit, config_parse_fair_queueing_controlled_delay_u32, QDISC_KIND_FQ_CODEL, 0 +FairQueueingControlledDelay.MemoryLimitBytes, config_parse_fair_queueing_controlled_delay_size, QDISC_KIND_FQ_CODEL, 0 +FairQueueingControlledDelay.Flows, config_parse_fair_queueing_controlled_delay_u32, QDISC_KIND_FQ_CODEL, 0 +FairQueueingControlledDelay.QuantumBytes, config_parse_fair_queueing_controlled_delay_size, QDISC_KIND_FQ_CODEL, 0 +FairQueueingControlledDelay.TargetSec, config_parse_fair_queueing_controlled_delay_usec, QDISC_KIND_FQ_CODEL, 0 +FairQueueingControlledDelay.IntervalSec, config_parse_fair_queueing_controlled_delay_usec, QDISC_KIND_FQ_CODEL, 0 +FairQueueingControlledDelay.CEThresholdSec, config_parse_fair_queueing_controlled_delay_usec, QDISC_KIND_FQ_CODEL, 0 +FairQueueingControlledDelay.ECN, config_parse_fair_queueing_controlled_delay_bool, QDISC_KIND_FQ_CODEL, 0 +FlowQueuePIE.Parent, config_parse_qdisc_parent, QDISC_KIND_FQ_PIE, 0 +FlowQueuePIE.Handle, config_parse_qdisc_handle, QDISC_KIND_FQ_PIE, 0 +FlowQueuePIE.PacketLimit, config_parse_fq_pie_packet_limit, QDISC_KIND_FQ_PIE, 0 +GenericRandomEarlyDetection.Parent, config_parse_qdisc_parent, QDISC_KIND_GRED, 0 +GenericRandomEarlyDetection.Handle, config_parse_qdisc_handle, QDISC_KIND_GRED, 0 +GenericRandomEarlyDetection.VirtualQueues, config_parse_generic_random_early_detection_u32, QDISC_KIND_GRED, 0 +GenericRandomEarlyDetection.DefaultVirtualQueue, config_parse_generic_random_early_detection_u32, QDISC_KIND_GRED, 0 +GenericRandomEarlyDetection.GenericRIO, config_parse_generic_random_early_detection_bool, QDISC_KIND_GRED, 0 +HeavyHitterFilter.Parent, config_parse_qdisc_parent, QDISC_KIND_HHF, 0 +HeavyHitterFilter.Handle, config_parse_qdisc_handle, QDISC_KIND_HHF, 0 +HeavyHitterFilter.PacketLimit, config_parse_heavy_hitter_filter_packet_limit, QDISC_KIND_HHF, 0 +HierarchyTokenBucket.Parent, config_parse_qdisc_parent, QDISC_KIND_HTB, 0 +HierarchyTokenBucket.Handle, config_parse_qdisc_handle, QDISC_KIND_HTB, 0 +HierarchyTokenBucket.DefaultClass, config_parse_hierarchy_token_bucket_default_class, QDISC_KIND_HTB, 0 +HierarchyTokenBucket.RateToQuantum, config_parse_hierarchy_token_bucket_u32, QDISC_KIND_HTB, 0 +HierarchyTokenBucketClass.Parent, config_parse_tclass_parent, TCLASS_KIND_HTB, 0 +HierarchyTokenBucketClass.ClassId, config_parse_tclass_classid, TCLASS_KIND_HTB, 0 +HierarchyTokenBucketClass.Priority, config_parse_hierarchy_token_bucket_class_u32, TCLASS_KIND_HTB, 0 +HierarchyTokenBucketClass.QuantumBytes, config_parse_hierarchy_token_bucket_class_size, TCLASS_KIND_HTB, 0 +HierarchyTokenBucketClass.MTUBytes, config_parse_hierarchy_token_bucket_class_size, TCLASS_KIND_HTB, 0 +HierarchyTokenBucketClass.OverheadBytes, config_parse_hierarchy_token_bucket_class_size, TCLASS_KIND_HTB, 0 +HierarchyTokenBucketClass.Rate, config_parse_hierarchy_token_bucket_class_rate, TCLASS_KIND_HTB, 0 +HierarchyTokenBucketClass.CeilRate, config_parse_hierarchy_token_bucket_class_rate, TCLASS_KIND_HTB, 0 +HierarchyTokenBucketClass.BufferBytes, config_parse_hierarchy_token_bucket_class_size, TCLASS_KIND_HTB, 0 +HierarchyTokenBucketClass.CeilBufferBytes, config_parse_hierarchy_token_bucket_class_size, TCLASS_KIND_HTB, 0 +NetworkEmulator.Parent, config_parse_qdisc_parent, QDISC_KIND_NETEM, 0 +NetworkEmulator.Handle, config_parse_qdisc_handle, QDISC_KIND_NETEM, 0 +NetworkEmulator.DelaySec, config_parse_network_emulator_delay, QDISC_KIND_NETEM, 0 +NetworkEmulator.DelayJitterSec, config_parse_network_emulator_delay, QDISC_KIND_NETEM, 0 +NetworkEmulator.LossRate, config_parse_network_emulator_rate, QDISC_KIND_NETEM, 0 +NetworkEmulator.DuplicateRate, config_parse_network_emulator_rate, QDISC_KIND_NETEM, 0 +NetworkEmulator.PacketLimit, config_parse_network_emulator_packet_limit, QDISC_KIND_NETEM, 0 +PIE.Parent, config_parse_qdisc_parent, QDISC_KIND_PIE, 0 +PIE.Handle, config_parse_qdisc_handle, QDISC_KIND_PIE, 0 +PIE.PacketLimit, config_parse_pie_packet_limit, QDISC_KIND_PIE, 0 +StochasticFairBlue.Parent, config_parse_qdisc_parent, QDISC_KIND_SFB, 0 +StochasticFairBlue.Handle, config_parse_qdisc_handle, QDISC_KIND_SFB, 0 +StochasticFairBlue.PacketLimit, config_parse_stochastic_fair_blue_u32, QDISC_KIND_SFB, 0 +StochasticFairnessQueueing.Parent, config_parse_qdisc_parent, QDISC_KIND_SFQ, 0 +StochasticFairnessQueueing.Handle, config_parse_qdisc_handle, QDISC_KIND_SFQ, 0 +StochasticFairnessQueueing.PerturbPeriodSec, config_parse_stochastic_fairness_queueing_perturb_period, QDISC_KIND_SFQ, 0 +TokenBucketFilter.Parent, config_parse_qdisc_parent, QDISC_KIND_TBF, 0 +TokenBucketFilter.Handle, config_parse_qdisc_handle, QDISC_KIND_TBF, 0 +TokenBucketFilter.Rate, config_parse_token_bucket_filter_rate, QDISC_KIND_TBF, 0 +TokenBucketFilter.BurstBytes, config_parse_token_bucket_filter_size, QDISC_KIND_TBF, 0 +TokenBucketFilter.LimitBytes, config_parse_token_bucket_filter_size, QDISC_KIND_TBF, 0 +TokenBucketFilter.MTUBytes, config_parse_token_bucket_filter_size, QDISC_KIND_TBF, 0 +TokenBucketFilter.MPUBytes, config_parse_token_bucket_filter_size, QDISC_KIND_TBF, 0 +TokenBucketFilter.PeakRate, config_parse_token_bucket_filter_rate, QDISC_KIND_TBF, 0 +TokenBucketFilter.LatencySec, config_parse_token_bucket_filter_latency, QDISC_KIND_TBF, 0 +TrivialLinkEqualizer.Parent, config_parse_qdisc_parent, QDISC_KIND_TEQL, 0 +TrivialLinkEqualizer.Handle, config_parse_qdisc_handle, QDISC_KIND_TEQL, 0 +TrivialLinkEqualizer.Id, config_parse_trivial_link_equalizer_id, QDISC_KIND_TEQL, 0 +/* backwards compatibility: do not add new entries to this section */ +Network.IPv4LL, config_parse_ipv4ll, 0, offsetof(Network, link_local) +Network.IPv6PrefixDelegation, config_parse_router_prefix_delegation, 0, offsetof(Network, router_prefix_delegation) +IPv6PrefixDelegation.RouterLifetimeSec, config_parse_sec, 0, offsetof(Network, router_lifetime_usec) +IPv6PrefixDelegation.Managed, config_parse_bool, 0, offsetof(Network, router_managed) +IPv6PrefixDelegation.OtherInformation, config_parse_bool, 0, offsetof(Network, router_other_information) +IPv6PrefixDelegation.RouterPreference, config_parse_router_preference, 0, 0 +IPv6PrefixDelegation.EmitDNS, config_parse_bool, 0, offsetof(Network, router_emit_dns) +IPv6PrefixDelegation.DNS, config_parse_radv_dns, 0, 0 +IPv6PrefixDelegation.EmitDomains, config_parse_bool, 0, offsetof(Network, router_emit_domains) +IPv6PrefixDelegation.Domains, config_parse_radv_search_domains, 0, 0 +IPv6PrefixDelegation.DNSLifetimeSec, config_parse_sec, 0, offsetof(Network, router_dns_lifetime_usec) +DHCPv4.BlackList, config_parse_dhcp_acl_ip_address, 0, 0 +DHCP.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier) +DHCP.UseDNS, config_parse_dhcp_use_dns, 0, 0 +DHCP.UseNTP, config_parse_dhcp_use_ntp, 0, 0 +DHCP.UseMTU, config_parse_bool, 0, offsetof(Network, dhcp_use_mtu) +DHCP.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp_use_hostname) +DHCP.UseDomains, config_parse_dhcp_use_domains, 0, offsetof(Network, dhcp_use_domains) +DHCP.UseDomainName, config_parse_dhcp_use_domains, 0, offsetof(Network, dhcp_use_domains) +DHCP.UseRoutes, config_parse_bool, 0, offsetof(Network, dhcp_use_routes) +DHCP.Anonymize, config_parse_bool, 0, offsetof(Network, dhcp_anonymize) +DHCP.SendHostname, config_parse_bool, 0, offsetof(Network, dhcp_send_hostname) +DHCP.Hostname, config_parse_hostname, 0, offsetof(Network, dhcp_hostname) +DHCP.RequestBroadcast, config_parse_bool, 0, offsetof(Network, dhcp_broadcast) +DHCP.CriticalConnection, config_parse_tristate, 0, offsetof(Network, dhcp_critical) +DHCP.VendorClassIdentifier, config_parse_string, 0, offsetof(Network, dhcp_vendor_class_identifier) +DHCP.UserClass, config_parse_dhcp_user_class, AF_INET, offsetof(Network, dhcp_user_class) +DHCP.DUIDType, config_parse_duid_type, 0, offsetof(Network, duid) +DHCP.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Network, duid) +DHCP.RouteMetric, config_parse_dhcp_route_metric, 0, 0 +DHCP.RouteTable, config_parse_section_route_table, 0, 0 +DHCP.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_use_timezone) +DHCP.IAID, config_parse_iaid, 0, 0 +DHCP.ListenPort, config_parse_uint16, 0, offsetof(Network, dhcp_client_port) +DHCP.RapidCommit, config_parse_bool, 0, offsetof(Network, dhcp6_rapid_commit) +DHCP.ForceDHCPv6PDOtherInformation, config_parse_bool, 0, offsetof(Network, dhcp6_force_pd_other_information) +DHCPv4.UseDomainName, config_parse_dhcp_use_domains, 0, offsetof(Network, dhcp_use_domains) +DHCPv4.CriticalConnection, config_parse_tristate, 0, offsetof(Network, dhcp_critical) +TrafficControlQueueingDiscipline.Parent, config_parse_qdisc_parent, _QDISC_KIND_INVALID, 0 +TrafficControlQueueingDiscipline.NetworkEmulatorDelaySec, config_parse_network_emulator_delay, 0, 0 +TrafficControlQueueingDiscipline.NetworkEmulatorDelayJitterSec, config_parse_network_emulator_delay, 0, 0 +TrafficControlQueueingDiscipline.NetworkEmulatorLossRate, config_parse_network_emulator_rate, 0, 0 +TrafficControlQueueingDiscipline.NetworkEmulatorDuplicateRate, config_parse_network_emulator_rate, 0, 0 +TrafficControlQueueingDiscipline.NetworkEmulatorPacketLimit, config_parse_network_emulator_packet_limit, 0, 0 +FairQueueing.Quantum, config_parse_fair_queueing_size, QDISC_KIND_FQ, 0 +FairQueueing.InitialQuantum, config_parse_fair_queueing_size, QDISC_KIND_FQ, 0 +FairQueueingControlledDelay.MemoryLimit, config_parse_fair_queueing_controlled_delay_size, QDISC_KIND_FQ_CODEL, 0 +FairQueueingControlledDelay.Quantum, config_parse_fair_queueing_controlled_delay_size, QDISC_KIND_FQ_CODEL, 0 +TokenBucketFilter.Burst, config_parse_token_bucket_filter_size, QDISC_KIND_TBF, 0 +TokenBucketFilter.LimitSize, config_parse_token_bucket_filter_size, QDISC_KIND_TBF, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c new file mode 100644 index 0000000..3254641 --- /dev/null +++ b/src/network/networkd-network.c @@ -0,0 +1,1238 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <net/if.h> +#include <netinet/in.h> +#include <linux/netdevice.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "conf-files.h" +#include "conf-parser.h" +#include "dns-domain.h" +#include "fd-util.h" +#include "hostname-util.h" +#include "in-addr-util.h" +#include "networkd-dhcp-server.h" +#include "network-internal.h" +#include "networkd-address-label.h" +#include "networkd-address.h" +#include "networkd-dhcp-common.h" +#include "networkd-fdb.h" +#include "networkd-manager.h" +#include "networkd-mdb.h" +#include "networkd-ndisc.h" +#include "networkd-neighbor.h" +#include "networkd-network.h" +#include "networkd-nexthop.h" +#include "networkd-radv.h" +#include "networkd-routing-policy-rule.h" +#include "networkd-sriov.h" +#include "parse-util.h" +#include "path-lookup.h" +#include "set.h" +#include "socket-util.h" +#include "stat-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "tc.h" +#include "util.h" + +/* Let's assume that anything above this number is a user misconfiguration. */ +#define MAX_NTP_SERVERS 128 + +/* Set defaults following RFC7844 */ +void network_apply_anonymize_if_set(Network *network) { + if (!network->dhcp_anonymize) + return; + /* RFC7844 3.7 + SHOULD NOT send the Host Name option */ + network->dhcp_send_hostname = false; + /* RFC7844 section 3.: + MAY contain the Client Identifier option + Section 3.5: + clients MUST use client identifiers based solely + on the link-layer address */ + /* NOTE: Using MAC, as it does not reveal extra information, + * and some servers might not answer if this option is not sent */ + network->dhcp_client_identifier = DHCP_CLIENT_ID_MAC; + /* RFC 7844 3.10: + SHOULD NOT use the Vendor Class Identifier option */ + network->dhcp_vendor_class_identifier = mfree(network->dhcp_vendor_class_identifier); + /* RFC7844 section 3.6.: + The client intending to protect its privacy SHOULD only request a + minimal number of options in the PRL and SHOULD also randomly shuffle + the ordering of option codes in the PRL. If this random ordering + cannot be implemented, the client MAY order the option codes in the + PRL by option code number (lowest to highest). + */ + /* NOTE: dhcp_use_mtu is false by default, + * though it was not initiallized to any value in network_load_one. + * Maybe there should be another var called *send*? + * (to use the MTU sent by the server but to do not send + * the option in the PRL). */ + network->dhcp_use_mtu = false; + /* NOTE: when Anonymize=yes, the PRL route options are sent by default, + * but this is needed to use them. */ + network->dhcp_use_routes = true; + /* RFC7844 section 3.6. + * same comments as previous option */ + network->dhcp_use_timezone = false; +} + +static int network_resolve_netdev_one(Network *network, const char *name, NetDevKind kind, NetDev **ret_netdev) { + const char *kind_string; + NetDev *netdev; + int r; + + /* For test-networkd-conf, the check must be earlier than the assertions. */ + if (!name) + return 0; + + assert(network); + assert(network->manager); + assert(network->filename); + assert(ret_netdev); + + if (kind == _NETDEV_KIND_TUNNEL) + kind_string = "tunnel"; + else { + kind_string = netdev_kind_to_string(kind); + if (!kind_string) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Invalid NetDev kind of %s, ignoring assignment.", + network->filename, name); + } + + r = netdev_get(network->manager, name, &netdev); + if (r < 0) + return log_error_errno(r, "%s: %s NetDev could not be found, ignoring assignment.", + network->filename, name); + + if (netdev->kind != kind && !(kind == _NETDEV_KIND_TUNNEL && + IN_SET(netdev->kind, + NETDEV_KIND_IPIP, + NETDEV_KIND_SIT, + NETDEV_KIND_GRE, + NETDEV_KIND_GRETAP, + NETDEV_KIND_IP6GRE, + NETDEV_KIND_IP6GRETAP, + NETDEV_KIND_VTI, + NETDEV_KIND_VTI6, + NETDEV_KIND_IP6TNL, + NETDEV_KIND_ERSPAN))) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: NetDev %s is not a %s, ignoring assignment", + network->filename, name, kind_string); + + *ret_netdev = netdev_ref(netdev); + return 1; +} + +static int network_resolve_stacked_netdevs(Network *network) { + void *name, *kind; + int r; + + assert(network); + + HASHMAP_FOREACH_KEY(kind, name, network->stacked_netdev_names) { + _cleanup_(netdev_unrefp) NetDev *netdev = NULL; + + r = network_resolve_netdev_one(network, name, PTR_TO_INT(kind), &netdev); + if (r <= 0) + continue; + + r = hashmap_ensure_allocated(&network->stacked_netdevs, &string_hash_ops); + if (r < 0) + return log_oom(); + + r = hashmap_put(network->stacked_netdevs, netdev->ifname, netdev); + if (r < 0) + return log_error_errno(r, "%s: Failed to add NetDev '%s' to network: %m", + network->filename, (const char *) name); + + netdev = NULL; + } + + return 0; +} + +int network_verify(Network *network) { + assert(network); + assert(network->filename); + + if (set_isempty(network->match_mac) && set_isempty(network->match_permanent_mac) && + strv_isempty(network->match_path) && strv_isempty(network->match_driver) && + strv_isempty(network->match_type) && strv_isempty(network->match_name) && + strv_isempty(network->match_property) && strv_isempty(network->match_wlan_iftype) && + strv_isempty(network->match_ssid) && !network->conditions) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: No valid settings found in the [Match] section, ignoring file. " + "To match all interfaces, add Name=* in the [Match] section.", + network->filename); + + /* skip out early if configuration does not match the environment */ + if (!condition_test_list(network->conditions, environ, NULL, NULL, NULL)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Conditions in the file do not match the system environment, skipping.", + network->filename); + + (void) network_resolve_netdev_one(network, network->bond_name, NETDEV_KIND_BOND, &network->bond); + (void) network_resolve_netdev_one(network, network->bridge_name, NETDEV_KIND_BRIDGE, &network->bridge); + (void) network_resolve_netdev_one(network, network->vrf_name, NETDEV_KIND_VRF, &network->vrf); + (void) network_resolve_stacked_netdevs(network); + + /* Free unnecessary entries. */ + network->bond_name = mfree(network->bond_name); + network->bridge_name = mfree(network->bridge_name); + network->vrf_name = mfree(network->vrf_name); + network->stacked_netdev_names = hashmap_free_free_key(network->stacked_netdev_names); + + if (network->bond) { + /* Bonding slave does not support addressing. */ + if (network->link_local >= 0 && network->link_local != ADDRESS_FAMILY_NO) { + log_warning("%s: Cannot enable LinkLocalAddressing= when Bond= is specified, disabling LinkLocalAddressing=.", + network->filename); + network->link_local = ADDRESS_FAMILY_NO; + } + if (network->dhcp_server) { + log_warning("%s: Cannot enable DHCPServer= when Bond= is specified, disabling DHCPServer=.", + network->filename); + network->dhcp_server = false; + } + if (!ordered_hashmap_isempty(network->addresses_by_section)) + log_warning("%s: Cannot set addresses when Bond= is specified, ignoring addresses.", + network->filename); + if (!hashmap_isempty(network->routes_by_section)) + log_warning("%s: Cannot set routes when Bond= is specified, ignoring routes.", + network->filename); + + network->addresses_by_section = ordered_hashmap_free_with_destructor(network->addresses_by_section, address_free); + network->routes_by_section = hashmap_free_with_destructor(network->routes_by_section, route_free); + } + + if (network->link_local < 0) + network->link_local = network->bridge ? ADDRESS_FAMILY_NO : ADDRESS_FAMILY_IPV6; + + if (FLAGS_SET(network->link_local, ADDRESS_FAMILY_FALLBACK_IPV4) && + !FLAGS_SET(network->dhcp, ADDRESS_FAMILY_IPV4)) { + log_warning("%s: fallback assignment of IPv4 link local address is enabled but DHCPv4 is disabled. " + "Disabling the fallback assignment.", network->filename); + SET_FLAG(network->link_local, ADDRESS_FAMILY_FALLBACK_IPV4, false); + } + + /* IPMasquerade=yes implies IPForward=yes */ + if (network->ip_masquerade) + network->ip_forward |= ADDRESS_FAMILY_IPV4; + + network_adjust_ipv6_accept_ra(network); + network_adjust_dhcp(network); + network_adjust_radv(network); + + if (network->mtu > 0 && network->dhcp_use_mtu) { + log_warning("%s: MTUBytes= in [Link] section and UseMTU= in [DHCP] section are set. " + "Disabling UseMTU=.", network->filename); + network->dhcp_use_mtu = false; + } + + if (network->dhcp_use_gateway < 0) + network->dhcp_use_gateway = network->dhcp_use_routes; + + if (network->ignore_carrier_loss < 0) + network->ignore_carrier_loss = network->configure_without_carrier; + + if (network->dhcp_critical >= 0) { + if (network->keep_configuration >= 0) + log_warning("%s: Both KeepConfiguration= and deprecated CriticalConnection= are set. " + "Ignoring CriticalConnection=.", network->filename); + else if (network->dhcp_critical) + /* CriticalConnection=yes also preserve foreign static configurations. */ + network->keep_configuration = KEEP_CONFIGURATION_YES; + else + network->keep_configuration = KEEP_CONFIGURATION_NO; + } + + if (network->keep_configuration < 0) + network->keep_configuration = KEEP_CONFIGURATION_NO; + + if (network->ipv6_proxy_ndp == 0 && !set_isempty(network->ipv6_proxy_ndp_addresses)) { + log_warning("%s: IPv6ProxyNDP= is disabled. Ignoring IPv6ProxyNDPAddress=.", network->filename); + network->ipv6_proxy_ndp_addresses = set_free_free(network->ipv6_proxy_ndp_addresses); + } + + network_drop_invalid_addresses(network); + network_drop_invalid_routes(network); + network_drop_invalid_nexthops(network); + network_drop_invalid_fdb_entries(network); + network_drop_invalid_mdb_entries(network); + network_drop_invalid_neighbors(network); + network_drop_invalid_address_labels(network); + network_drop_invalid_prefixes(network); + network_drop_invalid_route_prefixes(network); + network_drop_invalid_routing_policy_rules(network); + network_drop_invalid_traffic_control(network); + network_drop_invalid_sr_iov(network); + + return 0; +} + +int network_load_one(Manager *manager, OrderedHashmap **networks, const char *filename) { + _cleanup_free_ char *fname = NULL, *name = NULL; + _cleanup_(network_unrefp) Network *network = NULL; + _cleanup_fclose_ FILE *file = NULL; + const char *dropin_dirname; + char *d; + int r; + + assert(manager); + assert(filename); + + file = fopen(filename, "re"); + if (!file) { + if (errno == ENOENT) + return 0; + + return -errno; + } + + if (null_or_empty_fd(fileno(file))) { + log_debug("Skipping empty file: %s", filename); + return 0; + } + + fname = strdup(filename); + if (!fname) + return log_oom(); + + name = strdup(basename(filename)); + if (!name) + return log_oom(); + + d = strrchr(name, '.'); + if (!d) + return -EINVAL; + + *d = '\0'; + + dropin_dirname = strjoina(name, ".network.d"); + + network = new(Network, 1); + if (!network) + return log_oom(); + + *network = (Network) { + .filename = TAKE_PTR(fname), + .name = TAKE_PTR(name), + + .manager = manager, + .n_ref = 1, + + .required_for_online = true, + .required_operstate_for_online = LINK_OPERSTATE_RANGE_DEFAULT, + .arp = -1, + .multicast = -1, + .allmulticast = -1, + + .configure_without_carrier = false, + .ignore_carrier_loss = -1, + .keep_configuration = _KEEP_CONFIGURATION_INVALID, + + .dhcp = ADDRESS_FAMILY_NO, + .duid.type = _DUID_TYPE_INVALID, + .dhcp_critical = -1, + .dhcp_use_ntp = true, + .dhcp_use_sip = true, + .dhcp_use_dns = true, + .dhcp_use_hostname = true, + .dhcp_use_routes = true, + .dhcp_use_gateway = -1, + /* NOTE: this var might be overwritten by network_apply_anonymize_if_set */ + .dhcp_send_hostname = true, + .dhcp_send_release = true, + /* To enable/disable RFC7844 Anonymity Profiles */ + .dhcp_anonymize = false, + .dhcp_route_metric = DHCP_ROUTE_METRIC, + /* NOTE: this var might be overwritten by network_apply_anonymize_if_set */ + .dhcp_client_identifier = DHCP_CLIENT_ID_DUID, + .dhcp_route_table = RT_TABLE_MAIN, + .dhcp_route_table_set = false, + /* NOTE: from man: UseMTU=... Defaults to false*/ + .dhcp_use_mtu = false, + /* NOTE: from man: UseTimezone=... Defaults to "no".*/ + .dhcp_use_timezone = false, + .dhcp_ip_service_type = -1, + + .dhcp6_rapid_commit = true, + .dhcp6_route_metric = DHCP_ROUTE_METRIC, + .dhcp6_use_ntp = true, + .dhcp6_use_dns = true, + + .dhcp6_pd = -1, + .dhcp6_pd_announce = true, + .dhcp6_pd_assign = true, + .dhcp6_pd_subnet_id = -1, + + .dhcp_server_emit[SD_DHCP_LEASE_DNS].emit = true, + .dhcp_server_emit[SD_DHCP_LEASE_NTP].emit = true, + .dhcp_server_emit[SD_DHCP_LEASE_SIP].emit = true, + + .dhcp_server_emit_router = true, + .dhcp_server_emit_timezone = true, + + .router_lifetime_usec = 30 * USEC_PER_MINUTE, + .router_emit_dns = true, + .router_emit_domains = true, + + .use_bpdu = -1, + .hairpin = -1, + .fast_leave = -1, + .allow_port_to_be_root = -1, + .unicast_flood = -1, + .multicast_flood = -1, + .multicast_to_unicast = -1, + .neighbor_suppression = -1, + .learning = -1, + .bridge_proxy_arp = -1, + .bridge_proxy_arp_wifi = -1, + .priority = LINK_BRIDGE_PORT_PRIORITY_INVALID, + .multicast_router = _MULTICAST_ROUTER_INVALID, + + .lldp_mode = LLDP_MODE_ROUTERS_ONLY, + + .dns_default_route = -1, + .llmnr = RESOLVE_SUPPORT_YES, + .mdns = RESOLVE_SUPPORT_NO, + .dnssec_mode = _DNSSEC_MODE_INVALID, + .dns_over_tls_mode = _DNS_OVER_TLS_MODE_INVALID, + + /* If LinkLocalAddressing= is not set, then set to ADDRESS_FAMILY_IPV6 later. */ + .link_local = _ADDRESS_FAMILY_INVALID, + .ipv6ll_address_gen_mode = _IPV6_LINK_LOCAL_ADDRESS_GEN_MODE_INVALID, + + .ipv4_accept_local = -1, + .ipv6_privacy_extensions = IPV6_PRIVACY_EXTENSIONS_NO, + .ipv6_accept_ra = -1, + .ipv6_dad_transmits = -1, + .ipv6_hop_limit = -1, + .ipv6_proxy_ndp = -1, + .proxy_arp = -1, + + .ipv6_accept_ra_use_dns = true, + .ipv6_accept_ra_use_autonomous_prefix = true, + .ipv6_accept_ra_use_onlink_prefix = true, + .ipv6_accept_ra_route_table = RT_TABLE_MAIN, + .ipv6_accept_ra_route_table_set = false, + .ipv6_accept_ra_start_dhcp6_client = IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES, + + .can_triple_sampling = -1, + .can_termination = -1, + .can_listen_only = -1, + .can_fd_mode = -1, + .can_non_iso = -1, + }; + + r = config_parse_many( + filename, NETWORK_DIRS, dropin_dirname, + "Match\0" + "Link\0" + "SR-IOV\0" + "Network\0" + "Address\0" + "Neighbor\0" + "IPv6AddressLabel\0" + "RoutingPolicyRule\0" + "Route\0" + "NextHop\0" + "DHCP\0" /* compat */ + "DHCPv4\0" + "DHCPv6\0" + "DHCPv6PrefixDelegation\0" + "DHCPServer\0" + "IPv6AcceptRA\0" + "IPv6NDPProxyAddress\0" + "Bridge\0" + "BridgeFDB\0" + "BridgeMDB\0" + "BridgeVLAN\0" + "IPv6SendRA\0" + "IPv6PrefixDelegation\0" + "IPv6Prefix\0" + "IPv6RoutePrefix\0" + "LLDP\0" + "TrafficControlQueueingDiscipline\0" + "CAN\0" + "QDisc\0" + "BFIFO\0" + "CAKE\0" + "ControlledDelay\0" + "DeficitRoundRobinScheduler\0" + "DeficitRoundRobinSchedulerClass\0" + "EnhancedTransmissionSelection\0" + "FairQueueing\0" + "FairQueueingControlledDelay\0" + "FlowQueuePIE\0" + "GenericRandomEarlyDetection\0" + "HeavyHitterFilter\0" + "HierarchyTokenBucket\0" + "HierarchyTokenBucketClass\0" + "NetworkEmulator\0" + "PFIFO\0" + "PFIFOFast\0" + "PFIFOHeadDrop\0" + "PIE\0" + "QuickFairQueueing\0" + "QuickFairQueueingClass\0" + "StochasticFairBlue\0" + "StochasticFairnessQueueing\0" + "TokenBucketFilter\0" + "TrivialLinkEqualizer\0", + config_item_perf_lookup, network_network_gperf_lookup, + CONFIG_PARSE_WARN, + network, + &network->timestamp); + if (r < 0) + return r; + + network_apply_anonymize_if_set(network); + + r = network_add_ipv4ll_route(network); + if (r < 0) + log_warning_errno(r, "%s: Failed to add IPv4LL route, ignoring: %m", network->filename); + + r = network_add_default_route_on_device(network); + if (r < 0) + log_warning_errno(r, "%s: Failed to add default route on device, ignoring: %m", + network->filename); + + if (network_verify(network) < 0) + /* Ignore .network files that do not match the conditions. */ + return 0; + + r = ordered_hashmap_ensure_allocated(networks, &string_hash_ops); + if (r < 0) + return r; + + r = ordered_hashmap_put(*networks, network->name, network); + if (r < 0) + return r; + + network = NULL; + return 0; +} + +int network_load(Manager *manager, OrderedHashmap **networks) { + _cleanup_strv_free_ char **files = NULL; + char **f; + int r; + + assert(manager); + + ordered_hashmap_clear_with_destructor(*networks, network_unref); + + r = conf_files_list_strv(&files, ".network", NULL, 0, NETWORK_DIRS); + if (r < 0) + return log_error_errno(r, "Failed to enumerate network files: %m"); + + STRV_FOREACH(f, files) { + r = network_load_one(manager, networks, *f); + if (r < 0) + log_error_errno(r, "Failed to load %s, ignoring: %m", *f); + } + + return 0; +} + +int network_reload(Manager *manager) { + OrderedHashmap *new_networks = NULL; + Network *n, *old; + int r; + + assert(manager); + + r = network_load(manager, &new_networks); + if (r < 0) + goto failure; + + ORDERED_HASHMAP_FOREACH(n, new_networks) { + r = network_get_by_name(manager, n->name, &old); + if (r < 0) + continue; /* The .network file is new. */ + + if (n->timestamp != old->timestamp) + continue; /* The .network file is modified. */ + + if (!streq(n->filename, old->filename)) + continue; + + r = ordered_hashmap_replace(new_networks, old->name, old); + if (r < 0) + goto failure; + + network_ref(old); + network_unref(n); + } + + ordered_hashmap_free_with_destructor(manager->networks, network_unref); + manager->networks = new_networks; + + return 0; + +failure: + ordered_hashmap_free_with_destructor(new_networks, network_unref); + + return r; +} + +static Network *network_free(Network *network) { + if (!network) + return NULL; + + free(network->filename); + + set_free_free(network->match_mac); + set_free_free(network->match_permanent_mac); + strv_free(network->match_path); + strv_free(network->match_driver); + strv_free(network->match_type); + strv_free(network->match_name); + strv_free(network->match_property); + strv_free(network->match_wlan_iftype); + strv_free(network->match_ssid); + set_free_free(network->match_bssid); + condition_free_list(network->conditions); + + free(network->description); + free(network->dhcp_vendor_class_identifier); + free(network->dhcp_mudurl); + strv_free(network->dhcp_user_class); + free(network->dhcp_hostname); + set_free(network->dhcp_deny_listed_ip); + set_free(network->dhcp_allow_listed_ip); + set_free(network->dhcp_request_options); + set_free(network->dhcp6_request_options); + free(network->mac); + free(network->dhcp6_mudurl); + strv_free(network->dhcp6_user_class); + strv_free(network->dhcp6_vendor_class); + + strv_free(network->ntp); + for (unsigned i = 0; i < network->n_dns; i++) + in_addr_full_free(network->dns[i]); + free(network->dns); + ordered_set_free(network->search_domains); + ordered_set_free(network->route_domains); + strv_free(network->bind_carrier); + + ordered_set_free(network->router_search_domains); + free(network->router_dns); + set_free_free(network->ndisc_deny_listed_prefix); + + free(network->bridge_name); + free(network->bond_name); + free(network->vrf_name); + hashmap_free_free_key(network->stacked_netdev_names); + netdev_unref(network->bridge); + netdev_unref(network->bond); + netdev_unref(network->vrf); + hashmap_free_with_destructor(network->stacked_netdevs, netdev_unref); + + set_free_free(network->ipv6_proxy_ndp_addresses); + ordered_hashmap_free_with_destructor(network->addresses_by_section, address_free); + hashmap_free_with_destructor(network->routes_by_section, route_free); + hashmap_free_with_destructor(network->nexthops_by_section, nexthop_free); + hashmap_free_with_destructor(network->fdb_entries_by_section, fdb_entry_free); + hashmap_free_with_destructor(network->mdb_entries_by_section, mdb_entry_free); + hashmap_free_with_destructor(network->neighbors_by_section, neighbor_free); + hashmap_free_with_destructor(network->address_labels_by_section, address_label_free); + hashmap_free_with_destructor(network->prefixes_by_section, prefix_free); + hashmap_free_with_destructor(network->route_prefixes_by_section, route_prefix_free); + hashmap_free_with_destructor(network->rules_by_section, routing_policy_rule_free); + ordered_hashmap_free_with_destructor(network->sr_iov_by_section, sr_iov_free); + ordered_hashmap_free_with_destructor(network->tc_by_section, traffic_control_free); + + if (network->manager && + network->manager->duids_requesting_uuid) + set_remove(network->manager->duids_requesting_uuid, &network->duid); + + free(network->name); + + free(network->dhcp_server_timezone); + + for (sd_dhcp_lease_server_type t = 0; t < _SD_DHCP_LEASE_SERVER_TYPE_MAX; t++) + free(network->dhcp_server_emit[t].addresses); + + set_free_free(network->dnssec_negative_trust_anchors); + + free(network->lldp_mud); + + ordered_hashmap_free(network->dhcp_client_send_options); + ordered_hashmap_free(network->dhcp_client_send_vendor_options); + ordered_hashmap_free(network->dhcp_server_send_options); + ordered_hashmap_free(network->dhcp_server_send_vendor_options); + ordered_set_free(network->ipv6_tokens); + ordered_hashmap_free(network->dhcp6_client_send_options); + ordered_hashmap_free(network->dhcp6_client_send_vendor_options); + + return mfree(network); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(Network, network, network_free); + +int network_get_by_name(Manager *manager, const char *name, Network **ret) { + Network *network; + + assert(manager); + assert(name); + assert(ret); + + network = ordered_hashmap_get(manager->networks, name); + if (!network) + return -ENOENT; + + *ret = network; + + return 0; +} + +int network_get(Manager *manager, unsigned short iftype, sd_device *device, + const char *ifname, char * const *alternative_names, const char *driver, + const struct ether_addr *mac, const struct ether_addr *permanent_mac, + enum nl80211_iftype wlan_iftype, const char *ssid, const struct ether_addr *bssid, + Network **ret) { + Network *network; + + assert(manager); + assert(ret); + + ORDERED_HASHMAP_FOREACH(network, manager->networks) + if (net_match_config(network->match_mac, network->match_permanent_mac, + network->match_path, network->match_driver, + network->match_type, network->match_name, network->match_property, + network->match_wlan_iftype, network->match_ssid, network->match_bssid, + device, mac, permanent_mac, driver, iftype, + ifname, alternative_names, wlan_iftype, ssid, bssid)) { + if (network->match_name && device) { + const char *attr; + uint8_t name_assign_type = NET_NAME_UNKNOWN; + + if (sd_device_get_sysattr_value(device, "name_assign_type", &attr) >= 0) + (void) safe_atou8(attr, &name_assign_type); + + if (name_assign_type == NET_NAME_ENUM) + log_warning("%s: found matching network '%s', based on potentially unpredictable ifname", + ifname, network->filename); + else + log_debug("%s: found matching network '%s'", ifname, network->filename); + } else + log_debug("%s: found matching network '%s'", ifname, network->filename); + + *ret = network; + return 0; + } + + *ret = NULL; + + return -ENOENT; +} + +int network_apply(Network *network, Link *link) { + assert(network); + assert(link); + + link->network = network_ref(network); + + if (network->n_dns > 0 || + !strv_isempty(network->ntp) || + !ordered_set_isempty(network->search_domains) || + !ordered_set_isempty(network->route_domains)) + link_dirty(link); + + return 0; +} + +bool network_has_static_ipv6_configurations(Network *network) { + Address *address; + Route *route; + FdbEntry *fdb; + MdbEntry *mdb; + Neighbor *neighbor; + + assert(network); + + ORDERED_HASHMAP_FOREACH(address, network->addresses_by_section) + if (address->family == AF_INET6) + return true; + + HASHMAP_FOREACH(route, network->routes_by_section) + if (route->family == AF_INET6) + return true; + + HASHMAP_FOREACH(fdb, network->fdb_entries_by_section) + if (fdb->family == AF_INET6) + return true; + + HASHMAP_FOREACH(mdb, network->mdb_entries_by_section) + if (mdb->family == AF_INET6) + return true; + + HASHMAP_FOREACH(neighbor, network->neighbors_by_section) + if (neighbor->family == AF_INET6) + return true; + + if (!hashmap_isempty(network->address_labels_by_section)) + return true; + + if (!hashmap_isempty(network->prefixes_by_section)) + return true; + + if (!hashmap_isempty(network->route_prefixes_by_section)) + return true; + + return false; +} + +int config_parse_stacked_netdev(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + _cleanup_free_ char *name = NULL; + NetDevKind kind = ltype; + Hashmap **h = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + assert(IN_SET(kind, + NETDEV_KIND_VLAN, NETDEV_KIND_MACVLAN, NETDEV_KIND_MACVTAP, + NETDEV_KIND_IPVLAN, NETDEV_KIND_IPVTAP, NETDEV_KIND_VXLAN, + NETDEV_KIND_L2TP, NETDEV_KIND_MACSEC, _NETDEV_KIND_TUNNEL, + NETDEV_KIND_XFRM)); + + if (!ifname_valid(rvalue)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid netdev name in %s=, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + name = strdup(rvalue); + if (!name) + return log_oom(); + + r = hashmap_ensure_allocated(h, &string_hash_ops); + if (r < 0) + return log_oom(); + + r = hashmap_put(*h, name, INT_TO_PTR(kind)); + if (r < 0) + log_syntax(unit, LOG_WARNING, filename, line, r, + "Cannot add NetDev '%s' to network, ignoring assignment: %m", name); + else if (r == 0) + log_syntax(unit, LOG_DEBUG, filename, line, r, + "NetDev '%s' specified twice, ignoring.", name); + else + name = NULL; + + return 0; +} + +int config_parse_domains( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *n = data; + int r; + + assert(n); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + n->search_domains = ordered_set_free(n->search_domains); + n->route_domains = ordered_set_free(n->route_domains); + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *w = NULL, *normalized = NULL; + const char *domain; + bool is_route; + + r = extract_first_word(&p, &w, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to extract search or route domain, ignoring: %s", rvalue); + return 0; + } + if (r == 0) + return 0; + + is_route = w[0] == '~'; + domain = is_route ? w + 1 : w; + + if (dns_name_is_root(domain) || streq(domain, "*")) { + /* If the root domain appears as is, or the special token "*" is found, we'll + * consider this as routing domain, unconditionally. */ + is_route = true; + domain = "."; /* make sure we don't allow empty strings, thus write the root + * domain as "." */ + } else { + r = dns_name_normalize(domain, 0, &normalized); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "'%s' is not a valid domain name, ignoring.", domain); + continue; + } + + domain = normalized; + + if (is_localhost(domain)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "'localhost' domain may not be configured as search or route domain, ignoring assignment: %s", + domain); + continue; + } + } + + OrderedSet **set = is_route ? &n->route_domains : &n->search_domains; + r = ordered_set_ensure_allocated(set, &string_hash_ops_free); + if (r < 0) + return log_oom(); + + r = ordered_set_put_strdup(*set, domain); + if (r < 0) + return log_oom(); + } +} + +int config_parse_hostname( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_free_ char *hn = NULL; + char **hostname = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = config_parse_string(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &hn, userdata); + if (r < 0) + return r; + + if (!hostname_is_valid(hn, false)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Hostname is not valid, ignoring assignment: %s", rvalue); + return 0; + } + + r = dns_name_is_valid(hn); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to check validity of hostname '%s', ignoring assignment: %m", rvalue); + return 0; + } + if (r == 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Hostname is not a valid DNS domain name, ignoring assignment: %s", rvalue); + return 0; + } + + return free_and_replace(*hostname, hn); +} + +int config_parse_timezone( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_free_ char *tz = NULL; + char **datap = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = config_parse_string(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &tz, userdata); + if (r < 0) + return r; + + if (!timezone_is_valid(tz, LOG_WARNING)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Timezone is not valid, ignoring assignment: %s", rvalue); + return 0; + } + + return free_and_replace(*datap, tz); +} + +int config_parse_dns( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *n = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + for (unsigned i = 0; i < n->n_dns; i++) + in_addr_full_free(n->dns[i]); + n->dns = mfree(n->dns); + n->n_dns = 0; + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_(in_addr_full_freep) struct in_addr_full *dns = NULL; + _cleanup_free_ char *w = NULL; + struct in_addr_full **m; + + r = extract_first_word(&p, &w, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid syntax, ignoring: %s", rvalue); + return 0; + } + if (r == 0) + return 0; + + r = in_addr_full_new_from_string(w, &dns); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse dns server address, ignoring: %s", w); + continue; + } + + if (IN_SET(dns->port, 53, 853)) + dns->port = 0; + + m = reallocarray(n->dns, n->n_dns + 1, sizeof(struct in_addr_full*)); + if (!m) + return log_oom(); + + m[n->n_dns++] = TAKE_PTR(dns); + n->dns = m; + } +} + +int config_parse_dnssec_negative_trust_anchors( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *n = data; + int r; + + assert(n); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + n->dnssec_negative_trust_anchors = set_free_free(n->dnssec_negative_trust_anchors); + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *w = NULL; + + r = extract_first_word(&p, &w, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to extract negative trust anchor domain, ignoring: %s", rvalue); + return 0; + } + if (r == 0) + return 0; + + r = dns_name_is_valid(w); + if (r <= 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "%s is not a valid domain name, ignoring.", w); + continue; + } + + r = set_ensure_consume(&n->dnssec_negative_trust_anchors, &dns_name_hash_ops, TAKE_PTR(w)); + if (r < 0) + return log_oom(); + } +} + +int config_parse_ntp( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + char ***l = data; + int r; + + assert(l); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *l = strv_free(*l); + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *w = NULL; + + r = extract_first_word(&p, &w, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to extract NTP server name, ignoring: %s", rvalue); + return 0; + } + if (r == 0) + return 0; + + r = dns_name_is_valid_or_address(w); + if (r <= 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "%s is not a valid domain name or IP address, ignoring.", w); + continue; + } + + if (strv_length(*l) > MAX_NTP_SERVERS) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "More than %u NTP servers specified, ignoring \"%s\" and any subsequent entries.", + MAX_NTP_SERVERS, w); + return 0; + } + + r = strv_consume(l, TAKE_PTR(w)); + if (r < 0) + return log_oom(); + } +} + +int config_parse_required_for_online( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = data; + LinkOperationalStateRange range; + bool required = true; + int r; + + if (isempty(rvalue)) { + network->required_for_online = true; + network->required_operstate_for_online = LINK_OPERSTATE_RANGE_DEFAULT; + return 0; + } + + r = parse_operational_state_range(rvalue, &range); + if (r < 0) { + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse %s= setting, ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + required = r; + range = LINK_OPERSTATE_RANGE_DEFAULT; + } + + network->required_for_online = required; + network->required_operstate_for_online = range; + + return 0; +} + +DEFINE_CONFIG_PARSE_ENUM(config_parse_keep_configuration, keep_configuration, KeepConfiguration, + "Failed to parse KeepConfiguration= setting"); + +static const char* const keep_configuration_table[_KEEP_CONFIGURATION_MAX] = { + [KEEP_CONFIGURATION_NO] = "no", + [KEEP_CONFIGURATION_DHCP_ON_STOP] = "dhcp-on-stop", + [KEEP_CONFIGURATION_DHCP] = "dhcp", + [KEEP_CONFIGURATION_STATIC] = "static", + [KEEP_CONFIGURATION_YES] = "yes", +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(keep_configuration, KeepConfiguration, KEEP_CONFIGURATION_YES); + +static const char* const ipv6_link_local_address_gen_mode_table[_IPV6_LINK_LOCAL_ADDRESS_GEN_MODE_MAX] = { + [IPV6_LINK_LOCAL_ADDRESSS_GEN_MODE_EUI64] = "eui64", + [IPV6_LINK_LOCAL_ADDRESSS_GEN_MODE_NONE] = "none", + [IPV6_LINK_LOCAL_ADDRESSS_GEN_MODE_STABLE_PRIVACY] = "stable-privacy", + [IPV6_LINK_LOCAL_ADDRESSS_GEN_MODE_RANDOM] = "random", +}; + +DEFINE_STRING_TABLE_LOOKUP(ipv6_link_local_address_gen_mode, IPv6LinkLocalAddressGenMode); +DEFINE_CONFIG_PARSE_ENUM(config_parse_ipv6_link_local_address_gen_mode, ipv6_link_local_address_gen_mode, IPv6LinkLocalAddressGenMode, "Failed to parse IPv6 link local address generation mode"); diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h new file mode 100644 index 0000000..fd0fe05 --- /dev/null +++ b/src/network/networkd-network.h @@ -0,0 +1,340 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <linux/nl80211.h> + +#include "sd-bus.h" +#include "sd-device.h" + +#include "bridge.h" +#include "condition.h" +#include "conf-parser.h" +#include "hashmap.h" +#include "netdev.h" +#include "networkd-brvlan.h" +#include "networkd-dhcp-common.h" +#include "networkd-dhcp4.h" +#include "networkd-dhcp6.h" +#include "networkd-dhcp-server.h" +#include "networkd-lldp-rx.h" +#include "networkd-lldp-tx.h" +#include "networkd-ndisc.h" +#include "networkd-radv.h" +#include "networkd-sysctl.h" +#include "networkd-util.h" +#include "ordered-set.h" +#include "resolve-util.h" +#include "socket-netlink.h" + +typedef enum KeepConfiguration { + KEEP_CONFIGURATION_NO = 0, + KEEP_CONFIGURATION_DHCP_ON_START = 1 << 0, + KEEP_CONFIGURATION_DHCP_ON_STOP = 1 << 1, + KEEP_CONFIGURATION_DHCP = KEEP_CONFIGURATION_DHCP_ON_START | KEEP_CONFIGURATION_DHCP_ON_STOP, + KEEP_CONFIGURATION_STATIC = 1 << 2, + KEEP_CONFIGURATION_YES = KEEP_CONFIGURATION_DHCP | KEEP_CONFIGURATION_STATIC, + _KEEP_CONFIGURATION_MAX, + _KEEP_CONFIGURATION_INVALID = -1, +} KeepConfiguration; + +typedef enum IPv6LinkLocalAddressGenMode { + IPV6_LINK_LOCAL_ADDRESSS_GEN_MODE_EUI64 = IN6_ADDR_GEN_MODE_EUI64, + IPV6_LINK_LOCAL_ADDRESSS_GEN_MODE_NONE = IN6_ADDR_GEN_MODE_NONE, + IPV6_LINK_LOCAL_ADDRESSS_GEN_MODE_STABLE_PRIVACY = IN6_ADDR_GEN_MODE_STABLE_PRIVACY, + IPV6_LINK_LOCAL_ADDRESSS_GEN_MODE_RANDOM = IN6_ADDR_GEN_MODE_RANDOM, + _IPV6_LINK_LOCAL_ADDRESS_GEN_MODE_MAX, + _IPV6_LINK_LOCAL_ADDRESS_GEN_MODE_INVALID = -1 +} IPv6LinkLocalAddressGenMode; + +typedef struct Manager Manager; + +typedef struct NetworkDHCPServerEmitAddress { + bool emit; + struct in_addr *addresses; + size_t n_addresses; +} NetworkDHCPServerEmitAddress; + +struct Network { + Manager *manager; + + unsigned n_ref; + + char *name; + char *filename; + usec_t timestamp; + char *description; + + /* [Match] section */ + Set *match_mac; + Set *match_permanent_mac; + char **match_path; + char **match_driver; + char **match_type; + char **match_name; + char **match_property; + char **match_wlan_iftype; + char **match_ssid; + Set *match_bssid; + LIST_HEAD(Condition, conditions); + + /* Master or stacked netdevs */ + NetDev *bridge; + NetDev *bond; + NetDev *vrf; + NetDev *xfrm; + Hashmap *stacked_netdevs; + char *bridge_name; + char *bond_name; + char *vrf_name; + Hashmap *stacked_netdev_names; + + /* [Link] section */ + struct ether_addr *mac; + uint32_t mtu; + uint32_t group; + int arp; + int multicast; + int allmulticast; + bool unmanaged; + bool required_for_online; /* Is this network required to be considered online? */ + LinkOperationalStateRange required_operstate_for_online; + + /* misc settings */ + bool configure_without_carrier; + int ignore_carrier_loss; + KeepConfiguration keep_configuration; + char **bind_carrier; + bool default_route_on_device; + bool ip_masquerade; + + /* DHCP Client Support */ + AddressFamily dhcp; + DHCPClientIdentifier dhcp_client_identifier; + DUID duid; + uint32_t iaid; + bool iaid_set; + char *dhcp_vendor_class_identifier; + char *dhcp_mudurl; + char **dhcp_user_class; + char *dhcp_hostname; + uint64_t dhcp_max_attempts; + uint32_t dhcp_route_metric; + bool dhcp_route_metric_set; + uint32_t dhcp_route_table; + uint32_t dhcp_fallback_lease_lifetime; + uint32_t dhcp_route_mtu; + uint16_t dhcp_client_port; + int dhcp_critical; + int dhcp_ip_service_type; + bool dhcp_anonymize; + bool dhcp_send_hostname; + bool dhcp_broadcast; + bool dhcp_use_dns; + bool dhcp_use_dns_set; + bool dhcp_routes_to_dns; + bool dhcp_use_ntp; + bool dhcp_use_ntp_set; + bool dhcp_use_sip; + bool dhcp_use_mtu; + bool dhcp_use_routes; + int dhcp_use_gateway; + bool dhcp_use_timezone; + bool dhcp_use_hostname; + bool dhcp_route_table_set; + bool dhcp_send_release; + bool dhcp_send_decline; + DHCPUseDomains dhcp_use_domains; + Set *dhcp_deny_listed_ip; + Set *dhcp_allow_listed_ip; + Set *dhcp_request_options; + OrderedHashmap *dhcp_client_send_options; + OrderedHashmap *dhcp_client_send_vendor_options; + + /* DHCPv6 Client support*/ + bool dhcp6_use_dns; + bool dhcp6_use_dns_set; + bool dhcp6_use_ntp; + bool dhcp6_use_ntp_set; + bool dhcp6_rapid_commit; + uint8_t dhcp6_pd_length; + uint32_t dhcp6_route_metric; + bool dhcp6_route_metric_set; + char *dhcp6_mudurl; + char **dhcp6_user_class; + char **dhcp6_vendor_class; + struct in6_addr dhcp6_pd_address; + DHCP6ClientStartMode dhcp6_without_ra; + OrderedHashmap *dhcp6_client_send_options; + OrderedHashmap *dhcp6_client_send_vendor_options; + Set *dhcp6_request_options; + /* Start DHCPv6 PD also when 'O' RA flag is set, see RFC 7084, WPD-4 */ + bool dhcp6_force_pd_other_information; + + /* DHCP Server Support */ + bool dhcp_server; + NetworkDHCPServerEmitAddress dhcp_server_emit[_SD_DHCP_LEASE_SERVER_TYPE_MAX]; + bool dhcp_server_emit_router; + bool dhcp_server_emit_timezone; + char *dhcp_server_timezone; + usec_t dhcp_server_default_lease_time_usec, dhcp_server_max_lease_time_usec; + uint32_t dhcp_server_pool_offset; + uint32_t dhcp_server_pool_size; + OrderedHashmap *dhcp_server_send_options; + OrderedHashmap *dhcp_server_send_vendor_options; + + /* link local addressing support */ + AddressFamily link_local; + IPv6LinkLocalAddressGenMode ipv6ll_address_gen_mode; + bool ipv4ll_route; + + /* IPv6 RA support */ + RADVPrefixDelegation router_prefix_delegation; + usec_t router_lifetime_usec; + uint8_t router_preference; + bool router_managed; + bool router_other_information; + bool router_emit_dns; + bool router_emit_domains; + usec_t router_dns_lifetime_usec; + struct in6_addr *router_dns; + unsigned n_router_dns; + OrderedSet *router_search_domains; + + /* DHCPv6 Prefix Delegation support */ + int dhcp6_pd; + bool dhcp6_pd_announce; + bool dhcp6_pd_assign; + int64_t dhcp6_pd_subnet_id; + union in_addr_union dhcp6_pd_token; + + /* Bridge Support */ + int use_bpdu; + int hairpin; + int fast_leave; + int allow_port_to_be_root; + int unicast_flood; + int multicast_flood; + int multicast_to_unicast; + int neighbor_suppression; + int learning; + int bridge_proxy_arp; + int bridge_proxy_arp_wifi; + uint32_t cost; + uint16_t priority; + MulticastRouter multicast_router; + + /* Bridge VLAN */ + bool use_br_vlan; + uint16_t pvid; + uint32_t br_vid_bitmap[BRIDGE_VLAN_BITMAP_LEN]; + uint32_t br_untagged_bitmap[BRIDGE_VLAN_BITMAP_LEN]; + + /* CAN support */ + uint32_t can_bitrate; + unsigned can_sample_point; + uint32_t can_data_bitrate; + unsigned can_data_sample_point; + usec_t can_restart_us; + int can_triple_sampling; + int can_termination; + int can_listen_only; + int can_fd_mode; + int can_non_iso; + + /* sysctl settings */ + AddressFamily ip_forward; + int ipv4_accept_local; + int ipv6_dad_transmits; + int ipv6_hop_limit; + int proxy_arp; + uint32_t ipv6_mtu; + IPv6PrivacyExtensions ipv6_privacy_extensions; + int ipv6_proxy_ndp; + Set *ipv6_proxy_ndp_addresses; + + /* IPv6 accept RA */ + int ipv6_accept_ra; + bool ipv6_accept_ra_use_dns; + bool ipv6_accept_ra_use_autonomous_prefix; + bool ipv6_accept_ra_use_onlink_prefix; + bool active_slave; + bool primary_slave; + bool ipv6_accept_ra_route_table_set; + DHCPUseDomains ipv6_accept_ra_use_domains; + IPv6AcceptRAStartDHCP6Client ipv6_accept_ra_start_dhcp6_client; + uint32_t ipv6_accept_ra_route_table; + Set *ndisc_deny_listed_prefix; + OrderedSet *ipv6_tokens; + + /* LLDP support */ + LLDPMode lldp_mode; /* LLDP reception */ + LLDPEmit lldp_emit; /* LLDP transmission */ + char *lldp_mud; /* LLDP MUD URL */ + + OrderedHashmap *addresses_by_section; + Hashmap *routes_by_section; + Hashmap *nexthops_by_section; + Hashmap *fdb_entries_by_section; + Hashmap *mdb_entries_by_section; + Hashmap *neighbors_by_section; + Hashmap *address_labels_by_section; + Hashmap *prefixes_by_section; + Hashmap *route_prefixes_by_section; + Hashmap *rules_by_section; + OrderedHashmap *tc_by_section; + OrderedHashmap *sr_iov_by_section; + + /* All kinds of DNS configuration */ + struct in_addr_full **dns; + unsigned n_dns; + OrderedSet *search_domains, *route_domains; + int dns_default_route; + ResolveSupport llmnr; + ResolveSupport mdns; + DnssecMode dnssec_mode; + DnsOverTlsMode dns_over_tls_mode; + Set *dnssec_negative_trust_anchors; + + /* NTP */ + char **ntp; +}; + +Network *network_ref(Network *network); +Network *network_unref(Network *network); +DEFINE_TRIVIAL_CLEANUP_FUNC(Network*, network_unref); + +int network_load(Manager *manager, OrderedHashmap **networks); +int network_reload(Manager *manager); +int network_load_one(Manager *manager, OrderedHashmap **networks, const char *filename); +int network_verify(Network *network); + +int network_get_by_name(Manager *manager, const char *name, Network **ret); +int network_get(Manager *manager, unsigned short iftype, sd_device *device, + const char *ifname, char * const *alternative_names, const char *driver, + const struct ether_addr *mac, const struct ether_addr *permanent_mac, + enum nl80211_iftype wlan_iftype, const char *ssid, const struct ether_addr *bssid, + Network **ret); +int network_apply(Network *network, Link *link); +void network_apply_anonymize_if_set(Network *network); + +bool network_has_static_ipv6_configurations(Network *network); + +CONFIG_PARSER_PROTOTYPE(config_parse_stacked_netdev); +CONFIG_PARSER_PROTOTYPE(config_parse_tunnel); +CONFIG_PARSER_PROTOTYPE(config_parse_domains); +CONFIG_PARSER_PROTOTYPE(config_parse_dns); +CONFIG_PARSER_PROTOTYPE(config_parse_hostname); +CONFIG_PARSER_PROTOTYPE(config_parse_timezone); +CONFIG_PARSER_PROTOTYPE(config_parse_dnssec_negative_trust_anchors); +CONFIG_PARSER_PROTOTYPE(config_parse_ntp); +CONFIG_PARSER_PROTOTYPE(config_parse_required_for_online); +CONFIG_PARSER_PROTOTYPE(config_parse_keep_configuration); +CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_link_local_address_gen_mode); + +const struct ConfigPerfItem* network_network_gperf_lookup(const char *key, GPERF_LEN_TYPE length); + +const char* keep_configuration_to_string(KeepConfiguration i) _const_; +KeepConfiguration keep_configuration_from_string(const char *s) _pure_; + +const char* ipv6_link_local_address_gen_mode_to_string(IPv6LinkLocalAddressGenMode s) _const_; +IPv6LinkLocalAddressGenMode ipv6_link_local_address_gen_mode_from_string(const char *s) _pure_; diff --git a/src/network/networkd-nexthop.c b/src/network/networkd-nexthop.c new file mode 100644 index 0000000..4a09b4c --- /dev/null +++ b/src/network/networkd-nexthop.c @@ -0,0 +1,534 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2019 VMware, Inc. + */ + +#include <linux/nexthop.h> + +#include "alloc-util.h" +#include "netlink-util.h" +#include "networkd-link.h" +#include "networkd-manager.h" +#include "networkd-network.h" +#include "networkd-nexthop.h" +#include "parse-util.h" +#include "set.h" +#include "string-util.h" + +NextHop *nexthop_free(NextHop *nexthop) { + if (!nexthop) + return NULL; + + if (nexthop->network) { + assert(nexthop->section); + hashmap_remove(nexthop->network->nexthops_by_section, nexthop->section); + } + + network_config_section_free(nexthop->section); + + if (nexthop->link) { + set_remove(nexthop->link->nexthops, nexthop); + set_remove(nexthop->link->nexthops_foreign, nexthop); + } + + return mfree(nexthop); +} + +DEFINE_NETWORK_SECTION_FUNCTIONS(NextHop, nexthop_free); + +static int nexthop_new(NextHop **ret) { + _cleanup_(nexthop_freep) NextHop *nexthop = NULL; + + nexthop = new(NextHop, 1); + if (!nexthop) + return -ENOMEM; + + *nexthop = (NextHop) { + .family = AF_UNSPEC, + }; + + *ret = TAKE_PTR(nexthop); + + return 0; +} + +static int nexthop_new_static(Network *network, const char *filename, unsigned section_line, NextHop **ret) { + _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; + _cleanup_(nexthop_freep) NextHop *nexthop = NULL; + int r; + + assert(network); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = network_config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + nexthop = hashmap_get(network->nexthops_by_section, n); + if (nexthop) { + *ret = TAKE_PTR(nexthop); + return 0; + } + + r = nexthop_new(&nexthop); + if (r < 0) + return r; + + nexthop->protocol = RTPROT_STATIC; + nexthop->network = network; + nexthop->section = TAKE_PTR(n); + + r = hashmap_ensure_allocated(&network->nexthops_by_section, &network_config_hash_ops); + if (r < 0) + return r; + + r = hashmap_put(network->nexthops_by_section, nexthop->section, nexthop); + if (r < 0) + return r; + + *ret = TAKE_PTR(nexthop); + return 0; +} + +static void nexthop_hash_func(const NextHop *nexthop, struct siphash *state) { + assert(nexthop); + + siphash24_compress(&nexthop->id, sizeof(nexthop->id), state); + siphash24_compress(&nexthop->family, sizeof(nexthop->family), state); + + switch (nexthop->family) { + case AF_INET: + case AF_INET6: + siphash24_compress(&nexthop->gw, FAMILY_ADDRESS_SIZE(nexthop->family), state); + + break; + default: + /* treat any other address family as AF_UNSPEC */ + break; + } +} + +static int nexthop_compare_func(const NextHop *a, const NextHop *b) { + int r; + + r = CMP(a->id, b->id); + if (r != 0) + return r; + + r = CMP(a->family, b->family); + if (r != 0) + return r; + + if (IN_SET(a->family, AF_INET, AF_INET6)) + return memcmp(&a->gw, &b->gw, FAMILY_ADDRESS_SIZE(a->family)); + + return 0; +} + +DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( + nexthop_hash_ops, + NextHop, + nexthop_hash_func, + nexthop_compare_func, + nexthop_free); + +static int nexthop_get(Link *link, NextHop *in, NextHop **ret) { + NextHop *existing; + + assert(link); + assert(in); + + existing = set_get(link->nexthops, in); + if (existing) { + if (ret) + *ret = existing; + return 1; + } + + existing = set_get(link->nexthops_foreign, in); + if (existing) { + if (ret) + *ret = existing; + return 0; + } + + return -ENOENT; +} + +static int nexthop_add_internal(Link *link, Set **nexthops, NextHop *in, NextHop **ret) { + _cleanup_(nexthop_freep) NextHop *nexthop = NULL; + int r; + + assert(link); + assert(nexthops); + assert(in); + + r = nexthop_new(&nexthop); + if (r < 0) + return r; + + nexthop->id = in->id; + nexthop->family = in->family; + nexthop->gw = in->gw; + + r = set_ensure_put(nexthops, &nexthop_hash_ops, nexthop); + if (r < 0) + return r; + if (r == 0) + return -EEXIST; + + nexthop->link = link; + + if (ret) + *ret = nexthop; + + nexthop = NULL; + + return 0; +} + +static int nexthop_add_foreign(Link *link, NextHop *in, NextHop **ret) { + return nexthop_add_internal(link, &link->nexthops_foreign, in, ret); +} + +static int nexthop_add(Link *link, NextHop *in, NextHop **ret) { + NextHop *nexthop; + int r; + + r = nexthop_get(link, in, &nexthop); + if (r == -ENOENT) { + /* NextHop does not exist, create a new one */ + r = nexthop_add_internal(link, &link->nexthops, in, &nexthop); + if (r < 0) + return r; + } else if (r == 0) { + /* Take over a foreign nexthop */ + r = set_ensure_put(&link->nexthops, &nexthop_hash_ops, nexthop); + if (r < 0) + return r; + + set_remove(link->nexthops_foreign, nexthop); + } else if (r == 1) { + /* NextHop exists, do nothing */ + ; + } else + return r; + + if (ret) + *ret = nexthop; + + return 0; +} + +static int nexthop_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + assert(link->nexthop_messages > 0); + + link->nexthop_messages--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EEXIST) { + log_link_message_warning_errno(link, m, r, "Could not set nexthop"); + link_enter_failed(link); + return 1; + } + + if (link->nexthop_messages == 0) { + log_link_debug(link, "Nexthop set"); + link->static_nexthops_configured = true; + link_check_ready(link); + } + + return 1; +} + +static int nexthop_configure(NextHop *nexthop, Link *link) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(link); + assert(link->manager); + assert(link->manager->rtnl); + assert(link->ifindex > 0); + assert(IN_SET(nexthop->family, AF_INET, AF_INET6)); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *gw = NULL; + + if (!in_addr_is_null(nexthop->family, &nexthop->gw)) + (void) in_addr_to_string(nexthop->family, &nexthop->gw, &gw); + + log_link_debug(link, "Configuring nexthop: gw: %s", strna(gw)); + } + + r = sd_rtnl_message_new_nexthop(link->manager->rtnl, &req, + RTM_NEWNEXTHOP, nexthop->family, + nexthop->protocol); + if (r < 0) + return log_link_error_errno(link, r, "Could not create RTM_NEWNEXTHOP message: %m"); + + r = sd_netlink_message_append_u32(req, NHA_ID, nexthop->id); + if (r < 0) + return log_link_error_errno(link, r, "Could not append NHA_ID attribute: %m"); + + r = sd_netlink_message_append_u32(req, NHA_OIF, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not append NHA_OIF attribute: %m"); + + if (in_addr_is_null(nexthop->family, &nexthop->gw) == 0) { + r = netlink_message_append_in_addr_union(req, NHA_GATEWAY, nexthop->family, &nexthop->gw); + if (r < 0) + return log_link_error_errno(link, r, "Could not append NHA_GATEWAY attribute: %m"); + + r = sd_rtnl_message_nexthop_set_family(req, nexthop->family); + if (r < 0) + return log_link_error_errno(link, r, "Could not set nexthop family: %m"); + } + + r = netlink_call_async(link->manager->rtnl, NULL, req, nexthop_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + r = nexthop_add(link, nexthop, &nexthop); + if (r < 0) + return log_link_error_errno(link, r, "Could not add nexthop: %m"); + + return 1; +} + +int link_set_nexthop(Link *link) { + NextHop *nh; + int r; + + assert(link); + assert(link->network); + + link->static_nexthops_configured = false; + + HASHMAP_FOREACH(nh, link->network->nexthops_by_section) { + r = nexthop_configure(nh, link); + if (r < 0) + return log_link_warning_errno(link, r, "Could not set nexthop: %m"); + + link->nexthop_messages++; + } + + if (link->nexthop_messages == 0) { + link->static_nexthops_configured = true; + link_check_ready(link); + } else { + log_link_debug(link, "Setting nexthop"); + link_set_state(link, LINK_STATE_CONFIGURING); + } + + return 1; +} + +int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) { + _cleanup_(nexthop_freep) NextHop *tmp = NULL; + _cleanup_free_ char *gateway = NULL; + NextHop *nexthop = NULL; + uint32_t ifindex; + uint16_t type; + Link *link; + int r; + + assert(rtnl); + assert(message); + assert(m); + + if (sd_netlink_message_is_error(message)) { + r = sd_netlink_message_get_errno(message); + if (r < 0) + log_message_warning_errno(message, r, "rtnl: failed to receive rule message, ignoring"); + + return 0; + } + + r = sd_netlink_message_get_type(message, &type); + if (r < 0) { + log_warning_errno(r, "rtnl: could not get message type, ignoring: %m"); + return 0; + } else if (!IN_SET(type, RTM_NEWNEXTHOP, RTM_DELNEXTHOP)) { + log_warning("rtnl: received unexpected message type %u when processing nexthop, ignoring.", type); + return 0; + } + + r = sd_netlink_message_read_u32(message, NHA_OIF, &ifindex); + if (r == -ENODATA) { + log_warning_errno(r, "rtnl: received nexthop message without NHA_OIF attribute, ignoring: %m"); + return 0; + } else if (r < 0) { + log_warning_errno(r, "rtnl: could not get NHA_OIF attribute, ignoring: %m"); + return 0; + } else if (ifindex <= 0) { + log_warning("rtnl: received nexthop message with invalid ifindex %"PRIu32", ignoring.", ifindex); + return 0; + } + + r = link_get(m, ifindex, &link); + if (r < 0 || !link) { + if (!m->enumerating) + log_warning("rtnl: received nexthop message for link (%"PRIu32") we do not know about, ignoring", ifindex); + return 0; + } + + r = nexthop_new(&tmp); + if (r < 0) + return log_oom(); + + r = sd_rtnl_message_get_family(message, &tmp->family); + if (r < 0) { + log_link_warning_errno(link, r, "rtnl: could not get nexthop family, ignoring: %m"); + return 0; + } else if (!IN_SET(tmp->family, AF_INET, AF_INET6)) + return log_link_debug(link, "rtnl: received nexthop message with invalid family %d, ignoring.", tmp->family); + + r = netlink_message_read_in_addr_union(message, NHA_GATEWAY, tmp->family, &tmp->gw); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: could not get NHA_GATEWAY attribute, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_read_u32(message, NHA_ID, &tmp->id); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: could not get NHA_ID attribute, ignoring: %m"); + return 0; + } + + (void) nexthop_get(link, tmp, &nexthop); + + if (DEBUG_LOGGING) + (void) in_addr_to_string(tmp->family, &tmp->gw, &gateway); + + switch (type) { + case RTM_NEWNEXTHOP: + if (nexthop) + log_link_debug(link, "Received remembered nexthop: %s, id: %d", strna(gateway), tmp->id); + else { + log_link_debug(link, "Remembering foreign nexthop: %s, id: %d", strna(gateway), tmp->id); + r = nexthop_add_foreign(link, tmp, &nexthop); + if (r < 0) { + log_link_warning_errno(link, r, "Could not remember foreign nexthop, ignoring: %m"); + return 0; + } + } + break; + case RTM_DELNEXTHOP: + if (nexthop) { + log_link_debug(link, "Forgetting nexthop: %s, id: %d", strna(gateway), tmp->id); + nexthop_free(nexthop); + } else + log_link_debug(link, "Kernel removed a nexthop we don't remember: %s, id: %d, ignoring.", + strna(gateway), tmp->id); + break; + + default: + assert_not_reached("Received invalid RTNL message type"); + } + + return 1; +} + +static int nexthop_section_verify(NextHop *nh) { + if (section_is_invalid(nh->section)) + return -EINVAL; + + if (in_addr_is_null(nh->family, &nh->gw) < 0) + return -EINVAL; + + return 0; +} + +void network_drop_invalid_nexthops(Network *network) { + NextHop *nh; + + assert(network); + + HASHMAP_FOREACH(nh, network->nexthops_by_section) + if (nexthop_section_verify(nh) < 0) + nexthop_free(nh); +} + +int config_parse_nexthop_id( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = nexthop_new_static(network, filename, section_line, &n); + if (r < 0) + return log_oom(); + + r = safe_atou32(rvalue, &n->id); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Could not parse nexthop id \"%s\", ignoring assignment: %m", rvalue); + return 0; + } + + TAKE_PTR(n); + return 0; +} + +int config_parse_nexthop_gateway( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = nexthop_new_static(network, filename, section_line, &n); + if (r < 0) + return log_oom(); + + r = in_addr_from_string_auto(rvalue, &n->family, &n->gw); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue); + return 0; + } + + TAKE_PTR(n); + return 0; +} diff --git a/src/network/networkd-nexthop.h b/src/network/networkd-nexthop.h new file mode 100644 index 0000000..75714e7 --- /dev/null +++ b/src/network/networkd-nexthop.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2019 VMware, Inc. + */ + +#pragma once + +#include <inttypes.h> + +#include "sd-netlink.h" + +#include "conf-parser.h" +#include "in-addr-util.h" +#include "networkd-util.h" + +typedef struct Link Link; +typedef struct Manager Manager; +typedef struct Network Network; + +typedef struct NextHop { + Network *network; + NetworkConfigSection *section; + + Link *link; + + unsigned char protocol; + + uint32_t id; + int family; + union in_addr_union gw; +} NextHop; + +NextHop *nexthop_free(NextHop *nexthop); + +void network_drop_invalid_nexthops(Network *network); + +int link_set_nexthop(Link *link); + +int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message, Manager *m); + +CONFIG_PARSER_PROTOTYPE(config_parse_nexthop_id); +CONFIG_PARSER_PROTOTYPE(config_parse_nexthop_gateway); diff --git a/src/network/networkd-radv.c b/src/network/networkd-radv.c new file mode 100644 index 0000000..a8e1b2b --- /dev/null +++ b/src/network/networkd-radv.c @@ -0,0 +1,999 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2017 Intel Corporation. All rights reserved. +***/ + +#include <netinet/icmp6.h> +#include <arpa/inet.h> + +#include "dns-domain.h" +#include "networkd-link.h" +#include "networkd-manager.h" +#include "networkd-network.h" +#include "networkd-radv.h" +#include "parse-util.h" +#include "string-util.h" +#include "string-table.h" +#include "strv.h" + +Prefix *prefix_free(Prefix *prefix) { + if (!prefix) + return NULL; + + if (prefix->network) { + assert(prefix->section); + hashmap_remove(prefix->network->prefixes_by_section, prefix->section); + } + + network_config_section_free(prefix->section); + sd_radv_prefix_unref(prefix->radv_prefix); + + return mfree(prefix); +} + +DEFINE_NETWORK_SECTION_FUNCTIONS(Prefix, prefix_free); + +static int prefix_new(Prefix **ret) { + _cleanup_(prefix_freep) Prefix *prefix = NULL; + + prefix = new0(Prefix, 1); + if (!prefix) + return -ENOMEM; + + if (sd_radv_prefix_new(&prefix->radv_prefix) < 0) + return -ENOMEM; + + *ret = TAKE_PTR(prefix); + + return 0; +} + +static int prefix_new_static(Network *network, const char *filename, unsigned section_line, Prefix **ret) { + _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; + _cleanup_(prefix_freep) Prefix *prefix = NULL; + int r; + + assert(network); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = network_config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + prefix = hashmap_get(network->prefixes_by_section, n); + if (prefix) { + *ret = TAKE_PTR(prefix); + return 0; + } + + r = prefix_new(&prefix); + if (r < 0) + return r; + + prefix->network = network; + prefix->section = TAKE_PTR(n); + + r = hashmap_ensure_allocated(&network->prefixes_by_section, &network_config_hash_ops); + if (r < 0) + return r; + + r = hashmap_put(network->prefixes_by_section, prefix->section, prefix); + if (r < 0) + return r; + + *ret = TAKE_PTR(prefix); + + return 0; +} + +RoutePrefix *route_prefix_free(RoutePrefix *prefix) { + if (!prefix) + return NULL; + + if (prefix->network) { + assert(prefix->section); + hashmap_remove(prefix->network->route_prefixes_by_section, prefix->section); + } + + network_config_section_free(prefix->section); + sd_radv_route_prefix_unref(prefix->radv_route_prefix); + + return mfree(prefix); +} + +DEFINE_NETWORK_SECTION_FUNCTIONS(RoutePrefix, route_prefix_free); + +static int route_prefix_new(RoutePrefix **ret) { + _cleanup_(route_prefix_freep) RoutePrefix *prefix = NULL; + + prefix = new0(RoutePrefix, 1); + if (!prefix) + return -ENOMEM; + + if (sd_radv_route_prefix_new(&prefix->radv_route_prefix) < 0) + return -ENOMEM; + + *ret = TAKE_PTR(prefix); + + return 0; +} + +static int route_prefix_new_static(Network *network, const char *filename, unsigned section_line, RoutePrefix **ret) { + _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; + _cleanup_(route_prefix_freep) RoutePrefix *prefix = NULL; + int r; + + assert(network); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = network_config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + prefix = hashmap_get(network->route_prefixes_by_section, n); + if (prefix) { + *ret = TAKE_PTR(prefix); + return 0; + } + + r = route_prefix_new(&prefix); + if (r < 0) + return r; + + prefix->network = network; + prefix->section = TAKE_PTR(n); + + r = hashmap_ensure_allocated(&network->route_prefixes_by_section, &network_config_hash_ops); + if (r < 0) + return r; + + r = hashmap_put(network->route_prefixes_by_section, prefix->section, prefix); + if (r < 0) + return r; + + *ret = TAKE_PTR(prefix); + + return 0; +} + +void network_drop_invalid_prefixes(Network *network) { + Prefix *prefix; + + assert(network); + + HASHMAP_FOREACH(prefix, network->prefixes_by_section) + if (section_is_invalid(prefix->section)) + prefix_free(prefix); +} + +void network_drop_invalid_route_prefixes(Network *network) { + RoutePrefix *prefix; + + assert(network); + + HASHMAP_FOREACH(prefix, network->route_prefixes_by_section) + if (section_is_invalid(prefix->section)) + route_prefix_free(prefix); +} + +void network_adjust_radv(Network *network) { + assert(network); + + /* After this function is called, network->router_prefix_delegation can be treated as a boolean. */ + + if (network->dhcp6_pd < 0) + /* For backward compatibility. */ + network->dhcp6_pd = FLAGS_SET(network->router_prefix_delegation, RADV_PREFIX_DELEGATION_DHCP6); + + if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6)) { + if (network->router_prefix_delegation != RADV_PREFIX_DELEGATION_NONE) + log_warning("%s: IPv6PrefixDelegation= is enabled but IPv6 link local addressing is disabled. " + "Disabling IPv6PrefixDelegation=.", network->filename); + + network->router_prefix_delegation = RADV_PREFIX_DELEGATION_NONE; + } + + if (network->router_prefix_delegation == RADV_PREFIX_DELEGATION_NONE) { + network->n_router_dns = 0; + network->router_dns = mfree(network->router_dns); + network->router_search_domains = ordered_set_free(network->router_search_domains); + } + + if (!FLAGS_SET(network->router_prefix_delegation, RADV_PREFIX_DELEGATION_STATIC)) { + network->prefixes_by_section = hashmap_free_with_destructor(network->prefixes_by_section, prefix_free); + network->route_prefixes_by_section = hashmap_free_with_destructor(network->route_prefixes_by_section, route_prefix_free); + } +} + +int config_parse_prefix( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(prefix_free_or_set_invalidp) Prefix *p = NULL; + uint8_t prefixlen = 64; + union in_addr_union in6addr; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = prefix_new_static(network, filename, section_line, &p); + if (r < 0) + return log_oom(); + + r = in_addr_prefix_from_string(rvalue, AF_INET6, &in6addr, &prefixlen); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Prefix is invalid, ignoring assignment: %s", rvalue); + return 0; + } + + r = sd_radv_prefix_set_prefix(p->radv_prefix, &in6addr.in6, prefixlen); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to set radv prefix, ignoring assignment: %s", rvalue); + return 0; + } + + p = NULL; + + return 0; +} + +int config_parse_prefix_flags( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(prefix_free_or_set_invalidp) Prefix *p = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = prefix_new_static(network, filename, section_line, &p); + if (r < 0) + return log_oom(); + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse %s=, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + if (streq(lvalue, "OnLink")) + r = sd_radv_prefix_set_onlink(p->radv_prefix, r); + else if (streq(lvalue, "AddressAutoconfiguration")) + r = sd_radv_prefix_set_address_autoconfiguration(p->radv_prefix, r); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to set %s=, ignoring assignment: %m", lvalue); + return 0; + } + + p = NULL; + + return 0; +} + +int config_parse_prefix_lifetime( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(prefix_free_or_set_invalidp) Prefix *p = NULL; + usec_t usec; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = prefix_new_static(network, filename, section_line, &p); + if (r < 0) + return log_oom(); + + r = parse_sec(rvalue, &usec); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Lifetime is invalid, ignoring assignment: %s", rvalue); + return 0; + } + + /* a value of 0xffffffff represents infinity */ + if (streq(lvalue, "PreferredLifetimeSec")) + r = sd_radv_prefix_set_preferred_lifetime(p->radv_prefix, + DIV_ROUND_UP(usec, USEC_PER_SEC)); + else if (streq(lvalue, "ValidLifetimeSec")) + r = sd_radv_prefix_set_valid_lifetime(p->radv_prefix, + DIV_ROUND_UP(usec, USEC_PER_SEC)); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to set %s=, ignoring assignment: %m", lvalue); + return 0; + } + + p = NULL; + + return 0; +} + +int config_parse_prefix_assign( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(prefix_free_or_set_invalidp) Prefix *p = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = prefix_new_static(network, filename, section_line, &p); + if (r < 0) + return log_oom(); + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse %s=, ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + p->assign = r; + p = NULL; + + return 0; +} + +int config_parse_route_prefix( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(route_prefix_free_or_set_invalidp) RoutePrefix *p = NULL; + uint8_t prefixlen = 64; + union in_addr_union in6addr; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = route_prefix_new_static(network, filename, section_line, &p); + if (r < 0) + return log_oom(); + + r = in_addr_prefix_from_string(rvalue, AF_INET6, &in6addr, &prefixlen); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Route prefix is invalid, ignoring assignment: %s", rvalue); + return 0; + } + + r = sd_radv_prefix_set_route_prefix(p->radv_route_prefix, &in6addr.in6, prefixlen); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to set route prefix, ignoring assignment: %m"); + return 0; + } + + p = NULL; + + return 0; +} + +int config_parse_route_prefix_lifetime( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(route_prefix_free_or_set_invalidp) RoutePrefix *p = NULL; + usec_t usec; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = route_prefix_new_static(network, filename, section_line, &p); + if (r < 0) + return log_oom(); + + r = parse_sec(rvalue, &usec); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Route lifetime is invalid, ignoring assignment: %s", rvalue); + return 0; + } + + /* a value of 0xffffffff represents infinity */ + r = sd_radv_route_prefix_set_lifetime(p->radv_route_prefix, DIV_ROUND_UP(usec, USEC_PER_SEC)); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to set route lifetime, ignoring assignment: %m"); + return 0; + } + + p = NULL; + + return 0; +} + +static int network_get_ipv6_dns(Network *network, struct in6_addr **ret_addresses, size_t *ret_size) { + _cleanup_free_ struct in6_addr *addresses = NULL; + size_t n_addresses = 0, n_allocated = 0; + + assert(network); + assert(ret_addresses); + assert(ret_size); + + for (size_t i = 0; i < network->n_dns; i++) { + union in_addr_union *addr; + + if (network->dns[i]->family != AF_INET6) + continue; + + addr = &network->dns[i]->address; + + if (in_addr_is_null(AF_INET6, addr) || + in_addr_is_link_local(AF_INET6, addr) || + in_addr_is_localhost(AF_INET6, addr)) + continue; + + if (!GREEDY_REALLOC(addresses, n_allocated, n_addresses + 1)) + return -ENOMEM; + + addresses[n_addresses++] = addr->in6; + } + + *ret_addresses = TAKE_PTR(addresses); + *ret_size = n_addresses; + + return n_addresses; +} + +static int radv_set_dns(Link *link, Link *uplink) { + _cleanup_free_ struct in6_addr *dns = NULL; + usec_t lifetime_usec; + size_t n_dns; + int r; + + if (!link->network->router_emit_dns) + return 0; + + if (link->network->router_dns) { + struct in6_addr *p; + + dns = new(struct in6_addr, link->network->n_router_dns); + if (!dns) + return -ENOMEM; + + p = dns; + for (size_t i = 0; i < link->network->n_router_dns; i++) + if (IN6_IS_ADDR_UNSPECIFIED(&link->network->router_dns[i])) { + if (!IN6_IS_ADDR_UNSPECIFIED(&link->ipv6ll_address)) + *(p++) = link->ipv6ll_address; + } else + *(p++) = link->network->router_dns[i]; + + n_dns = p - dns; + lifetime_usec = link->network->router_dns_lifetime_usec; + + goto set_dns; + } + + lifetime_usec = SD_RADV_DEFAULT_DNS_LIFETIME_USEC; + + r = network_get_ipv6_dns(link->network, &dns, &n_dns); + if (r > 0) + goto set_dns; + + if (uplink) { + if (!uplink->network) { + log_link_debug(uplink, "Cannot fetch DNS servers as uplink interface is not managed by us"); + return 0; + } + + r = network_get_ipv6_dns(uplink->network, &dns, &n_dns); + if (r > 0) + goto set_dns; + } + + return 0; + + set_dns: + return sd_radv_set_rdnss(link->radv, + DIV_ROUND_UP(lifetime_usec, USEC_PER_SEC), + dns, n_dns); +} + +static int radv_set_domains(Link *link, Link *uplink) { + OrderedSet *search_domains; + usec_t lifetime_usec; + _cleanup_free_ char **s = NULL; /* just free() because the strings are owned by the set */ + + if (!link->network->router_emit_domains) + return 0; + + search_domains = link->network->router_search_domains; + lifetime_usec = link->network->router_dns_lifetime_usec; + + if (search_domains) + goto set_domains; + + lifetime_usec = SD_RADV_DEFAULT_DNS_LIFETIME_USEC; + + search_domains = link->network->search_domains; + if (search_domains) + goto set_domains; + + if (uplink) { + if (!uplink->network) { + log_link_debug(uplink, "Cannot fetch DNS search domains as uplink interface is not managed by us"); + return 0; + } + + search_domains = uplink->network->search_domains; + if (search_domains) + goto set_domains; + } + + return 0; + + set_domains: + s = ordered_set_get_strv(search_domains); + if (!s) + return log_oom(); + + return sd_radv_set_dnssl(link->radv, + DIV_ROUND_UP(lifetime_usec, USEC_PER_SEC), + s); + +} + +int radv_emit_dns(Link *link) { + Link *uplink; + int r; + + uplink = manager_find_uplink(link->manager, link); + + r = radv_set_dns(link, uplink); + if (r < 0) + log_link_warning_errno(link, r, "Could not set RA DNS: %m"); + + r = radv_set_domains(link, uplink); + if (r < 0) + log_link_warning_errno(link, r, "Could not set RA Domains: %m"); + + return 0; +} + +static bool link_radv_enabled(Link *link) { + assert(link); + + if (!link_ipv6ll_enabled(link)) + return false; + + return link->network->router_prefix_delegation; +} + +int radv_configure(Link *link) { + uint16_t router_lifetime; + RoutePrefix *q; + Prefix *p; + int r; + + assert(link); + assert(link->network); + + if (!link_radv_enabled(link)) + return 0; + + r = sd_radv_new(&link->radv); + if (r < 0) + return r; + + r = sd_radv_attach_event(link->radv, link->manager->event, 0); + if (r < 0) + return r; + + r = sd_radv_set_mac(link->radv, &link->hw_addr.addr.ether); + if (r < 0) + return r; + + r = sd_radv_set_ifindex(link->radv, link->ifindex); + if (r < 0) + return r; + + r = sd_radv_set_managed_information(link->radv, link->network->router_managed); + if (r < 0) + return r; + + r = sd_radv_set_other_information(link->radv, link->network->router_other_information); + if (r < 0) + return r; + + /* a value of UINT16_MAX represents infinity, 0x0 means this host is not a router */ + if (link->network->router_lifetime_usec == USEC_INFINITY) + router_lifetime = UINT16_MAX; + else if (link->network->router_lifetime_usec > (UINT16_MAX - 1) * USEC_PER_SEC) + router_lifetime = UINT16_MAX - 1; + else + router_lifetime = DIV_ROUND_UP(link->network->router_lifetime_usec, USEC_PER_SEC); + + r = sd_radv_set_router_lifetime(link->radv, router_lifetime); + if (r < 0) + return r; + + if (router_lifetime > 0) { + r = sd_radv_set_preference(link->radv, link->network->router_preference); + if (r < 0) + return r; + } + + HASHMAP_FOREACH(p, link->network->prefixes_by_section) { + r = sd_radv_add_prefix(link->radv, p->radv_prefix, false); + if (r == -EEXIST) + continue; + if (r == -ENOEXEC) { + log_link_warning_errno(link, r, "[IPv6Prefix] section configured without Prefix= setting, ignoring section."); + continue; + } + if (r < 0) + return r; + } + + HASHMAP_FOREACH(q, link->network->route_prefixes_by_section) { + r = sd_radv_add_route_prefix(link->radv, q->radv_route_prefix, false); + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + + return 0; +} + +int radv_update_mac(Link *link) { + bool restart; + int r; + + assert(link); + + if (!link->radv) + return 0; + + restart = sd_radv_is_running(link->radv); + + r = sd_radv_stop(link->radv); + if (r < 0) + return r; + + r = sd_radv_set_mac(link->radv, &link->hw_addr.addr.ether); + if (r < 0) + return r; + + if (restart) { + r = sd_radv_start(link->radv); + if (r < 0) + return r; + } + + return 0; +} + +int radv_add_prefix( + Link *link, + const struct in6_addr *prefix, + uint8_t prefix_len, + uint32_t lifetime_preferred, + uint32_t lifetime_valid) { + + _cleanup_(sd_radv_prefix_unrefp) sd_radv_prefix *p = NULL; + int r; + + assert(link); + + if (!link->radv) + return 0; + + r = sd_radv_prefix_new(&p); + if (r < 0) + return r; + + r = sd_radv_prefix_set_prefix(p, prefix, prefix_len); + if (r < 0) + return r; + + r = sd_radv_prefix_set_preferred_lifetime(p, lifetime_preferred); + if (r < 0) + return r; + + r = sd_radv_prefix_set_valid_lifetime(p, lifetime_valid); + if (r < 0) + return r; + + r = sd_radv_add_prefix(link->radv, p, true); + if (r < 0 && r != -EEXIST) + return r; + + return 0; +} + +int config_parse_radv_dns( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *n = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + n->n_router_dns = 0; + n->router_dns = mfree(n->router_dns); + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *w = NULL; + union in_addr_union a; + + r = extract_first_word(&p, &w, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to extract word, ignoring: %s", rvalue); + return 0; + } + if (r == 0) + return 0; + + if (streq(w, "_link_local")) + a = IN_ADDR_NULL; + else { + r = in_addr_from_string(AF_INET6, w, &a); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse DNS server address, ignoring: %s", w); + continue; + } + + if (in_addr_is_null(AF_INET6, &a)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "DNS server address is null, ignoring: %s", w); + continue; + } + } + + struct in6_addr *m; + m = reallocarray(n->router_dns, n->n_router_dns + 1, sizeof(struct in6_addr)); + if (!m) + return log_oom(); + + m[n->n_router_dns++] = a.in6; + n->router_dns = m; + } +} + +int config_parse_radv_search_domains( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *n = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + n->router_search_domains = ordered_set_free(n->router_search_domains); + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *w = NULL, *idna = NULL; + + r = extract_first_word(&p, &w, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to extract word, ignoring: %s", rvalue); + return 0; + } + if (r == 0) + return 0; + + r = dns_name_apply_idna(w, &idna); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to apply IDNA to domain name '%s', ignoring: %m", w); + continue; + } else if (r == 0) + /* transfer ownership to simplify subsequent operations */ + idna = TAKE_PTR(w); + + r = ordered_set_ensure_allocated(&n->router_search_domains, &string_hash_ops_free); + if (r < 0) + return log_oom(); + + r = ordered_set_consume(n->router_search_domains, TAKE_PTR(idna)); + if (r < 0) + return log_oom(); + } +} + +static const char * const radv_prefix_delegation_table[_RADV_PREFIX_DELEGATION_MAX] = { + [RADV_PREFIX_DELEGATION_NONE] = "no", + [RADV_PREFIX_DELEGATION_STATIC] = "static", + [RADV_PREFIX_DELEGATION_DHCP6] = "dhcpv6", + [RADV_PREFIX_DELEGATION_BOTH] = "yes", +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN( + radv_prefix_delegation, + RADVPrefixDelegation, + RADV_PREFIX_DELEGATION_BOTH); + +int config_parse_router_prefix_delegation( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + RADVPrefixDelegation val, *ra = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (streq(lvalue, "IPv6SendRA")) { + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid %s= setting, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + /* When IPv6SendRA= is enabled, only static prefixes are sent by default, and users + * need to explicitly enable DHCPv6PrefixDelegation=. */ + *ra = r ? RADV_PREFIX_DELEGATION_STATIC : RADV_PREFIX_DELEGATION_NONE; + return 0; + } + + /* For backward compatibility */ + val = radv_prefix_delegation_from_string(rvalue); + if (val < 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid %s= setting, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + *ra = val; + return 0; +} + +int config_parse_router_preference( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + if (streq(rvalue, "high")) + network->router_preference = SD_NDISC_PREFERENCE_HIGH; + else if (STR_IN_SET(rvalue, "medium", "normal", "default")) + network->router_preference = SD_NDISC_PREFERENCE_MEDIUM; + else if (streq(rvalue, "low")) + network->router_preference = SD_NDISC_PREFERENCE_LOW; + else + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid router preference, ignoring assignment: %s", rvalue); + + return 0; +} diff --git a/src/network/networkd-radv.h b/src/network/networkd-radv.h new file mode 100644 index 0000000..4dfbefe --- /dev/null +++ b/src/network/networkd-radv.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/*** + Copyright © 2017 Intel Corporation. All rights reserved. +***/ + +#include <inttypes.h> +#include <stdbool.h> + +#include "sd-radv.h" + +#include "in-addr-util.h" +#include "conf-parser.h" +#include "networkd-util.h" + +typedef struct Network Network; +typedef struct Link Link; + +typedef enum RADVPrefixDelegation { + RADV_PREFIX_DELEGATION_NONE = 0, + RADV_PREFIX_DELEGATION_STATIC = 1 << 0, + RADV_PREFIX_DELEGATION_DHCP6 = 1 << 1, + RADV_PREFIX_DELEGATION_BOTH = RADV_PREFIX_DELEGATION_STATIC | RADV_PREFIX_DELEGATION_DHCP6, + _RADV_PREFIX_DELEGATION_MAX, + _RADV_PREFIX_DELEGATION_INVALID = -1, +} RADVPrefixDelegation; + +typedef struct Prefix { + Network *network; + NetworkConfigSection *section; + + sd_radv_prefix *radv_prefix; + + bool assign; +} Prefix; + +typedef struct RoutePrefix { + Network *network; + NetworkConfigSection *section; + + sd_radv_route_prefix *radv_route_prefix; +} RoutePrefix; + +Prefix *prefix_free(Prefix *prefix); +RoutePrefix *route_prefix_free(RoutePrefix *prefix); + +void network_drop_invalid_prefixes(Network *network); +void network_drop_invalid_route_prefixes(Network *network); +void network_adjust_radv(Network *network); + +int radv_emit_dns(Link *link); +int radv_configure(Link *link); +int radv_update_mac(Link *link); +int radv_add_prefix(Link *link, const struct in6_addr *prefix, uint8_t prefix_len, + uint32_t lifetime_preferred, uint32_t lifetime_valid); + +const char* radv_prefix_delegation_to_string(RADVPrefixDelegation i) _const_; +RADVPrefixDelegation radv_prefix_delegation_from_string(const char *s) _pure_; + +CONFIG_PARSER_PROTOTYPE(config_parse_router_prefix_delegation); +CONFIG_PARSER_PROTOTYPE(config_parse_router_preference); +CONFIG_PARSER_PROTOTYPE(config_parse_prefix); +CONFIG_PARSER_PROTOTYPE(config_parse_prefix_flags); +CONFIG_PARSER_PROTOTYPE(config_parse_prefix_lifetime); +CONFIG_PARSER_PROTOTYPE(config_parse_prefix_assign); +CONFIG_PARSER_PROTOTYPE(config_parse_radv_dns); +CONFIG_PARSER_PROTOTYPE(config_parse_radv_search_domains); +CONFIG_PARSER_PROTOTYPE(config_parse_route_prefix); +CONFIG_PARSER_PROTOTYPE(config_parse_route_prefix_lifetime); diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c new file mode 100644 index 0000000..0ed8958 --- /dev/null +++ b/src/network/networkd-route.c @@ -0,0 +1,2537 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <linux/icmpv6.h> +#include <linux/ipv6_route.h> + +#include "alloc-util.h" +#include "netlink-util.h" +#include "networkd-ipv4ll.h" +#include "networkd-manager.h" +#include "networkd-network.h" +#include "networkd-nexthop.h" +#include "networkd-route.h" +#include "networkd-routing-policy-rule.h" +#include "parse-util.h" +#include "socket-netlink.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "strxcpyx.h" +#include "sysctl-util.h" +#include "vrf.h" + +#define ROUTES_DEFAULT_MAX_PER_FAMILY 4096U + +static uint32_t link_get_vrf_table(const Link *link) { + return link->network->vrf ? VRF(link->network->vrf)->table : RT_TABLE_MAIN; +} + +uint32_t link_get_dhcp_route_table(const Link *link) { + /* When the interface is part of an VRF use the VRFs routing table, unless + * another table is explicitly specified. */ + if (link->network->dhcp_route_table_set) + return link->network->dhcp_route_table; + return link_get_vrf_table(link); +} + +uint32_t link_get_ipv6_accept_ra_route_table(const Link *link) { + if (link->network->ipv6_accept_ra_route_table_set) + return link->network->ipv6_accept_ra_route_table; + return link_get_vrf_table(link); +} + +static const char * const route_type_table[__RTN_MAX] = { + [RTN_UNICAST] = "unicast", + [RTN_LOCAL] = "local", + [RTN_BROADCAST] = "broadcast", + [RTN_ANYCAST] = "anycast", + [RTN_MULTICAST] = "multicast", + [RTN_BLACKHOLE] = "blackhole", + [RTN_UNREACHABLE] = "unreachable", + [RTN_PROHIBIT] = "prohibit", + [RTN_THROW] = "throw", + [RTN_NAT] = "nat", + [RTN_XRESOLVE] = "xresolve", +}; + +assert_cc(__RTN_MAX <= UCHAR_MAX); +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(route_type, int); + +static const char * const route_scope_table[] = { + [RT_SCOPE_UNIVERSE] = "global", + [RT_SCOPE_SITE] = "site", + [RT_SCOPE_LINK] = "link", + [RT_SCOPE_HOST] = "host", + [RT_SCOPE_NOWHERE] = "nowhere", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(route_scope, int); + +#define ROUTE_SCOPE_STR_MAX CONST_MAX(DECIMAL_STR_MAX(int), STRLEN("nowhere") + 1) +static const char *format_route_scope(int scope, char *buf, size_t size) { + const char *s; + char *p = buf; + + s = route_scope_to_string(scope); + if (s) + strpcpy(&p, size, s); + else + strpcpyf(&p, size, "%d", scope); + + return buf; +} + +static const char * const route_table_table[] = { + [RT_TABLE_DEFAULT] = "default", + [RT_TABLE_MAIN] = "main", + [RT_TABLE_LOCAL] = "local", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(route_table, int); + +#define ROUTE_TABLE_STR_MAX CONST_MAX(DECIMAL_STR_MAX(int), STRLEN("default") + 1) +static const char *format_route_table(int table, char *buf, size_t size) { + const char *s; + char *p = buf; + + s = route_table_to_string(table); + if (s) + strpcpy(&p, size, s); + else + strpcpyf(&p, size, "%d", table); + + return buf; +} + +static const char * const route_protocol_table[] = { + [RTPROT_KERNEL] = "kernel", + [RTPROT_BOOT] = "boot", + [RTPROT_STATIC] = "static", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(route_protocol, int); + +static const char * const route_protocol_full_table[] = { + [RTPROT_REDIRECT] = "redirect", + [RTPROT_KERNEL] = "kernel", + [RTPROT_BOOT] = "boot", + [RTPROT_STATIC] = "static", + [RTPROT_GATED] = "gated", + [RTPROT_RA] = "ra", + [RTPROT_MRT] = "mrt", + [RTPROT_ZEBRA] = "zebra", + [RTPROT_BIRD] = "bird", + [RTPROT_DNROUTED] = "dnrouted", + [RTPROT_XORP] = "xorp", + [RTPROT_NTK] = "ntk", + [RTPROT_DHCP] = "dhcp", + [RTPROT_MROUTED] = "mrouted", + [RTPROT_BABEL] = "babel", + [RTPROT_BGP] = "bgp", + [RTPROT_ISIS] = "isis", + [RTPROT_OSPF] = "ospf", + [RTPROT_RIP] = "rip", + [RTPROT_EIGRP] = "eigrp", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(route_protocol_full, int); + +#define ROUTE_PROTOCOL_STR_MAX CONST_MAX(DECIMAL_STR_MAX(int), STRLEN("redirect") + 1) +static const char *format_route_protocol(int protocol, char *buf, size_t size) { + const char *s; + char *p = buf; + + s = route_protocol_full_to_string(protocol); + if (s) + strpcpy(&p, size, s); + else + strpcpyf(&p, size, "%d", protocol); + + return buf; +} + +static unsigned routes_max(void) { + static thread_local unsigned cached = 0; + + _cleanup_free_ char *s4 = NULL, *s6 = NULL; + unsigned val4 = ROUTES_DEFAULT_MAX_PER_FAMILY, val6 = ROUTES_DEFAULT_MAX_PER_FAMILY; + + if (cached > 0) + return cached; + + if (sysctl_read("net/ipv4/route/max_size", &s4) >= 0) { + truncate_nl(s4); + if (safe_atou(s4, &val4) >= 0 && + val4 == 2147483647U) + /* This is the default "no limit" value in the kernel */ + val4 = ROUTES_DEFAULT_MAX_PER_FAMILY; + } + + if (sysctl_read("net/ipv6/route/max_size", &s6) >= 0) { + truncate_nl(s6); + (void) safe_atou(s6, &val6); + } + + cached = MAX(ROUTES_DEFAULT_MAX_PER_FAMILY, val4) + + MAX(ROUTES_DEFAULT_MAX_PER_FAMILY, val6); + return cached; +} + +int route_new(Route **ret) { + _cleanup_(route_freep) Route *route = NULL; + + route = new(Route, 1); + if (!route) + return -ENOMEM; + + *route = (Route) { + .family = AF_UNSPEC, + .scope = RT_SCOPE_UNIVERSE, + .protocol = RTPROT_UNSPEC, + .type = RTN_UNICAST, + .table = RT_TABLE_MAIN, + .lifetime = USEC_INFINITY, + .quickack = -1, + .fast_open_no_cookie = -1, + .gateway_onlink = -1, + .ttl_propagate = -1, + }; + + *ret = TAKE_PTR(route); + + return 0; +} + +static int route_new_static(Network *network, const char *filename, unsigned section_line, Route **ret) { + _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; + _cleanup_(route_freep) Route *route = NULL; + int r; + + assert(network); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = network_config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + route = hashmap_get(network->routes_by_section, n); + if (route) { + *ret = TAKE_PTR(route); + return 0; + } + + if (hashmap_size(network->routes_by_section) >= routes_max()) + return -E2BIG; + + r = route_new(&route); + if (r < 0) + return r; + + route->protocol = RTPROT_STATIC; + route->network = network; + route->section = TAKE_PTR(n); + + r = hashmap_ensure_allocated(&network->routes_by_section, &network_config_hash_ops); + if (r < 0) + return r; + + r = hashmap_put(network->routes_by_section, route->section, route); + if (r < 0) + return r; + + *ret = TAKE_PTR(route); + return 0; +} + +Route *route_free(Route *route) { + if (!route) + return NULL; + + if (route->network) { + assert(route->section); + hashmap_remove(route->network->routes_by_section, route->section); + } + + network_config_section_free(route->section); + + if (route->link) { + NDiscRoute *n; + + set_remove(route->link->routes, route); + set_remove(route->link->routes_foreign, route); + set_remove(route->link->dhcp_routes, route); + set_remove(route->link->dhcp_routes_old, route); + set_remove(route->link->dhcp6_routes, route); + set_remove(route->link->dhcp6_routes_old, route); + set_remove(route->link->dhcp6_pd_routes, route); + set_remove(route->link->dhcp6_pd_routes_old, route); + SET_FOREACH(n, route->link->ndisc_routes) + if (n->route == route) + free(set_remove(route->link->ndisc_routes, n)); + } + + if (route->manager) { + set_remove(route->manager->routes, route); + set_remove(route->manager->routes_foreign, route); + } + + ordered_set_free_free(route->multipath_routes); + + sd_event_source_unref(route->expire); + + return mfree(route); +} + +void route_hash_func(const Route *route, struct siphash *state) { + assert(route); + + siphash24_compress(&route->family, sizeof(route->family), state); + + switch (route->family) { + case AF_INET: + case AF_INET6: + siphash24_compress(&route->dst_prefixlen, sizeof(route->dst_prefixlen), state); + siphash24_compress(&route->dst, FAMILY_ADDRESS_SIZE(route->family), state); + + siphash24_compress(&route->src_prefixlen, sizeof(route->src_prefixlen), state); + siphash24_compress(&route->src, FAMILY_ADDRESS_SIZE(route->family), state); + + siphash24_compress(&route->gw_family, sizeof(route->gw_family), state); + if (IN_SET(route->gw_family, AF_INET, AF_INET6)) { + siphash24_compress(&route->gw, FAMILY_ADDRESS_SIZE(route->gw_family), state); + siphash24_compress(&route->gw_weight, sizeof(route->gw_weight), state); + } + + siphash24_compress(&route->prefsrc, FAMILY_ADDRESS_SIZE(route->family), state); + + siphash24_compress(&route->tos, sizeof(route->tos), state); + siphash24_compress(&route->priority, sizeof(route->priority), state); + siphash24_compress(&route->table, sizeof(route->table), state); + siphash24_compress(&route->protocol, sizeof(route->protocol), state); + siphash24_compress(&route->scope, sizeof(route->scope), state); + siphash24_compress(&route->type, sizeof(route->type), state); + + siphash24_compress(&route->initcwnd, sizeof(route->initcwnd), state); + siphash24_compress(&route->initrwnd, sizeof(route->initrwnd), state); + + break; + default: + /* treat any other address family as AF_UNSPEC */ + break; + } +} + +int route_compare_func(const Route *a, const Route *b) { + int r; + + r = CMP(a->family, b->family); + if (r != 0) + return r; + + switch (a->family) { + case AF_INET: + case AF_INET6: + r = CMP(a->dst_prefixlen, b->dst_prefixlen); + if (r != 0) + return r; + + r = memcmp(&a->dst, &b->dst, FAMILY_ADDRESS_SIZE(a->family)); + if (r != 0) + return r; + + r = CMP(a->src_prefixlen, b->src_prefixlen); + if (r != 0) + return r; + + r = memcmp(&a->src, &b->src, FAMILY_ADDRESS_SIZE(a->family)); + if (r != 0) + return r; + + r = CMP(a->gw_family, b->gw_family); + if (r != 0) + return r; + + if (IN_SET(a->gw_family, AF_INET, AF_INET6)) { + r = memcmp(&a->gw, &b->gw, FAMILY_ADDRESS_SIZE(a->family)); + if (r != 0) + return r; + + r = CMP(a->gw_weight, b->gw_weight); + if (r != 0) + return r; + } + + r = memcmp(&a->prefsrc, &b->prefsrc, FAMILY_ADDRESS_SIZE(a->family)); + if (r != 0) + return r; + + r = CMP(a->tos, b->tos); + if (r != 0) + return r; + + r = CMP(a->priority, b->priority); + if (r != 0) + return r; + + r = CMP(a->table, b->table); + if (r != 0) + return r; + + r = CMP(a->protocol, b->protocol); + if (r != 0) + return r; + + r = CMP(a->scope, b->scope); + if (r != 0) + return r; + + r = CMP(a->type, b->type); + if (r != 0) + return r; + + r = CMP(a->initcwnd, b->initcwnd); + if (r != 0) + return r; + + r = CMP(a->initrwnd, b->initrwnd); + if (r != 0) + return r; + + return 0; + default: + /* treat any other address family as AF_UNSPEC */ + return 0; + } +} + +DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( + route_hash_ops, + Route, + route_hash_func, + route_compare_func, + route_free); + +static bool route_equal(const Route *r1, const Route *r2) { + if (r1 == r2) + return true; + + if (!r1 || !r2) + return false; + + return route_compare_func(r1, r2) == 0; +} + +static int route_get(const Manager *manager, const Link *link, const Route *in, Route **ret) { + Route *existing; + + assert(manager || link); + assert(in); + + if (link) { + existing = set_get(link->routes, in); + if (existing) { + if (ret) + *ret = existing; + return 1; + } + + existing = set_get(link->routes_foreign, in); + if (existing) { + if (ret) + *ret = existing; + return 0; + } + } else { + existing = set_get(manager->routes, in); + if (existing) { + if (ret) + *ret = existing; + return 1; + } + + existing = set_get(manager->routes_foreign, in); + if (existing) { + if (ret) + *ret = existing; + return 0; + } + } + + return -ENOENT; +} + +static void route_copy(Route *dest, const Route *src, const MultipathRoute *m) { + assert(dest); + assert(src); + + dest->family = src->family; + dest->src = src->src; + dest->src_prefixlen = src->src_prefixlen; + dest->dst = src->dst; + dest->dst_prefixlen = src->dst_prefixlen; + dest->prefsrc = src->prefsrc; + dest->scope = src->scope; + dest->protocol = src->protocol; + dest->type = src->type; + dest->tos = src->tos; + dest->priority = src->priority; + dest->table = src->table; + dest->initcwnd = src->initcwnd; + dest->initrwnd = src->initrwnd; + dest->lifetime = src->lifetime; + + if (m) { + dest->gw_family = m->gateway.family; + dest->gw = m->gateway.address; + dest->gw_weight = m->weight; + } else { + dest->gw_family = src->gw_family; + dest->gw = src->gw; + dest->gw_weight = src->gw_weight; + } +} + +static int route_add_internal(Manager *manager, Link *link, Set **routes, const Route *in, Route **ret) { + _cleanup_(route_freep) Route *route = NULL; + int r; + + assert(manager || link); + assert(routes); + assert(in); + + r = route_new(&route); + if (r < 0) + return r; + + route_copy(route, in, NULL); + + r = set_ensure_put(routes, &route_hash_ops, route); + if (r < 0) + return r; + if (r == 0) + return -EEXIST; + + route->link = link; + route->manager = manager; + + if (ret) + *ret = route; + + route = NULL; + + return 0; +} + +static int route_add_foreign(Manager *manager, Link *link, const Route *in, Route **ret) { + assert(manager || link); + return route_add_internal(manager, link, link ? &link->routes_foreign : &manager->routes_foreign, in, ret); +} + +static int route_add(Manager *manager, Link *link, const Route *in, const MultipathRoute *m, Route **ret) { + _cleanup_(route_freep) Route *tmp = NULL; + Route *route; + int r; + + assert(manager || link); + assert(in); + + if (m) { + assert(link && (m->ifindex == 0 || m->ifindex == link->ifindex)); + + r = route_new(&tmp); + if (r < 0) + return r; + + route_copy(tmp, in, m); + in = tmp; + } + + r = route_get(manager, link, in, &route); + if (r == -ENOENT) { + /* Route does not exist, create a new one */ + r = route_add_internal(manager, link, link ? &link->routes : &manager->routes, in, &route); + if (r < 0) + return r; + } else if (r == 0) { + /* Take over a foreign route */ + if (link) { + r = set_ensure_put(&link->routes, &route_hash_ops, route); + if (r < 0) + return r; + + set_remove(link->routes_foreign, route); + } else { + r = set_ensure_put(&manager->routes, &route_hash_ops, route); + if (r < 0) + return r; + + set_remove(manager->routes_foreign, route); + } + } else if (r == 1) { + /* Route exists, do nothing */ + ; + } else + return r; + + if (ret) + *ret = route; + + return 0; +} + +static int route_set_netlink_message(const Route *route, sd_netlink_message *req, Link *link) { + unsigned flags; + int r; + + assert(route); + assert(req); + + /* link may be NULL */ + + if (in_addr_is_null(route->gw_family, &route->gw) == 0) { + if (route->gw_family == route->family) { + r = netlink_message_append_in_addr_union(req, RTA_GATEWAY, route->gw_family, &route->gw); + if (r < 0) + return log_link_error_errno(link, r, "Could not append RTA_GATEWAY attribute: %m"); + } else { + RouteVia rtvia = { + .family = route->gw_family, + .address = route->gw, + }; + + r = sd_netlink_message_append_data(req, RTA_VIA, &rtvia, sizeof(rtvia)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append RTA_VIA attribute: %m"); + } + } + + if (route->dst_prefixlen > 0) { + r = netlink_message_append_in_addr_union(req, RTA_DST, route->family, &route->dst); + if (r < 0) + return log_link_error_errno(link, r, "Could not append RTA_DST attribute: %m"); + + r = sd_rtnl_message_route_set_dst_prefixlen(req, route->dst_prefixlen); + if (r < 0) + return log_link_error_errno(link, r, "Could not set destination prefix length: %m"); + } + + if (route->src_prefixlen > 0) { + r = netlink_message_append_in_addr_union(req, RTA_SRC, route->family, &route->src); + if (r < 0) + return log_link_error_errno(link, r, "Could not append RTA_SRC attribute: %m"); + + r = sd_rtnl_message_route_set_src_prefixlen(req, route->src_prefixlen); + if (r < 0) + return log_link_error_errno(link, r, "Could not set source prefix length: %m"); + } + + if (in_addr_is_null(route->family, &route->prefsrc) == 0) { + r = netlink_message_append_in_addr_union(req, RTA_PREFSRC, route->family, &route->prefsrc); + if (r < 0) + return log_link_error_errno(link, r, "Could not append RTA_PREFSRC attribute: %m"); + } + + r = sd_rtnl_message_route_set_scope(req, route->scope); + if (r < 0) + return log_link_error_errno(link, r, "Could not set scope: %m"); + + flags = route->flags; + if (route->gateway_onlink >= 0) + SET_FLAG(flags, RTNH_F_ONLINK, route->gateway_onlink); + + r = sd_rtnl_message_route_set_flags(req, flags); + if (r < 0) + return log_link_error_errno(link, r, "Could not set flags: %m"); + + if (route->table != RT_TABLE_MAIN) { + if (route->table < 256) { + r = sd_rtnl_message_route_set_table(req, route->table); + if (r < 0) + return log_link_error_errno(link, r, "Could not set route table: %m"); + } else { + r = sd_rtnl_message_route_set_table(req, RT_TABLE_UNSPEC); + if (r < 0) + return log_link_error_errno(link, r, "Could not set route table: %m"); + + /* Table attribute to allow more than 256. */ + r = sd_netlink_message_append_data(req, RTA_TABLE, &route->table, sizeof(route->table)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append RTA_TABLE attribute: %m"); + } + } + + r = sd_rtnl_message_route_set_type(req, route->type); + if (r < 0) + return log_link_error_errno(link, r, "Could not set route type: %m"); + + if (!IN_SET(route->type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE, RTN_THROW)) { + assert(link); /* Those routes must be attached to a specific link */ + + r = sd_netlink_message_append_u32(req, RTA_OIF, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not append RTA_OIF attribute: %m"); + } + + r = sd_netlink_message_append_u8(req, RTA_PREF, route->pref); + if (r < 0) + return log_link_error_errno(link, r, "Could not append RTA_PREF attribute: %m"); + + r = sd_netlink_message_append_u32(req, RTA_PRIORITY, route->priority); + if (r < 0) + return log_link_error_errno(link, r, "Could not append RTA_PRIORITY attribute: %m"); + + return 0; +} + +static int route_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(m); + + /* Note that link may be NULL. */ + if (link && IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -ESRCH) + log_link_message_warning_errno(link, m, r, "Could not drop route, ignoring"); + + return 1; +} + +int route_remove( + const Route *route, + Manager *manager, + Link *link, + link_netlink_message_handler_t callback) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(link || manager); + assert(IN_SET(route->family, AF_INET, AF_INET6)); + + if (!manager) + manager = link->manager; + /* link may be NULL! */ + + r = sd_rtnl_message_new_route(manager->rtnl, &req, + RTM_DELROUTE, route->family, + route->protocol); + if (r < 0) + return log_link_error_errno(link, r, "Could not create RTM_DELROUTE message: %m"); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *dst = NULL, *dst_prefixlen = NULL, *src = NULL, *gw = NULL, *prefsrc = NULL; + char scope[ROUTE_SCOPE_STR_MAX], table[ROUTE_TABLE_STR_MAX], protocol[ROUTE_PROTOCOL_STR_MAX]; + + if (!in_addr_is_null(route->family, &route->dst)) { + (void) in_addr_to_string(route->family, &route->dst, &dst); + (void) asprintf(&dst_prefixlen, "/%u", route->dst_prefixlen); + } + if (!in_addr_is_null(route->family, &route->src)) + (void) in_addr_to_string(route->family, &route->src, &src); + if (!in_addr_is_null(route->gw_family, &route->gw)) + (void) in_addr_to_string(route->gw_family, &route->gw, &gw); + if (!in_addr_is_null(route->family, &route->prefsrc)) + (void) in_addr_to_string(route->family, &route->prefsrc, &prefsrc); + + log_link_debug(link, "Removing route: dst: %s%s, src: %s, gw: %s, prefsrc: %s, scope: %s, table: %s, proto: %s, type: %s", + strna(dst), strempty(dst_prefixlen), strna(src), strna(gw), strna(prefsrc), + format_route_scope(route->scope, scope, sizeof(scope)), + format_route_table(route->table, table, sizeof(table)), + format_route_protocol(route->protocol, protocol, sizeof(protocol)), + strna(route_type_to_string(route->type))); + } + + r = route_set_netlink_message(route, req, link); + if (r < 0) + return r; + + r = netlink_call_async(manager->rtnl, NULL, req, + callback ?: route_remove_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); /* link may be NULL, link_ref() is OK with that */ + + return 0; +} + +static bool link_has_route(const Link *link, const Route *route) { + Route *net_route; + + assert(link); + assert(route); + + if (!link->network) + return false; + + HASHMAP_FOREACH(net_route, link->network->routes_by_section) + if (route_equal(net_route, route)) + return true; + + return false; +} + +static bool links_have_route(Manager *manager, const Route *route, const Link *except) { + Link *link; + + assert(manager); + + HASHMAP_FOREACH(link, manager->links) { + if (link == except) + continue; + + if (link_has_route(link, route)) + return true; + } + + return false; +} + +static int manager_drop_foreign_routes(Manager *manager) { + Route *route; + int k, r = 0; + + assert(manager); + + SET_FOREACH(route, manager->routes_foreign) { + /* do not touch routes managed by the kernel */ + if (route->protocol == RTPROT_KERNEL) + continue; + + if (links_have_route(manager, route, NULL)) + /* The route will be configured later. */ + continue; + + /* The existing links do not have the route. Let's drop this now. It may by + * re-configured later. */ + k = route_remove(route, manager, NULL, NULL); + if (k < 0 && r >= 0) + r = k; + } + + return r; +} + +static int manager_drop_routes(Manager *manager, Link *except) { + Route *route; + int k, r = 0; + + assert(manager); + + SET_FOREACH(route, manager->routes) { + /* do not touch routes managed by the kernel */ + if (route->protocol == RTPROT_KERNEL) + continue; + + if (links_have_route(manager, route, except)) + /* The route will be configured later. */ + continue; + + /* The existing links do not have the route. Let's drop this now. It may by + * re-configured later. */ + k = route_remove(route, manager, NULL, NULL); + if (k < 0 && r >= 0) + r = k; + } + + return r; +} + +int link_drop_foreign_routes(Link *link) { + Route *route; + int k, r = 0; + + assert(link); + assert(link->manager); + + SET_FOREACH(route, link->routes_foreign) { + /* do not touch routes managed by the kernel */ + if (route->protocol == RTPROT_KERNEL) + continue; + + /* do not touch multicast route added by kernel */ + /* FIXME: Why the kernel adds this route with protocol RTPROT_BOOT??? We need to investigate that. + * https://tools.ietf.org/html/rfc4862#section-5.4 may explain why. */ + if (route->protocol == RTPROT_BOOT && + route->family == AF_INET6 && + route->dst_prefixlen == 8 && + in_addr_equal(AF_INET6, &route->dst, &(union in_addr_union) { .in6 = {{{ 0xff,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 }}} })) + continue; + + if (route->protocol == RTPROT_STATIC && link->network && + FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_STATIC)) + continue; + + if (route->protocol == RTPROT_DHCP && link->network && + FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP)) + continue; + + if (link_has_route(link, route)) + k = route_add(NULL, link, route, NULL, NULL); + else + k = route_remove(route, NULL, link, NULL); + if (k < 0 && r >= 0) + r = k; + } + + k = manager_drop_foreign_routes(link->manager); + if (k < 0 && r >= 0) + r = k; + + return r; +} + +int link_drop_routes(Link *link) { + Route *route; + int k, r = 0; + + assert(link); + + SET_FOREACH(route, link->routes) { + /* do not touch routes managed by the kernel */ + if (route->protocol == RTPROT_KERNEL) + continue; + + k = route_remove(route, NULL, link, NULL); + if (k < 0 && r >= 0) + r = k; + } + + k = manager_drop_routes(link->manager, link); + if (k < 0 && r >= 0) + r = k; + + return r; +} + +static int route_expire_handler(sd_event_source *s, uint64_t usec, void *userdata) { + Route *route = userdata; + int r; + + assert(route); + + r = route_remove(route, route->manager, route->link, NULL); + if (r < 0) { + log_link_warning_errno(route->link, r, "Could not remove route: %m"); + route_free(route); + } + + return 1; +} + +static int route_add_and_setup_timer(Link *link, const Route *route, const MultipathRoute *m, Route **ret) { + _cleanup_(sd_event_source_unrefp) sd_event_source *expire = NULL; + Route *nr; + int r; + + assert(link); + assert(route); + + if (IN_SET(route->type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE, RTN_THROW)) + r = route_add(link->manager, NULL, route, NULL, &nr); + else if (!m || m->ifindex == 0 || m->ifindex == link->ifindex) + r = route_add(NULL, link, route, m, &nr); + else { + Link *link_gw; + + r = link_get(link->manager, m->ifindex, &link_gw); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get link with ifindex %d: %m", m->ifindex); + + r = route_add(NULL, link_gw, route, m, &nr); + } + if (r < 0) + return log_link_error_errno(link, r, "Could not add route: %m"); + + /* TODO: drop expiration handling once it can be pushed into the kernel */ + if (nr->lifetime != USEC_INFINITY && !kernel_route_expiration_supported()) { + r = sd_event_add_time(link->manager->event, &expire, clock_boottime_or_monotonic(), + nr->lifetime, 0, route_expire_handler, nr); + if (r < 0) + return log_link_error_errno(link, r, "Could not arm expiration timer: %m"); + } + + sd_event_source_unref(nr->expire); + nr->expire = TAKE_PTR(expire); + + if (ret) + *ret = nr; + + return 0; +} + +static int append_nexthop_one(const Route *route, const MultipathRoute *m, struct rtattr **rta, size_t offset) { + struct rtnexthop *rtnh; + struct rtattr *new_rta; + int r; + + assert(route); + assert(m); + assert(rta); + assert(*rta); + + new_rta = realloc(*rta, RTA_ALIGN((*rta)->rta_len) + RTA_SPACE(sizeof(struct rtnexthop))); + if (!new_rta) + return -ENOMEM; + *rta = new_rta; + + rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset); + *rtnh = (struct rtnexthop) { + .rtnh_len = sizeof(*rtnh), + .rtnh_ifindex = m->ifindex, + .rtnh_hops = m->weight > 0 ? m->weight - 1 : 0, + }; + + (*rta)->rta_len += sizeof(struct rtnexthop); + + if (route->family == m->gateway.family) { + r = rtattr_append_attribute(rta, RTA_GATEWAY, &m->gateway.address, FAMILY_ADDRESS_SIZE(m->gateway.family)); + if (r < 0) + goto clear; + rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset); + rtnh->rtnh_len += RTA_SPACE(FAMILY_ADDRESS_SIZE(m->gateway.family)); + } else { + r = rtattr_append_attribute(rta, RTA_VIA, &m->gateway, FAMILY_ADDRESS_SIZE(m->gateway.family) + sizeof(m->gateway.family)); + if (r < 0) + goto clear; + rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset); + rtnh->rtnh_len += RTA_SPACE(FAMILY_ADDRESS_SIZE(m->gateway.family) + sizeof(m->gateway.family)); + } + + return 0; + +clear: + (*rta)->rta_len -= sizeof(struct rtnexthop); + return r; +} + +static int append_nexthops(const Route *route, sd_netlink_message *req) { + _cleanup_free_ struct rtattr *rta = NULL; + struct rtnexthop *rtnh; + MultipathRoute *m; + size_t offset; + int r; + + if (ordered_set_isempty(route->multipath_routes)) + return 0; + + rta = new(struct rtattr, 1); + if (!rta) + return -ENOMEM; + + *rta = (struct rtattr) { + .rta_type = RTA_MULTIPATH, + .rta_len = RTA_LENGTH(0), + }; + offset = (uint8_t *) RTA_DATA(rta) - (uint8_t *) rta; + + ORDERED_SET_FOREACH(m, route->multipath_routes) { + r = append_nexthop_one(route, m, &rta, offset); + if (r < 0) + return r; + + rtnh = (struct rtnexthop *)((uint8_t *) rta + offset); + offset = (uint8_t *) RTNH_NEXT(rtnh) - (uint8_t *) rta; + } + + r = sd_netlink_message_append_data(req, RTA_MULTIPATH, RTA_DATA(rta), RTA_PAYLOAD(rta)); + if (r < 0) + return r; + + return 0; +} + +int route_configure( + const Route *route, + Link *link, + link_netlink_message_handler_t callback, + Route **ret) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(link); + assert(link->manager); + assert(link->manager->rtnl); + assert(link->ifindex > 0); + assert(IN_SET(route->family, AF_INET, AF_INET6)); + assert(callback); + + if (route_get(link->manager, link, route, NULL) <= 0 && + set_size(link->routes) >= routes_max()) + return log_link_error_errno(link, SYNTHETIC_ERRNO(E2BIG), + "Too many routes are configured, refusing: %m"); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *dst = NULL, *dst_prefixlen = NULL, *src = NULL, *gw = NULL, *prefsrc = NULL; + char scope[ROUTE_SCOPE_STR_MAX], table[ROUTE_TABLE_STR_MAX], protocol[ROUTE_PROTOCOL_STR_MAX]; + + if (!in_addr_is_null(route->family, &route->dst)) { + (void) in_addr_to_string(route->family, &route->dst, &dst); + (void) asprintf(&dst_prefixlen, "/%u", route->dst_prefixlen); + } + if (!in_addr_is_null(route->family, &route->src)) + (void) in_addr_to_string(route->family, &route->src, &src); + if (!in_addr_is_null(route->gw_family, &route->gw)) + (void) in_addr_to_string(route->gw_family, &route->gw, &gw); + if (!in_addr_is_null(route->family, &route->prefsrc)) + (void) in_addr_to_string(route->family, &route->prefsrc, &prefsrc); + + log_link_debug(link, "Configuring route: dst: %s%s, src: %s, gw: %s, prefsrc: %s, scope: %s, table: %s, proto: %s, type: %s", + strna(dst), strempty(dst_prefixlen), strna(src), strna(gw), strna(prefsrc), + format_route_scope(route->scope, scope, sizeof(scope)), + format_route_table(route->table, table, sizeof(table)), + format_route_protocol(route->protocol, protocol, sizeof(protocol)), + strna(route_type_to_string(route->type))); + } + + r = sd_rtnl_message_new_route(link->manager->rtnl, &req, + RTM_NEWROUTE, route->family, + route->protocol); + if (r < 0) + return log_link_error_errno(link, r, "Could not create RTM_NEWROUTE message: %m"); + + r = route_set_netlink_message(route, req, link); + if (r < 0) + return r; + + if (route->lifetime != USEC_INFINITY && kernel_route_expiration_supported()) { + r = sd_netlink_message_append_u32(req, RTA_EXPIRES, + DIV_ROUND_UP(usec_sub_unsigned(route->lifetime, now(clock_boottime_or_monotonic())), USEC_PER_SEC)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append RTA_EXPIRES attribute: %m"); + } + + if (route->ttl_propagate >= 0) { + r = sd_netlink_message_append_u8(req, RTA_TTL_PROPAGATE, route->ttl_propagate); + if (r < 0) + return log_link_error_errno(link, r, "Could not append RTA_TTL_PROPAGATE attribute: %m"); + } + + r = sd_netlink_message_open_container(req, RTA_METRICS); + if (r < 0) + return log_link_error_errno(link, r, "Could not append RTA_METRICS attribute: %m"); + + if (route->mtu > 0) { + r = sd_netlink_message_append_u32(req, RTAX_MTU, route->mtu); + if (r < 0) + return log_link_error_errno(link, r, "Could not append RTAX_MTU attribute: %m"); + } + + if (route->initcwnd > 0) { + r = sd_netlink_message_append_u32(req, RTAX_INITCWND, route->initcwnd); + if (r < 0) + return log_link_error_errno(link, r, "Could not append RTAX_INITCWND attribute: %m"); + } + + if (route->initrwnd > 0) { + r = sd_netlink_message_append_u32(req, RTAX_INITRWND, route->initrwnd); + if (r < 0) + return log_link_error_errno(link, r, "Could not append RTAX_INITRWND attribute: %m"); + } + + if (route->quickack >= 0) { + r = sd_netlink_message_append_u32(req, RTAX_QUICKACK, route->quickack); + if (r < 0) + return log_link_error_errno(link, r, "Could not append RTAX_QUICKACK attribute: %m"); + } + + if (route->fast_open_no_cookie >= 0) { + r = sd_netlink_message_append_u32(req, RTAX_FASTOPEN_NO_COOKIE, route->fast_open_no_cookie); + if (r < 0) + return log_link_error_errno(link, r, "Could not append RTAX_FASTOPEN_NO_COOKIE attribute: %m"); + } + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not append RTA_METRICS attribute: %m"); + + r = append_nexthops(route, req); + if (r < 0) + return log_link_error_errno(link, r, "Could not append RTA_MULTIPATH attribute: %m"); + + if (ordered_set_isempty(route->multipath_routes)) { + Route *nr; + + r = route_add_and_setup_timer(link, route, NULL, &nr); + if (r < 0) + return r; + + if (ret) + *ret = nr; + } else { + MultipathRoute *m; + + assert(!ret); + + ORDERED_SET_FOREACH(m, route->multipath_routes) { + r = route_add_and_setup_timer(link, route, m, NULL); + if (r < 0) + return r; + } + } + + r = netlink_call_async(link->manager->rtnl, NULL, req, callback, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + return 0; +} + +static int route_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + assert(link->route_messages > 0); + + link->route_messages--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EEXIST) { + log_link_message_warning_errno(link, m, r, "Could not set route"); + link_enter_failed(link); + return 1; + } + + if (link->route_messages == 0) { + log_link_debug(link, "Routes set"); + link->static_routes_configured = true; + link_set_nexthop(link); + } + + return 1; +} + +int link_set_routes(Link *link) { + enum { + PHASE_NON_GATEWAY, /* First phase: Routes without a gateway */ + PHASE_GATEWAY, /* Second phase: Routes with a gateway */ + _PHASE_MAX + } phase; + Route *rt; + int r; + + assert(link); + assert(link->network); + assert(link->state != _LINK_STATE_INVALID); + + link->static_routes_configured = false; + + if (!link->addresses_ready) + return 0; + + if (!link_has_carrier(link) && !link->network->configure_without_carrier) + /* During configuring addresses, the link lost its carrier. As networkd is dropping + * the addresses now, let's not configure the routes either. */ + return 0; + + r = link_set_routing_policy_rules(link); + if (r < 0) + return r; + + /* First add the routes that enable us to talk to gateways, then add in the others that need a gateway. */ + for (phase = 0; phase < _PHASE_MAX; phase++) + HASHMAP_FOREACH(rt, link->network->routes_by_section) { + if (rt->gateway_from_dhcp_or_ra) + continue; + + if ((in_addr_is_null(rt->gw_family, &rt->gw) && ordered_set_isempty(rt->multipath_routes)) != (phase == PHASE_NON_GATEWAY)) + continue; + + r = route_configure(rt, link, route_handler, NULL); + if (r < 0) + return log_link_warning_errno(link, r, "Could not set routes: %m"); + + link->route_messages++; + } + + if (link->route_messages == 0) { + link->static_routes_configured = true; + link_set_nexthop(link); + } else { + log_link_debug(link, "Setting routes"); + link_set_state(link, LINK_STATE_CONFIGURING); + } + + return 0; +} + +static int process_route_one(Manager *manager, Link *link, uint16_t type, const Route *tmp, const MultipathRoute *m) { + _cleanup_(route_freep) Route *nr = NULL; + Route *route = NULL; + int r; + + assert(manager); + assert(tmp); + assert(IN_SET(type, RTM_NEWROUTE, RTM_DELROUTE)); + + if (m) { + if (link) + return log_link_warning_errno(link, SYNTHETIC_ERRNO(EINVAL), + "rtnl: received route contains both RTA_OIF and RTA_MULTIPATH, ignoring."); + + if (m->ifindex <= 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "rtnl: received multipath route with invalid ifindex, ignoring."); + + r = link_get(manager, m->ifindex, &link); + if (r < 0) { + log_warning_errno(r, "rtnl: received multipath route for link (%d) we do not know, ignoring: %m", m->ifindex); + return 0; + } + + r = route_new(&nr); + if (r < 0) + return log_oom(); + + route_copy(nr, tmp, m); + + tmp = nr; + } + + (void) route_get(manager, link, tmp, &route); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *buf_dst = NULL, *buf_dst_prefixlen = NULL, + *buf_src = NULL, *buf_gw = NULL, *buf_prefsrc = NULL; + char buf_scope[ROUTE_SCOPE_STR_MAX], buf_table[ROUTE_TABLE_STR_MAX], + buf_protocol[ROUTE_PROTOCOL_STR_MAX]; + + if (!in_addr_is_null(tmp->family, &tmp->dst)) { + (void) in_addr_to_string(tmp->family, &tmp->dst, &buf_dst); + (void) asprintf(&buf_dst_prefixlen, "/%u", tmp->dst_prefixlen); + } + if (!in_addr_is_null(tmp->family, &tmp->src)) + (void) in_addr_to_string(tmp->family, &tmp->src, &buf_src); + if (!in_addr_is_null(tmp->gw_family, &tmp->gw)) + (void) in_addr_to_string(tmp->gw_family, &tmp->gw, &buf_gw); + if (!in_addr_is_null(tmp->family, &tmp->prefsrc)) + (void) in_addr_to_string(tmp->family, &tmp->prefsrc, &buf_prefsrc); + + log_link_debug(link, + "%s route: dst: %s%s, src: %s, gw: %s, prefsrc: %s, scope: %s, table: %s, proto: %s, type: %s", + (!route && !manager->manage_foreign_routes) ? "Ignoring received foreign" : + type == RTM_DELROUTE ? "Forgetting" : + route ? "Received remembered" : "Remembering", + strna(buf_dst), strempty(buf_dst_prefixlen), + strna(buf_src), strna(buf_gw), strna(buf_prefsrc), + format_route_scope(tmp->scope, buf_scope, sizeof buf_scope), + format_route_table(tmp->table, buf_table, sizeof buf_table), + format_route_protocol(tmp->protocol, buf_protocol, sizeof buf_protocol), + strna(route_type_to_string(tmp->type))); + } + + switch (type) { + case RTM_NEWROUTE: + if (!route && manager->manage_foreign_routes) { + /* A route appeared that we did not request */ + r = route_add_foreign(manager, link, tmp, NULL); + if (r < 0) { + log_link_warning_errno(link, r, "Failed to remember foreign route, ignoring: %m"); + return 0; + } + } + + break; + + case RTM_DELROUTE: + route_free(route); + break; + + default: + assert_not_reached("Received route message with invalid RTNL message type"); + } + + return 1; +} + +int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) { + _cleanup_ordered_set_free_free_ OrderedSet *multipath_routes = NULL; + _cleanup_(route_freep) Route *tmp = NULL; + _cleanup_free_ void *rta_multipath = NULL; + Link *link = NULL; + uint32_t ifindex; + uint16_t type; + unsigned char table; + RouteVia via; + size_t rta_len; + int r; + + assert(rtnl); + assert(message); + assert(m); + + if (sd_netlink_message_is_error(message)) { + r = sd_netlink_message_get_errno(message); + if (r < 0) + log_message_warning_errno(message, r, "rtnl: failed to receive route message, ignoring"); + + return 0; + } + + r = sd_netlink_message_get_type(message, &type); + if (r < 0) { + log_warning_errno(r, "rtnl: could not get message type, ignoring: %m"); + return 0; + } else if (!IN_SET(type, RTM_NEWROUTE, RTM_DELROUTE)) { + log_warning("rtnl: received unexpected message type %u when processing route, ignoring.", type); + return 0; + } + + r = sd_netlink_message_read_u32(message, RTA_OIF, &ifindex); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: could not get ifindex from route message, ignoring: %m"); + return 0; + } else if (r >= 0) { + if (ifindex <= 0) { + log_warning("rtnl: received route message with invalid ifindex %d, ignoring.", ifindex); + return 0; + } + + r = link_get(m, ifindex, &link); + if (r < 0 || !link) { + /* when enumerating we might be out of sync, but we will + * get the route again, so just ignore it */ + if (!m->enumerating) + log_warning("rtnl: received route message for link (%d) we do not know about, ignoring", ifindex); + return 0; + } + } + + r = route_new(&tmp); + if (r < 0) + return log_oom(); + + r = sd_rtnl_message_route_get_family(message, &tmp->family); + if (r < 0) { + log_link_warning(link, "rtnl: received route message without family, ignoring"); + return 0; + } else if (!IN_SET(tmp->family, AF_INET, AF_INET6)) { + log_link_debug(link, "rtnl: received route message with invalid family '%i', ignoring", tmp->family); + return 0; + } + + r = sd_rtnl_message_route_get_protocol(message, &tmp->protocol); + if (r < 0) { + log_warning_errno(r, "rtnl: received route message without route protocol: %m"); + return 0; + } + + switch (tmp->family) { + case AF_INET: + r = sd_netlink_message_read_in_addr(message, RTA_DST, &tmp->dst.in); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: received route message without valid destination, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_read_in_addr(message, RTA_GATEWAY, &tmp->gw.in); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: received route message without valid gateway, ignoring: %m"); + return 0; + } else if (r >= 0) + tmp->gw_family = AF_INET; + + r = sd_netlink_message_read(message, RTA_VIA, sizeof(via), &via); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: received route message without valid gateway, ignoring: %m"); + return 0; + } else if (r >= 0) { + tmp->gw_family = via.family; + tmp->gw = via.address; + } + + r = sd_netlink_message_read_in_addr(message, RTA_SRC, &tmp->src.in); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: received route message without valid source, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_read_in_addr(message, RTA_PREFSRC, &tmp->prefsrc.in); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: received route message without valid preferred source, ignoring: %m"); + return 0; + } + + break; + + case AF_INET6: + r = sd_netlink_message_read_in6_addr(message, RTA_DST, &tmp->dst.in6); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: received route message without valid destination, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_read_in6_addr(message, RTA_GATEWAY, &tmp->gw.in6); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: received route message without valid gateway, ignoring: %m"); + return 0; + } else if (r >= 0) + tmp->gw_family = AF_INET6; + + r = sd_netlink_message_read_in6_addr(message, RTA_SRC, &tmp->src.in6); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: received route message without valid source, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_read_in6_addr(message, RTA_PREFSRC, &tmp->prefsrc.in6); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: received route message without valid preferred source, ignoring: %m"); + return 0; + } + + break; + + default: + assert_not_reached("Received route message with unsupported address family"); + return 0; + } + + r = sd_rtnl_message_route_get_dst_prefixlen(message, &tmp->dst_prefixlen); + if (r < 0) { + log_link_warning_errno(link, r, "rtnl: received route message with invalid destination prefixlen, ignoring: %m"); + return 0; + } + + r = sd_rtnl_message_route_get_src_prefixlen(message, &tmp->src_prefixlen); + if (r < 0) { + log_link_warning_errno(link, r, "rtnl: received route message with invalid source prefixlen, ignoring: %m"); + return 0; + } + + r = sd_rtnl_message_route_get_scope(message, &tmp->scope); + if (r < 0) { + log_link_warning_errno(link, r, "rtnl: received route message with invalid scope, ignoring: %m"); + return 0; + } + + r = sd_rtnl_message_route_get_tos(message, &tmp->tos); + if (r < 0) { + log_link_warning_errno(link, r, "rtnl: received route message with invalid tos, ignoring: %m"); + return 0; + } + + r = sd_rtnl_message_route_get_type(message, &tmp->type); + if (r < 0) { + log_link_warning_errno(link, r, "rtnl: received route message with invalid type, ignoring: %m"); + return 0; + } + + r = sd_rtnl_message_route_get_table(message, &table); + if (r < 0) { + log_link_warning_errno(link, r, "rtnl: received route message with invalid table, ignoring: %m"); + return 0; + } + tmp->table = table; + + r = sd_netlink_message_read_u32(message, RTA_PRIORITY, &tmp->priority); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: received route message with invalid priority, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_enter_container(message, RTA_METRICS); + if (r < 0 && r != -ENODATA) { + log_link_error_errno(link, r, "rtnl: Could not enter RTA_METRICS container: %m"); + return 0; + } + if (r >= 0) { + r = sd_netlink_message_read_u32(message, RTAX_INITCWND, &tmp->initcwnd); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: received route message with invalid initcwnd, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_read_u32(message, RTAX_INITRWND, &tmp->initrwnd); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: received route message with invalid initrwnd, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_exit_container(message); + if (r < 0) { + log_link_error_errno(link, r, "rtnl: Could not exit from RTA_METRICS container: %m"); + return 0; + } + } + + r = sd_netlink_message_read_data(message, RTA_MULTIPATH, &rta_len, &rta_multipath); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: failed to read RTA_MULTIPATH attribute, ignoring: %m"); + return 0; + } else if (r >= 0) { + r = rtattr_read_nexthop(rta_multipath, rta_len, tmp->family, &multipath_routes); + if (r < 0) { + log_link_warning_errno(link, r, "rtnl: failed to parse RTA_MULTIPATH attribute, ignoring: %m"); + return 0; + } + } + + if (ordered_set_isempty(multipath_routes)) + (void) process_route_one(m, link, type, tmp, NULL); + else { + MultipathRoute *mr; + + ORDERED_SET_FOREACH(mr, multipath_routes) { + r = process_route_one(m, link, type, tmp, mr); + if (r < 0) + break; + } + } + + return 1; +} + +int link_serialize_routes(const Link *link, FILE *f) { + bool space = false; + Route *route; + + assert(link); + assert(link->network); + assert(f); + + fputs("ROUTES=", f); + SET_FOREACH(route, link->routes) { + _cleanup_free_ char *route_str = NULL; + + if (in_addr_to_string(route->family, &route->dst, &route_str) < 0) + continue; + + fprintf(f, "%s%s/%hhu/%hhu/%"PRIu32"/%"PRIu32"/"USEC_FMT, + space ? " " : "", route_str, + route->dst_prefixlen, route->tos, route->priority, route->table, route->lifetime); + space = true; + } + fputc('\n', f); + + return 0; +} + +int link_deserialize_routes(Link *link, const char *routes) { + int r; + + assert(link); + + for (const char *p = routes;; ) { + _cleanup_(route_freep) Route *tmp = NULL; + _cleanup_free_ char *route_str = NULL; + char *prefixlen_str; + + r = extract_first_word(&p, &route_str, NULL, 0); + if (r < 0) + return log_link_debug_errno(link, r, "Failed to parse ROUTES=: %m"); + if (r == 0) + return 0; + + prefixlen_str = strchr(route_str, '/'); + if (!prefixlen_str) { + log_link_debug(link, "Failed to parse route, ignoring: %s", route_str); + continue; + } + *prefixlen_str++ = '\0'; + + r = route_new(&tmp); + if (r < 0) + return log_oom(); + + r = sscanf(prefixlen_str, + "%hhu/%hhu/%"SCNu32"/%"PRIu32"/"USEC_FMT, + &tmp->dst_prefixlen, + &tmp->tos, + &tmp->priority, + &tmp->table, + &tmp->lifetime); + if (r != 5) { + log_link_debug(link, + "Failed to parse destination prefix length, tos, priority, table or expiration: %s", + prefixlen_str); + continue; + } + + r = in_addr_from_string_auto(route_str, &tmp->family, &tmp->dst); + if (r < 0) { + log_link_debug_errno(link, r, "Failed to parse route destination %s: %m", route_str); + continue; + } + + r = route_add_and_setup_timer(link, tmp, NULL, NULL); + if (r < 0) + return log_link_debug_errno(link, r, "Failed to add route: %m"); + } +} + +int network_add_ipv4ll_route(Network *network) { + _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + unsigned section_line; + int r; + + assert(network); + + if (!network->ipv4ll_route) + return 0; + + section_line = hashmap_find_free_section_line(network->routes_by_section); + + /* IPv4LLRoute= is in [Network] section. */ + r = route_new_static(network, network->filename, section_line, &n); + if (r < 0) + return r; + + r = in_addr_from_string(AF_INET, "169.254.0.0", &n->dst); + if (r < 0) + return r; + + n->family = AF_INET; + n->dst_prefixlen = 16; + n->scope = RT_SCOPE_LINK; + n->scope_set = true; + n->table_set = true; + n->priority = IPV4LL_ROUTE_METRIC; + n->protocol = RTPROT_STATIC; + + TAKE_PTR(n); + return 0; +} + +int network_add_default_route_on_device(Network *network) { + _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + unsigned section_line; + int r; + + assert(network); + + if (!network->default_route_on_device) + return 0; + + section_line = hashmap_find_free_section_line(network->routes_by_section); + + /* DefaultRouteOnDevice= is in [Network] section. */ + r = route_new_static(network, network->filename, section_line, &n); + if (r < 0) + return r; + + n->family = AF_INET; + n->scope = RT_SCOPE_LINK; + n->scope_set = true; + n->protocol = RTPROT_STATIC; + + TAKE_PTR(n); + return 0; +} + +int config_parse_gateway( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + if (streq(section, "Network")) { + /* we are not in an Route section, so use line number instead */ + r = route_new_static(network, filename, line, &n); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + } else { + r = route_new_static(network, filename, section_line, &n); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + if (isempty(rvalue)) { + n->gateway_from_dhcp_or_ra = false; + n->gw_family = AF_UNSPEC; + n->gw = IN_ADDR_NULL; + TAKE_PTR(n); + return 0; + } + + if (streq(rvalue, "_dhcp")) { + n->gateway_from_dhcp_or_ra = true; + TAKE_PTR(n); + return 0; + } + + if (streq(rvalue, "_dhcp4")) { + n->gw_family = AF_INET; + n->gateway_from_dhcp_or_ra = true; + TAKE_PTR(n); + return 0; + } + + if (streq(rvalue, "_ipv6ra")) { + n->gw_family = AF_INET6; + n->gateway_from_dhcp_or_ra = true; + TAKE_PTR(n); + return 0; + } + } + + r = in_addr_from_string_auto(rvalue, &n->gw_family, &n->gw); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue); + return 0; + } + + n->gateway_from_dhcp_or_ra = false; + TAKE_PTR(n); + return 0; +} + +int config_parse_preferred_src( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = route_new_static(network, filename, section_line, &n); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + if (n->family == AF_UNSPEC) + r = in_addr_from_string_auto(rvalue, &n->family, &n->prefsrc); + else + r = in_addr_from_string(n->family, rvalue, &n->prefsrc); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, EINVAL, + "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue); + return 0; + } + + TAKE_PTR(n); + return 0; +} + +int config_parse_destination( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + union in_addr_union *buffer; + unsigned char *prefixlen; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = route_new_static(network, filename, section_line, &n); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + if (streq(lvalue, "Destination")) { + buffer = &n->dst; + prefixlen = &n->dst_prefixlen; + } else if (streq(lvalue, "Source")) { + buffer = &n->src; + prefixlen = &n->src_prefixlen; + } else + assert_not_reached(lvalue); + + if (n->family == AF_UNSPEC) + r = in_addr_prefix_from_string_auto(rvalue, &n->family, buffer, prefixlen); + else + r = in_addr_prefix_from_string(rvalue, n->family, buffer, prefixlen); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, EINVAL, + "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue); + return 0; + } + + TAKE_PTR(n); + return 0; +} + +int config_parse_route_priority( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = route_new_static(network, filename, section_line, &n); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + r = safe_atou32(rvalue, &n->priority); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Could not parse route priority \"%s\", ignoring assignment: %m", rvalue); + return 0; + } + + n->priority_set = true; + TAKE_PTR(n); + return 0; +} + +int config_parse_route_scope( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = route_new_static(network, filename, section_line, &n); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + r = route_scope_from_string(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown route scope: %s", rvalue); + return 0; + } + + n->scope = r; + n->scope_set = true; + TAKE_PTR(n); + return 0; +} + +int config_parse_route_table( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = route_new_static(network, filename, section_line, &n); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + r = route_table_from_string(rvalue); + if (r >= 0) + n->table = r; + else { + r = safe_atou32(rvalue, &n->table); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Could not parse route table number \"%s\", ignoring assignment: %m", rvalue); + return 0; + } + } + + n->table_set = true; + TAKE_PTR(n); + return 0; +} + +int config_parse_route_boolean( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = route_new_static(network, filename, section_line, &n); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Could not parse %s=\"%s\", ignoring assignment: %m", lvalue, rvalue); + return 0; + } + + if (STR_IN_SET(lvalue, "GatewayOnLink", "GatewayOnlink")) + n->gateway_onlink = r; + else if (streq(lvalue, "QuickAck")) + n->quickack = r; + else if (streq(lvalue, "FastOpenNoCookie")) + n->fast_open_no_cookie = r; + else if (streq(lvalue, "TTLPropagate")) + n->ttl_propagate = r; + else + assert_not_reached("Invalid lvalue"); + + TAKE_PTR(n); + return 0; +} + +int config_parse_ipv6_route_preference( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + int r; + + r = route_new_static(network, filename, section_line, &n); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + if (streq(rvalue, "low")) + n->pref = ICMPV6_ROUTER_PREF_LOW; + else if (streq(rvalue, "medium")) + n->pref = ICMPV6_ROUTER_PREF_MEDIUM; + else if (streq(rvalue, "high")) + n->pref = ICMPV6_ROUTER_PREF_HIGH; + else { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown route preference: %s", rvalue); + return 0; + } + + n->pref_set = true; + TAKE_PTR(n); + return 0; +} + +int config_parse_route_protocol( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + int r; + + r = route_new_static(network, filename, section_line, &n); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + r = route_protocol_from_string(rvalue); + if (r >= 0) + n->protocol = r; + else { + r = safe_atou8(rvalue , &n->protocol); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Could not parse route protocol \"%s\", ignoring assignment: %m", rvalue); + return 0; + } + } + + TAKE_PTR(n); + return 0; +} + +int config_parse_route_type( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + int t, r; + + r = route_new_static(network, filename, section_line, &n); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + t = route_type_from_string(rvalue); + if (t < 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Could not parse route type \"%s\", ignoring assignment: %m", rvalue); + return 0; + } + + n->type = (unsigned char) t; + + TAKE_PTR(n); + return 0; +} + +int config_parse_tcp_window( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + Network *network = userdata; + uint32_t k; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = route_new_static(network, filename, section_line, &n); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + r = safe_atou32(rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Could not parse TCP %s \"%s\", ignoring assignment: %m", lvalue, rvalue); + return 0; + } + if (k >= 1024) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Specified TCP %s \"%s\" is too large, ignoring assignment: %m", lvalue, rvalue); + return 0; + } + + if (streq(lvalue, "InitialCongestionWindow")) + n->initcwnd = k; + else if (streq(lvalue, "InitialAdvertisedReceiveWindow")) + n->initrwnd = k; + else + assert_not_reached("Invalid TCP window type."); + + TAKE_PTR(n); + return 0; +} + +int config_parse_route_mtu( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = route_new_static(network, filename, section_line, &n); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + r = config_parse_mtu(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &n->mtu, userdata); + if (r < 0) + return r; + + TAKE_PTR(n); + return 0; +} + +int config_parse_multipath_route( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + _cleanup_free_ char *word = NULL, *buf = NULL; + _cleanup_free_ MultipathRoute *m = NULL; + Network *network = userdata; + const char *p, *ip, *dev; + union in_addr_union a; + int family, r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = route_new_static(network, filename, section_line, &n); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + if (isempty(rvalue)) { + n->multipath_routes = ordered_set_free_free(n->multipath_routes); + return 0; + } + + m = new0(MultipathRoute, 1); + if (!m) + return log_oom(); + + p = rvalue; + r = extract_first_word(&p, &word, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r <= 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid multipath route option, ignoring assignment: %s", rvalue); + return 0; + } + + dev = strchr(word, '@'); + if (dev) { + buf = strndup(word, dev - word); + if (!buf) + return log_oom(); + ip = buf; + dev++; + } else + ip = word; + + r = in_addr_from_string_auto(ip, &family, &a); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid multipath route gateway '%s', ignoring assignment: %m", rvalue); + return 0; + } + m->gateway.address = a; + m->gateway.family = family; + + if (dev) { + r = resolve_interface(NULL, dev); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid interface name or index, ignoring assignment: %s", dev); + return 0; + } + m->ifindex = r; + } + + if (!isempty(p)) { + r = safe_atou32(p, &m->weight); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid multipath route weight, ignoring assignment: %s", p); + return 0; + } + if (m->weight == 0 || m->weight > 256) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid multipath route weight, ignoring assignment: %s", p); + return 0; + } + } + + r = ordered_set_ensure_allocated(&n->multipath_routes, NULL); + if (r < 0) + return log_oom(); + + r = ordered_set_put(n->multipath_routes, m); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to store multipath route, ignoring assignment: %m"); + return 0; + } + + TAKE_PTR(m); + TAKE_PTR(n); + return 0; +} + +static int route_section_verify(Route *route, Network *network) { + if (section_is_invalid(route->section)) + return -EINVAL; + + if (route->gateway_from_dhcp_or_ra) { + if (route->gw_family == AF_UNSPEC) { + /* When deprecated Gateway=_dhcp is set, then assume gateway family based on other settings. */ + switch (route->family) { + case AF_UNSPEC: + log_warning("%s: Deprecated value \"_dhcp\" is specified for Gateway= in [Route] section from line %u. " + "Please use \"_dhcp4\" or \"_ipv6ra\" instead. Assuming \"_dhcp4\".", + route->section->filename, route->section->line); + route->family = AF_INET; + break; + case AF_INET: + case AF_INET6: + log_warning("%s: Deprecated value \"_dhcp\" is specified for Gateway= in [Route] section from line %u. " + "Assuming \"%s\" based on Destination=, Source=, or PreferredSource= setting.", + route->section->filename, route->section->line, route->family == AF_INET ? "_dhcp4" : "_ipv6ra"); + break; + default: + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Invalid route family. Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + } + route->gw_family = route->family; + } + + if (route->gw_family == AF_INET && !FLAGS_SET(network->dhcp, ADDRESS_FAMILY_IPV4)) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Gateway=\"_dhcp4\" is specified but DHCPv4 client is disabled. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + + if (route->gw_family == AF_INET6 && !network->ipv6_accept_ra) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Gateway=\"_ipv6ra\" is specified but IPv6AcceptRA= is disabled. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + } + + /* When only Gateway= is specified, assume the route family based on the Gateway address. */ + if (route->family == AF_UNSPEC) + route->family = route->gw_family; + + if (route->family == AF_UNSPEC) { + assert(route->section); + + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Route section without Gateway=, Destination=, Source=, " + "or PreferredSource= field configured. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + } + + if (route->family == AF_INET6 && route->gw_family == AF_INET) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: IPv4 gateway is configured for IPv6 route. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + + if (!route->table_set && network->vrf) { + route->table = VRF(network->vrf)->table; + route->table_set = true; + } + + if (!route->table_set && IN_SET(route->type, RTN_LOCAL, RTN_BROADCAST, RTN_ANYCAST, RTN_NAT)) + route->table = RT_TABLE_LOCAL; + + if (!route->scope_set && route->family != AF_INET6) { + if (IN_SET(route->type, RTN_LOCAL, RTN_NAT)) + route->scope = RT_SCOPE_HOST; + else if (IN_SET(route->type, RTN_BROADCAST, RTN_ANYCAST, RTN_MULTICAST)) + route->scope = RT_SCOPE_LINK; + } + + if (route->scope != RT_SCOPE_UNIVERSE && route->family == AF_INET6) { + log_warning("%s: Scope= is specified for IPv6 route. It will be ignored.", route->section->filename); + route->scope = RT_SCOPE_UNIVERSE; + } + + if (route->family == AF_INET6 && route->priority == 0) + route->priority = IP6_RT_PRIO_USER; + + if (ordered_hashmap_isempty(network->addresses_by_section) && + in_addr_is_null(route->gw_family, &route->gw) == 0 && + route->gateway_onlink < 0) { + log_warning("%s: Gateway= without static address configured. " + "Enabling GatewayOnLink= option.", + network->filename); + route->gateway_onlink = true; + } + + return 0; +} + +void network_drop_invalid_routes(Network *network) { + Route *route; + + assert(network); + + HASHMAP_FOREACH(route, network->routes_by_section) + if (route_section_verify(route, network) < 0) + route_free(route); +} diff --git a/src/network/networkd-route.h b/src/network/networkd-route.h new file mode 100644 index 0000000..f593693 --- /dev/null +++ b/src/network/networkd-route.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <inttypes.h> +#include <stdbool.h> +#include <stdio.h> + +#include "sd-netlink.h" + +#include "conf-parser.h" +#include "in-addr-util.h" +#include "networkd-link.h" +#include "networkd-util.h" + +typedef struct Manager Manager; +typedef struct Network Network; + +typedef struct Route { + Network *network; + NetworkConfigSection *section; + + Link *link; + Manager *manager; + + int family; + int gw_family; + uint32_t gw_weight; + int quickack; + int fast_open_no_cookie; + int ttl_propagate; + + unsigned char dst_prefixlen; + unsigned char src_prefixlen; + unsigned char scope; + unsigned char protocol; /* RTPROT_* */ + unsigned char type; /* RTN_* */ + unsigned char tos; + uint32_t priority; /* note that ip(8) calls this 'metric' */ + uint32_t table; + uint32_t mtu; + uint32_t initcwnd; + uint32_t initrwnd; + unsigned char pref; + unsigned flags; + int gateway_onlink; + + bool scope_set:1; + bool table_set:1; + bool priority_set:1; + bool protocol_set:1; + bool pref_set:1; + bool gateway_from_dhcp_or_ra:1; + + union in_addr_union gw; + union in_addr_union dst; + union in_addr_union src; + union in_addr_union prefsrc; + OrderedSet *multipath_routes; + + usec_t lifetime; + sd_event_source *expire; +} Route; + +void route_hash_func(const Route *route, struct siphash *state); +int route_compare_func(const Route *a, const Route *b); +extern const struct hash_ops route_hash_ops; + +int route_new(Route **ret); +Route *route_free(Route *route); +DEFINE_NETWORK_SECTION_FUNCTIONS(Route, route_free); + +int route_configure(const Route *route, Link *link, link_netlink_message_handler_t callback, Route **ret); +int route_remove(const Route *route, Manager *manager, Link *link, link_netlink_message_handler_t callback); + +int link_set_routes(Link *link); +int link_drop_routes(Link *link); +int link_drop_foreign_routes(Link *link); +int link_serialize_routes(const Link *link, FILE *f); +int link_deserialize_routes(Link *link, const char *routes); + +uint32_t link_get_dhcp_route_table(const Link *link); +uint32_t link_get_ipv6_accept_ra_route_table(const Link *link); + +int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Manager *m); + +int network_add_ipv4ll_route(Network *network); +int network_add_default_route_on_device(Network *network); +void network_drop_invalid_routes(Network *network); + +CONFIG_PARSER_PROTOTYPE(config_parse_gateway); +CONFIG_PARSER_PROTOTYPE(config_parse_preferred_src); +CONFIG_PARSER_PROTOTYPE(config_parse_destination); +CONFIG_PARSER_PROTOTYPE(config_parse_route_priority); +CONFIG_PARSER_PROTOTYPE(config_parse_route_scope); +CONFIG_PARSER_PROTOTYPE(config_parse_route_table); +CONFIG_PARSER_PROTOTYPE(config_parse_route_boolean); +CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_route_preference); +CONFIG_PARSER_PROTOTYPE(config_parse_route_protocol); +CONFIG_PARSER_PROTOTYPE(config_parse_route_type); +CONFIG_PARSER_PROTOTYPE(config_parse_tcp_window); +CONFIG_PARSER_PROTOTYPE(config_parse_route_mtu); +CONFIG_PARSER_PROTOTYPE(config_parse_multipath_route); diff --git a/src/network/networkd-routing-policy-rule.c b/src/network/networkd-routing-policy-rule.c new file mode 100644 index 0000000..e44ecb4 --- /dev/null +++ b/src/network/networkd-routing-policy-rule.c @@ -0,0 +1,1810 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <net/if.h> +#include <linux/fib_rules.h> + +#include "af-list.h" +#include "alloc-util.h" +#include "conf-parser.h" +#include "fileio.h" +#include "format-util.h" +#include "ip-protocol-list.h" +#include "netlink-util.h" +#include "networkd-manager.h" +#include "networkd-routing-policy-rule.h" +#include "networkd-util.h" +#include "parse-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "strv.h" +#include "user-util.h" + +RoutingPolicyRule *routing_policy_rule_free(RoutingPolicyRule *rule) { + if (!rule) + return NULL; + + if (rule->network) { + assert(rule->section); + hashmap_remove(rule->network->rules_by_section, rule->section); + } + + if (rule->manager) { + if (set_get(rule->manager->rules, rule) == rule) + set_remove(rule->manager->rules, rule); + if (set_get(rule->manager->rules_foreign, rule) == rule) + set_remove(rule->manager->rules_foreign, rule); + } + + network_config_section_free(rule->section); + free(rule->iif); + free(rule->oif); + + return mfree(rule); +} + +DEFINE_NETWORK_SECTION_FUNCTIONS(RoutingPolicyRule, routing_policy_rule_free); + +static int routing_policy_rule_new(RoutingPolicyRule **ret) { + RoutingPolicyRule *rule; + + rule = new(RoutingPolicyRule, 1); + if (!rule) + return -ENOMEM; + + *rule = (RoutingPolicyRule) { + .table = RT_TABLE_MAIN, + .uid_range.start = UID_INVALID, + .uid_range.end = UID_INVALID, + .suppress_prefixlen = -1, + }; + + *ret = rule; + return 0; +} + +static int routing_policy_rule_new_static(Network *network, const char *filename, unsigned section_line, RoutingPolicyRule **ret) { + _cleanup_(routing_policy_rule_freep) RoutingPolicyRule *rule = NULL; + _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; + int r; + + assert(network); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = network_config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + rule = hashmap_get(network->rules_by_section, n); + if (rule) { + *ret = TAKE_PTR(rule); + return 0; + } + + r = routing_policy_rule_new(&rule); + if (r < 0) + return r; + + rule->network = network; + rule->section = TAKE_PTR(n); + + r = hashmap_ensure_allocated(&network->rules_by_section, &network_config_hash_ops); + if (r < 0) + return r; + + r = hashmap_put(network->rules_by_section, rule->section, rule); + if (r < 0) + return r; + + *ret = TAKE_PTR(rule); + return 0; +} + +static int routing_policy_rule_copy(RoutingPolicyRule *dest, RoutingPolicyRule *src) { + _cleanup_free_ char *iif = NULL, *oif = NULL; + + assert(dest); + assert(src); + + if (src->iif) { + iif = strdup(src->iif); + if (!iif) + return -ENOMEM; + } + + if (src->oif) { + oif = strdup(src->oif); + if (!oif) + return -ENOMEM; + } + + dest->family = src->family; + dest->from = src->from; + dest->from_prefixlen = src->from_prefixlen; + dest->to = src->to; + dest->to_prefixlen = src->to_prefixlen; + dest->invert_rule = src->invert_rule; + dest->tos = src->tos; + dest->fwmark = src->fwmark; + dest->fwmask = src->fwmask; + dest->priority = src->priority; + dest->table = src->table; + dest->iif = TAKE_PTR(iif); + dest->oif = TAKE_PTR(oif); + dest->protocol = src->protocol; + dest->sport = src->sport; + dest->dport = src->dport; + dest->uid_range = src->uid_range; + dest->suppress_prefixlen = src->suppress_prefixlen; + + return 0; +} + +static void routing_policy_rule_hash_func(const RoutingPolicyRule *rule, struct siphash *state) { + assert(rule); + + siphash24_compress(&rule->family, sizeof(rule->family), state); + + switch (rule->family) { + case AF_INET: + case AF_INET6: + siphash24_compress(&rule->from, FAMILY_ADDRESS_SIZE(rule->family), state); + siphash24_compress(&rule->from_prefixlen, sizeof(rule->from_prefixlen), state); + + siphash24_compress(&rule->to, FAMILY_ADDRESS_SIZE(rule->family), state); + siphash24_compress(&rule->to_prefixlen, sizeof(rule->to_prefixlen), state); + + siphash24_compress_boolean(rule->invert_rule, state); + + siphash24_compress(&rule->tos, sizeof(rule->tos), state); + siphash24_compress(&rule->fwmark, sizeof(rule->fwmark), state); + siphash24_compress(&rule->fwmask, sizeof(rule->fwmask), state); + siphash24_compress(&rule->priority, sizeof(rule->priority), state); + siphash24_compress(&rule->table, sizeof(rule->table), state); + siphash24_compress(&rule->suppress_prefixlen, sizeof(rule->suppress_prefixlen), state); + + siphash24_compress(&rule->protocol, sizeof(rule->protocol), state); + siphash24_compress(&rule->sport, sizeof(rule->sport), state); + siphash24_compress(&rule->dport, sizeof(rule->dport), state); + siphash24_compress(&rule->uid_range, sizeof(rule->uid_range), state); + + siphash24_compress_string(rule->iif, state); + siphash24_compress_string(rule->oif, state); + + break; + default: + /* treat any other address family as AF_UNSPEC */ + break; + } +} + +static int routing_policy_rule_compare_func(const RoutingPolicyRule *a, const RoutingPolicyRule *b) { + int r; + + r = CMP(a->family, b->family); + if (r != 0) + return r; + + switch (a->family) { + case AF_INET: + case AF_INET6: + r = CMP(a->from_prefixlen, b->from_prefixlen); + if (r != 0) + return r; + + r = memcmp(&a->from, &b->from, FAMILY_ADDRESS_SIZE(a->family)); + if (r != 0) + return r; + + r = CMP(a->to_prefixlen, b->to_prefixlen); + if (r != 0) + return r; + + r = memcmp(&a->to, &b->to, FAMILY_ADDRESS_SIZE(a->family)); + if (r != 0) + return r; + + r = CMP(a->invert_rule, b->invert_rule); + if (r != 0) + return r; + + r = CMP(a->tos, b->tos); + if (r != 0) + return r; + + r = CMP(a->fwmark, b->fwmark); + if (r != 0) + return r; + + r = CMP(a->fwmask, b->fwmask); + if (r != 0) + return r; + + r = CMP(a->priority, b->priority); + if (r != 0) + return r; + + r = CMP(a->table, b->table); + if (r != 0) + return r; + + r = CMP(a->suppress_prefixlen, b->suppress_prefixlen); + if (r != 0) + return r; + + r = CMP(a->protocol, b->protocol); + if (r != 0) + return r; + + r = memcmp(&a->sport, &b->sport, sizeof(a->sport)); + if (r != 0) + return r; + + r = memcmp(&a->dport, &b->dport, sizeof(a->dport)); + if (r != 0) + return r; + + r = memcmp(&a->uid_range, &b->uid_range, sizeof(a->uid_range)); + if (r != 0) + return r; + + r = strcmp_ptr(a->iif, b->iif); + if (r != 0) + return r; + + r = strcmp_ptr(a->oif, b->oif); + if (r != 0) + return r; + + return 0; + default: + /* treat any other address family as AF_UNSPEC */ + return 0; + } +} + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + routing_policy_rule_hash_ops, + RoutingPolicyRule, + routing_policy_rule_hash_func, + routing_policy_rule_compare_func, + routing_policy_rule_free); + +static int routing_policy_rule_get(Manager *m, RoutingPolicyRule *rule, RoutingPolicyRule **ret) { + + RoutingPolicyRule *existing; + + assert(m); + + existing = set_get(m->rules, rule); + if (existing) { + if (ret) + *ret = existing; + return 1; + } + + existing = set_get(m->rules_foreign, rule); + if (existing) { + if (ret) + *ret = existing; + return 0; + } + + return -ENOENT; +} + +static int routing_policy_rule_add_internal(Manager *m, Set **rules, RoutingPolicyRule *in, int family, RoutingPolicyRule **ret) { + _cleanup_(routing_policy_rule_freep) RoutingPolicyRule *rule = NULL; + int r; + + assert(m); + assert(rules); + assert(in); + assert(IN_SET(family, AF_INET, AF_INET6)); + assert(in->family == AF_UNSPEC || in->family == family); + + r = routing_policy_rule_new(&rule); + if (r < 0) + return r; + + r = routing_policy_rule_copy(rule, in); + if (r < 0) + return r; + + rule->family = family; + + r = set_ensure_put(rules, &routing_policy_rule_hash_ops, rule); + if (r < 0) + return r; + if (r == 0) + return -EEXIST; + + rule->manager = m; + + if (ret) + *ret = rule; + + TAKE_PTR(rule); + return 0; +} + +static int routing_policy_rule_add(Manager *m, RoutingPolicyRule *rule, int family, RoutingPolicyRule **ret) { + return routing_policy_rule_add_internal(m, &m->rules, rule, family, ret); +} + +static int routing_policy_rule_add_foreign(Manager *m, RoutingPolicyRule *rule, RoutingPolicyRule **ret) { + return routing_policy_rule_add_internal(m, &m->rules_foreign, rule, rule->family, ret); +} + +static int routing_policy_rule_set_netlink_message(RoutingPolicyRule *rule, sd_netlink_message *m, Link *link) { + int r; + + assert(rule); + assert(m); + assert(link); + + if (in_addr_is_null(rule->family, &rule->from) == 0) { + r = netlink_message_append_in_addr_union(m, FRA_SRC, rule->family, &rule->from); + if (r < 0) + return log_link_error_errno(link, r, "Could not append FRA_SRC attribute: %m"); + + r = sd_rtnl_message_routing_policy_rule_set_rtm_src_prefixlen(m, rule->from_prefixlen); + if (r < 0) + return log_link_error_errno(link, r, "Could not set source prefix length: %m"); + } + + if (in_addr_is_null(rule->family, &rule->to) == 0) { + r = netlink_message_append_in_addr_union(m, FRA_DST, rule->family, &rule->to); + if (r < 0) + return log_link_error_errno(link, r, "Could not append FRA_DST attribute: %m"); + + r = sd_rtnl_message_routing_policy_rule_set_rtm_dst_prefixlen(m, rule->to_prefixlen); + if (r < 0) + return log_link_error_errno(link, r, "Could not set destination prefix length: %m"); + } + + r = sd_netlink_message_append_u32(m, FRA_PRIORITY, rule->priority); + if (r < 0) + return log_link_error_errno(link, r, "Could not append FRA_PRIORITY attribute: %m"); + + if (rule->tos > 0) { + r = sd_rtnl_message_routing_policy_rule_set_tos(m, rule->tos); + if (r < 0) + return log_link_error_errno(link, r, "Could not set IP rule TOS: %m"); + } + + if (rule->table < 256) { + r = sd_rtnl_message_routing_policy_rule_set_table(m, rule->table); + if (r < 0) + return log_link_error_errno(link, r, "Could not set IP rule table: %m"); + } else { + r = sd_rtnl_message_routing_policy_rule_set_table(m, RT_TABLE_UNSPEC); + if (r < 0) + return log_link_error_errno(link, r, "Could not set IP rule table: %m"); + + r = sd_netlink_message_append_u32(m, FRA_TABLE, rule->table); + if (r < 0) + return log_link_error_errno(link, r, "Could not append FRA_TABLE attribute: %m"); + } + + if (rule->fwmark > 0) { + r = sd_netlink_message_append_u32(m, FRA_FWMARK, rule->fwmark); + if (r < 0) + return log_link_error_errno(link, r, "Could not append FRA_FWMARK attribute: %m"); + + r = sd_netlink_message_append_u32(m, FRA_FWMASK, rule->fwmask); + if (r < 0) + return log_link_error_errno(link, r, "Could not append FRA_FWMASK attribute: %m"); + } + + if (rule->iif) { + r = sd_netlink_message_append_string(m, FRA_IIFNAME, rule->iif); + if (r < 0) + return log_link_error_errno(link, r, "Could not append FRA_IIFNAME attribute: %m"); + } + + if (rule->oif) { + r = sd_netlink_message_append_string(m, FRA_OIFNAME, rule->oif); + if (r < 0) + return log_link_error_errno(link, r, "Could not append FRA_OIFNAME attribute: %m"); + } + + r = sd_netlink_message_append_u8(m, FRA_IP_PROTO, rule->protocol); + if (r < 0) + return log_link_error_errno(link, r, "Could not append FRA_IP_PROTO attribute: %m"); + + if (rule->sport.start != 0 || rule->sport.end != 0) { + r = sd_netlink_message_append_data(m, FRA_SPORT_RANGE, &rule->sport, sizeof(rule->sport)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append FRA_SPORT_RANGE attribute: %m"); + } + + if (rule->dport.start != 0 || rule->dport.end != 0) { + r = sd_netlink_message_append_data(m, FRA_DPORT_RANGE, &rule->dport, sizeof(rule->dport)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append FRA_DPORT_RANGE attribute: %m"); + } + + if (rule->uid_range.start != UID_INVALID && rule->uid_range.end != UID_INVALID) { + r = sd_netlink_message_append_data(m, FRA_UID_RANGE, &rule->uid_range, sizeof(rule->uid_range)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append FRA_UID_RANGE attribute: %m"); + } + + if (rule->invert_rule) { + r = sd_rtnl_message_routing_policy_rule_set_flags(m, FIB_RULE_INVERT); + if (r < 0) + return log_link_error_errno(link, r, "Could not append FIB_RULE_INVERT attribute: %m"); + } + + if (rule->suppress_prefixlen >= 0) { + r = sd_netlink_message_append_u32(m, FRA_SUPPRESS_PREFIXLEN, (uint32_t) rule->suppress_prefixlen); + if (r < 0) + return log_link_error_errno(link, r, "Could not append FRA_SUPPRESS_PREFIXLEN attribute: %m"); + } + + return 0; +} + +static int routing_policy_rule_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(m); + assert(link); + assert(link->ifname); + + link->routing_policy_rule_remove_messages--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0) + log_link_message_warning_errno(link, m, r, "Could not drop routing policy rule"); + + return 1; +} + +static int routing_policy_rule_remove(RoutingPolicyRule *rule, Link *link) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(rule); + assert(link); + assert(link->manager); + assert(link->manager->rtnl); + assert(link->ifindex > 0); + assert(IN_SET(rule->family, AF_INET, AF_INET6)); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *from = NULL, *to = NULL; + + (void) in_addr_to_string(rule->family, &rule->from, &from); + (void) in_addr_to_string(rule->family, &rule->to, &to); + + log_link_debug(link, + "Removing routing policy rule: priority: %"PRIu32", %s/%u -> %s/%u, iif: %s, oif: %s, table: %"PRIu32, + rule->priority, strna(from), rule->from_prefixlen, strna(to), rule->to_prefixlen, strna(rule->iif), strna(rule->oif), rule->table); + } + + r = sd_rtnl_message_new_routing_policy_rule(link->manager->rtnl, &m, RTM_DELRULE, rule->family); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate RTM_DELRULE message: %m"); + + r = routing_policy_rule_set_netlink_message(rule, m, link); + if (r < 0) + return r; + + r = netlink_call_async(link->manager->rtnl, NULL, m, + routing_policy_rule_remove_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + return 0; +} + +static int routing_policy_rule_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(rtnl); + assert(m); + assert(link); + assert(link->ifname); + assert(link->routing_policy_rule_messages > 0); + + link->routing_policy_rule_messages--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EEXIST) { + log_link_message_warning_errno(link, m, r, "Could not add routing policy rule"); + link_enter_failed(link); + return 1; + } + + if (link->routing_policy_rule_messages == 0) { + log_link_debug(link, "Routing policy rule configured"); + link->routing_policy_rules_configured = true; + link_check_ready(link); + } + + return 1; +} + +static int routing_policy_rule_configure_internal(RoutingPolicyRule *rule, int family, Link *link) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(rule); + assert(link); + assert(link->ifindex > 0); + assert(link->manager); + assert(link->manager->rtnl); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *from = NULL, *to = NULL; + + (void) in_addr_to_string(family, &rule->from, &from); + (void) in_addr_to_string(family, &rule->to, &to); + + log_link_debug(link, + "Configuring routing policy rule: priority: %"PRIu32", %s/%u -> %s/%u, iif: %s, oif: %s, table: %"PRIu32, + rule->priority, strna(from), rule->from_prefixlen, strna(to), rule->to_prefixlen, strna(rule->iif), strna(rule->oif), rule->table); + } + + r = sd_rtnl_message_new_routing_policy_rule(link->manager->rtnl, &m, RTM_NEWRULE, family); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate RTM_NEWRULE message: %m"); + + r = routing_policy_rule_set_netlink_message(rule, m, link); + if (r < 0) + return r; + + r = netlink_call_async(link->manager->rtnl, NULL, m, + routing_policy_rule_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + link->routing_policy_rule_messages++; + + r = routing_policy_rule_add(link->manager, rule, family, NULL); + if (r < 0) + return log_link_error_errno(link, r, "Could not add rule: %m"); + + return 1; +} + +static int routing_policy_rule_configure(RoutingPolicyRule *rule, Link *link) { + int r; + + if (IN_SET(rule->family, AF_INET, AF_INET6)) + return routing_policy_rule_configure_internal(rule, rule->family, link); + + if (FLAGS_SET(rule->address_family, ADDRESS_FAMILY_IPV4)) { + r = routing_policy_rule_configure_internal(rule, AF_INET, link); + if (r < 0) + return r; + } + + if (FLAGS_SET(rule->address_family, ADDRESS_FAMILY_IPV6)) { + r = routing_policy_rule_configure_internal(rule, AF_INET6, link); + if (r < 0) + return r; + } + + return 0; +} + +static bool manager_links_have_routing_policy_rule(Manager *m, RoutingPolicyRule *rule) { + Link *link; + + assert(m); + assert(rule); + + HASHMAP_FOREACH(link, m->links) { + RoutingPolicyRule *link_rule; + + if (!link->network) + continue; + + HASHMAP_FOREACH(link_rule, link->network->rules_by_section) + if (routing_policy_rule_compare_func(link_rule, rule) == 0) + return true; + } + + return false; +} + +static void routing_policy_rule_purge(Manager *m, Link *link) { + RoutingPolicyRule *rule; + int r; + + assert(m); + assert(link); + + SET_FOREACH(rule, m->rules_saved) { + RoutingPolicyRule *existing; + + existing = set_get(m->rules_foreign, rule); + if (!existing) + continue; /* Saved rule does not exist anymore. */ + + if (manager_links_have_routing_policy_rule(m, existing)) + continue; /* Existing links have the saved rule. */ + + /* Existing links do not have the saved rule. Let's drop the rule now, and re-configure it + * later when it is requested. */ + + r = routing_policy_rule_remove(existing, link); + if (r < 0) { + log_warning_errno(r, "Could not remove routing policy rules: %m"); + continue; + } + + link->routing_policy_rule_remove_messages++; + + assert_se(set_remove(m->rules_foreign, existing) == existing); + routing_policy_rule_free(existing); + } +} + +int link_set_routing_policy_rules(Link *link) { + RoutingPolicyRule *rule; + int r; + + assert(link); + assert(link->network); + + link->routing_policy_rules_configured = false; + + HASHMAP_FOREACH(rule, link->network->rules_by_section) { + RoutingPolicyRule *existing; + + r = routing_policy_rule_get(link->manager, rule, &existing); + if (r > 0) + continue; + if (r == 0) { + r = set_ensure_put(&link->manager->rules, &routing_policy_rule_hash_ops, existing); + if (r < 0) + return log_link_warning_errno(link, r, "Could not store existing routing policy rule: %m"); + + set_remove(link->manager->rules_foreign, existing); + continue; + } + + r = routing_policy_rule_configure(rule, link); + if (r < 0) + return log_link_warning_errno(link, r, "Could not set routing policy rule: %m"); + } + + routing_policy_rule_purge(link->manager, link); + if (link->routing_policy_rule_messages == 0) + link->routing_policy_rules_configured = true; + else { + log_link_debug(link, "Setting routing policy rules"); + link_set_state(link, LINK_STATE_CONFIGURING); + } + + return 0; +} + +int manager_rtnl_process_rule(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) { + _cleanup_(routing_policy_rule_freep) RoutingPolicyRule *tmp = NULL; + _cleanup_free_ char *from = NULL, *to = NULL; + RoutingPolicyRule *rule = NULL; + const char *iif = NULL, *oif = NULL; + uint32_t suppress_prefixlen; + unsigned flags; + uint16_t type; + int r; + + assert(rtnl); + assert(message); + + if (sd_netlink_message_is_error(message)) { + r = sd_netlink_message_get_errno(message); + if (r < 0) + log_message_warning_errno(message, r, "rtnl: failed to receive rule message, ignoring"); + + return 0; + } + + r = sd_netlink_message_get_type(message, &type); + if (r < 0) { + log_warning_errno(r, "rtnl: could not get message type, ignoring: %m"); + return 0; + } else if (!IN_SET(type, RTM_NEWRULE, RTM_DELRULE)) { + log_warning("rtnl: received unexpected message type %u when processing rule, ignoring.", type); + return 0; + } + + r = routing_policy_rule_new(&tmp); + if (r < 0) { + log_oom(); + return 0; + } + + r = sd_rtnl_message_get_family(message, &tmp->family); + if (r < 0) { + log_warning_errno(r, "rtnl: could not get rule family, ignoring: %m"); + return 0; + } else if (!IN_SET(tmp->family, AF_INET, AF_INET6)) { + log_debug("rtnl: received rule message with invalid family %d, ignoring.", tmp->family); + return 0; + } + + switch (tmp->family) { + case AF_INET: + r = sd_netlink_message_read_in_addr(message, FRA_SRC, &tmp->from.in); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: could not get FRA_SRC attribute, ignoring: %m"); + return 0; + } else if (r >= 0) { + r = sd_rtnl_message_routing_policy_rule_get_rtm_src_prefixlen(message, &tmp->from_prefixlen); + if (r < 0) { + log_warning_errno(r, "rtnl: received rule message without valid source prefix length, ignoring: %m"); + return 0; + } + } + + r = sd_netlink_message_read_in_addr(message, FRA_DST, &tmp->to.in); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: could not get FRA_DST attribute, ignoring: %m"); + return 0; + } else if (r >= 0) { + r = sd_rtnl_message_routing_policy_rule_get_rtm_dst_prefixlen(message, &tmp->to_prefixlen); + if (r < 0) { + log_warning_errno(r, "rtnl: received rule message without valid destination prefix length, ignoring: %m"); + return 0; + } + } + + break; + + case AF_INET6: + r = sd_netlink_message_read_in6_addr(message, FRA_SRC, &tmp->from.in6); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: could not get FRA_SRC attribute, ignoring: %m"); + return 0; + } else if (r >= 0) { + r = sd_rtnl_message_routing_policy_rule_get_rtm_src_prefixlen(message, &tmp->from_prefixlen); + if (r < 0) { + log_warning_errno(r, "rtnl: received rule message without valid source prefix length, ignoring: %m"); + return 0; + } + } + + r = sd_netlink_message_read_in6_addr(message, FRA_DST, &tmp->to.in6); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: could not get FRA_DST attribute, ignoring: %m"); + return 0; + } else if (r >= 0) { + r = sd_rtnl_message_routing_policy_rule_get_rtm_dst_prefixlen(message, &tmp->to_prefixlen); + if (r < 0) { + log_warning_errno(r, "rtnl: received rule message without valid destination prefix length, ignoring: %m"); + return 0; + } + } + + break; + + default: + assert_not_reached("Received rule message with unsupported address family"); + } + + r = sd_rtnl_message_routing_policy_rule_get_flags(message, &flags); + if (r < 0) { + log_warning_errno(r, "rtnl: received rule message without valid flag, ignoring: %m"); + return 0; + } + tmp->invert_rule = flags & FIB_RULE_INVERT; + + r = sd_netlink_message_read_u32(message, FRA_FWMARK, &tmp->fwmark); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: could not get FRA_FWMARK attribute, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_read_u32(message, FRA_FWMASK, &tmp->fwmask); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: could not get FRA_FWMASK attribute, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_read_u32(message, FRA_PRIORITY, &tmp->priority); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: could not get FRA_PRIORITY attribute, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_read_u32(message, FRA_TABLE, &tmp->table); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: could not get FRA_TABLE attribute, ignoring: %m"); + return 0; + } + + r = sd_rtnl_message_routing_policy_rule_get_tos(message, &tmp->tos); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: could not get ip rule TOS, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_read_string(message, FRA_IIFNAME, &iif); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: could not get FRA_IIFNAME attribute, ignoring: %m"); + return 0; + } + r = free_and_strdup(&tmp->iif, iif); + if (r < 0) + return log_oom(); + + r = sd_netlink_message_read_string(message, FRA_OIFNAME, &oif); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: could not get FRA_OIFNAME attribute, ignoring: %m"); + return 0; + } + r = free_and_strdup(&tmp->oif, oif); + if (r < 0) + return log_oom(); + + r = sd_netlink_message_read_u8(message, FRA_IP_PROTO, &tmp->protocol); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: could not get FRA_IP_PROTO attribute, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_read(message, FRA_SPORT_RANGE, sizeof(tmp->sport), &tmp->sport); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: could not get FRA_SPORT_RANGE attribute, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_read(message, FRA_DPORT_RANGE, sizeof(tmp->dport), &tmp->dport); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: could not get FRA_DPORT_RANGE attribute, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_read(message, FRA_UID_RANGE, sizeof(tmp->uid_range), &tmp->uid_range); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: could not get FRA_UID_RANGE attribute, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_read_u32(message, FRA_SUPPRESS_PREFIXLEN, &suppress_prefixlen); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: could not get FRA_SUPPRESS_PREFIXLEN attribute, ignoring: %m"); + return 0; + } + if (r >= 0) + tmp->suppress_prefixlen = (int) suppress_prefixlen; + + (void) routing_policy_rule_get(m, tmp, &rule); + + if (DEBUG_LOGGING) { + (void) in_addr_to_string(tmp->family, &tmp->from, &from); + (void) in_addr_to_string(tmp->family, &tmp->to, &to); + } + + switch (type) { + case RTM_NEWRULE: + if (rule) + log_debug("Received remembered routing policy rule: priority: %"PRIu32", %s/%u -> %s/%u, iif: %s, oif: %s, table: %"PRIu32, + tmp->priority, strna(from), tmp->from_prefixlen, strna(to), tmp->to_prefixlen, strna(tmp->iif), strna(tmp->oif), tmp->table); + else { + log_debug("Remembering foreign routing policy rule: priority: %"PRIu32", %s/%u -> %s/%u, iif: %s, oif: %s, table: %"PRIu32, + tmp->priority, strna(from), tmp->from_prefixlen, strna(to), tmp->to_prefixlen, strna(tmp->iif), strna(tmp->oif), tmp->table); + r = routing_policy_rule_add_foreign(m, tmp, &rule); + if (r < 0) { + log_warning_errno(r, "Could not remember foreign rule, ignoring: %m"); + return 0; + } + } + break; + case RTM_DELRULE: + if (rule) { + log_debug("Forgetting routing policy rule: priority: %"PRIu32", %s/%u -> %s/%u, iif: %s, oif: %s, table: %"PRIu32, + tmp->priority, strna(from), tmp->from_prefixlen, strna(to), tmp->to_prefixlen, strna(tmp->iif), strna(tmp->oif), tmp->table); + routing_policy_rule_free(rule); + } else + log_debug("Kernel removed a routing policy rule we don't remember: priority: %"PRIu32", %s/%u -> %s/%u, iif: %s, oif: %s, table: %"PRIu32", ignoring.", + tmp->priority, strna(from), tmp->from_prefixlen, strna(to), tmp->to_prefixlen, strna(tmp->iif), strna(tmp->oif), tmp->table); + break; + + default: + assert_not_reached("Received invalid RTNL message type"); + } + + return 1; +} + +static int parse_fwmark_fwmask(const char *s, uint32_t *ret_fwmark, uint32_t *ret_fwmask) { + _cleanup_free_ char *fwmark_str = NULL; + uint32_t fwmark, fwmask = 0; + const char *slash; + int r; + + assert(s); + assert(ret_fwmark); + assert(ret_fwmask); + + slash = strchr(s, '/'); + if (slash) { + fwmark_str = strndup(s, slash - s); + if (!fwmark_str) + return -ENOMEM; + } + + r = safe_atou32(fwmark_str ?: s, &fwmark); + if (r < 0) + return r; + + if (fwmark > 0) { + if (slash) { + r = safe_atou32(slash + 1, &fwmask); + if (r < 0) + return r; + } else + fwmask = UINT32_MAX; + } + + *ret_fwmark = fwmark; + *ret_fwmask = fwmask; + + return 0; +} + +int config_parse_routing_policy_rule_tos( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(routing_policy_rule_free_or_set_invalidp) RoutingPolicyRule *n = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = routing_policy_rule_new_static(network, filename, section_line, &n); + if (r < 0) + return log_oom(); + + r = safe_atou8(rvalue, &n->tos); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse RPDB rule TOS, ignoring: %s", rvalue); + return 0; + } + + n = NULL; + + return 0; +} + +int config_parse_routing_policy_rule_priority( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(routing_policy_rule_free_or_set_invalidp) RoutingPolicyRule *n = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = routing_policy_rule_new_static(network, filename, section_line, &n); + if (r < 0) + return log_oom(); + + r = safe_atou32(rvalue, &n->priority); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse RPDB rule priority, ignoring: %s", rvalue); + return 0; + } + + n = NULL; + + return 0; +} + +int config_parse_routing_policy_rule_table( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(routing_policy_rule_free_or_set_invalidp) RoutingPolicyRule *n = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = routing_policy_rule_new_static(network, filename, section_line, &n); + if (r < 0) + return log_oom(); + + r = safe_atou32(rvalue, &n->table); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse RPDB rule table, ignoring: %s", rvalue); + return 0; + } + + n = NULL; + + return 0; +} + +int config_parse_routing_policy_rule_fwmark_mask( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(routing_policy_rule_free_or_set_invalidp) RoutingPolicyRule *n = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = routing_policy_rule_new_static(network, filename, section_line, &n); + if (r < 0) + return log_oom(); + + r = parse_fwmark_fwmask(rvalue, &n->fwmark, &n->fwmask); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse RPDB rule firewall mark or mask, ignoring: %s", rvalue); + return 0; + } + + n = NULL; + + return 0; +} + +int config_parse_routing_policy_rule_prefix( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(routing_policy_rule_free_or_set_invalidp) RoutingPolicyRule *n = NULL; + Network *network = userdata; + union in_addr_union *buffer; + uint8_t *prefixlen; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = routing_policy_rule_new_static(network, filename, section_line, &n); + if (r < 0) + return log_oom(); + + if (streq(lvalue, "To")) { + buffer = &n->to; + prefixlen = &n->to_prefixlen; + } else { + buffer = &n->from; + prefixlen = &n->from_prefixlen; + } + + if (n->family == AF_UNSPEC) + r = in_addr_prefix_from_string_auto(rvalue, &n->family, buffer, prefixlen); + else + r = in_addr_prefix_from_string(rvalue, n->family, buffer, prefixlen); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "RPDB rule prefix is invalid, ignoring assignment: %s", rvalue); + return 0; + } + + n = NULL; + + return 0; +} + +int config_parse_routing_policy_rule_device( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(routing_policy_rule_free_or_set_invalidp) RoutingPolicyRule *n = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = routing_policy_rule_new_static(network, filename, section_line, &n); + if (r < 0) + return log_oom(); + + if (!ifname_valid(rvalue)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Failed to parse '%s' interface name, ignoring: %s", lvalue, rvalue); + return 0; + } + + if (streq(lvalue, "IncomingInterface")) { + r = free_and_strdup(&n->iif, rvalue); + if (r < 0) + return log_oom(); + } else { + r = free_and_strdup(&n->oif, rvalue); + if (r < 0) + return log_oom(); + } + + n = NULL; + + return 0; +} + +int config_parse_routing_policy_rule_port_range( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + _cleanup_(routing_policy_rule_free_or_set_invalidp) RoutingPolicyRule *n = NULL; + Network *network = userdata; + uint16_t low, high; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = routing_policy_rule_new_static(network, filename, section_line, &n); + if (r < 0) + return log_oom(); + + r = parse_ip_port_range(rvalue, &low, &high); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse routing policy rule port range '%s'", rvalue); + return 0; + } + + if (streq(lvalue, "SourcePort")) { + n->sport.start = low; + n->sport.end = high; + } else { + n->dport.start = low; + n->dport.end = high; + } + + n = NULL; + + return 0; +} + +int config_parse_routing_policy_rule_ip_protocol( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(routing_policy_rule_free_or_set_invalidp) RoutingPolicyRule *n = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = routing_policy_rule_new_static(network, filename, section_line, &n); + if (r < 0) + return log_oom(); + + r = parse_ip_protocol(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse IP protocol '%s' for routing policy rule, ignoring: %m", rvalue); + return 0; + } + + n->protocol = r; + + n = NULL; + + return 0; +} + +int config_parse_routing_policy_rule_invert( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(routing_policy_rule_free_or_set_invalidp) RoutingPolicyRule *n = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = routing_policy_rule_new_static(network, filename, section_line, &n); + if (r < 0) + return log_oom(); + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse RPDB rule invert, ignoring: %s", rvalue); + return 0; + } + + n->invert_rule = r; + + n = NULL; + + return 0; +} + +int config_parse_routing_policy_rule_family( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(routing_policy_rule_free_or_set_invalidp) RoutingPolicyRule *n = NULL; + Network *network = userdata; + AddressFamily a; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = routing_policy_rule_new_static(network, filename, section_line, &n); + if (r < 0) + return log_oom(); + + a = routing_policy_rule_address_family_from_string(rvalue); + if (a < 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid address family '%s', ignoring.", rvalue); + return 0; + } + + n->address_family = a; + n = NULL; + + return 0; +} + +int config_parse_routing_policy_rule_uid_range( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(routing_policy_rule_free_or_set_invalidp) RoutingPolicyRule *n = NULL; + Network *network = userdata; + uid_t start, end; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = routing_policy_rule_new_static(network, filename, section_line, &n); + if (r < 0) + return log_oom(); + + r = get_user_creds(&rvalue, &start, NULL, NULL, NULL, 0); + if (r >= 0) + end = start; + else { + r = parse_uid_range(rvalue, &start, &end); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid uid or uid range '%s', ignoring: %m", rvalue); + return 0; + } + } + + n->uid_range.start = start; + n->uid_range.end = end; + n = NULL; + + return 0; +} + +int config_parse_routing_policy_rule_suppress_prefixlen( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(routing_policy_rule_free_or_set_invalidp) RoutingPolicyRule *n = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = routing_policy_rule_new_static(network, filename, section_line, &n); + if (r < 0) + return log_oom(); + + r = parse_ip_prefix_length(rvalue, &n->suppress_prefixlen); + if (r == -ERANGE) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Prefix length outside of valid range 0-128, ignoring: %s", rvalue); + return 0; + } + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse RPDB rule suppress_prefixlen, ignoring: %s", rvalue); + return 0; + } + + n = NULL; + + return 0; +} + +static int routing_policy_rule_section_verify(RoutingPolicyRule *rule) { + if (section_is_invalid(rule->section)) + return -EINVAL; + + if ((rule->family == AF_INET && FLAGS_SET(rule->address_family, ADDRESS_FAMILY_IPV6)) || + (rule->family == AF_INET6 && FLAGS_SET(rule->address_family, ADDRESS_FAMILY_IPV4))) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: address family specified by Family= conflicts with the address " + "specified by To= or From=. Ignoring [RoutingPolicyRule] section from line %u.", + rule->section->filename, rule->section->line); + + if (rule->family == AF_UNSPEC && rule->address_family == ADDRESS_FAMILY_NO) + rule->family = AF_INET; + + return 0; +} + +void network_drop_invalid_routing_policy_rules(Network *network) { + RoutingPolicyRule *rule; + + assert(network); + + HASHMAP_FOREACH(rule, network->rules_by_section) + if (routing_policy_rule_section_verify(rule) < 0) + routing_policy_rule_free(rule); +} + +int routing_policy_serialize_rules(Set *rules, FILE *f) { + RoutingPolicyRule *rule; + int r; + + assert(f); + + SET_FOREACH(rule, rules) { + const char *family_str; + bool space = false; + + fputs("RULE=", f); + + family_str = af_to_name(rule->family); + if (family_str) { + fprintf(f, "family=%s", + family_str); + space = true; + } + + if (!in_addr_is_null(rule->family, &rule->from)) { + _cleanup_free_ char *str = NULL; + + r = in_addr_to_string(rule->family, &rule->from, &str); + if (r < 0) + return r; + + fprintf(f, "%sfrom=%s/%hhu", + space ? " " : "", + str, rule->from_prefixlen); + space = true; + } + + if (!in_addr_is_null(rule->family, &rule->to)) { + _cleanup_free_ char *str = NULL; + + r = in_addr_to_string(rule->family, &rule->to, &str); + if (r < 0) + return r; + + fprintf(f, "%sto=%s/%hhu", + space ? " " : "", + str, rule->to_prefixlen); + space = true; + } + + if (rule->tos != 0) { + fprintf(f, "%stos=%hhu", + space ? " " : "", + rule->tos); + space = true; + } + + if (rule->priority != 0) { + fprintf(f, "%spriority=%"PRIu32, + space ? " " : "", + rule->priority); + space = true; + } + + if (rule->fwmark != 0) { + fprintf(f, "%sfwmark=%"PRIu32, + space ? " " : "", + rule->fwmark); + if (rule->fwmask != UINT32_MAX) + fprintf(f, "/%"PRIu32, rule->fwmask); + space = true; + } + + if (rule->iif) { + fprintf(f, "%siif=%s", + space ? " " : "", + rule->iif); + space = true; + } + + if (rule->oif) { + fprintf(f, "%soif=%s", + space ? " " : "", + rule->oif); + space = true; + } + + if (rule->protocol != 0) { + fprintf(f, "%sprotocol=%hhu", + space ? " " : "", + rule->protocol); + space = true; + } + + if (rule->sport.start != 0 || rule->sport.end != 0) { + fprintf(f, "%ssourcesport=%"PRIu16"-%"PRIu16, + space ? " " : "", + rule->sport.start, rule->sport.end); + space = true; + } + + if (rule->dport.start != 0 || rule->dport.end != 0) { + fprintf(f, "%sdestinationport=%"PRIu16"-%"PRIu16, + space ? " " : "", + rule->dport.start, rule->dport.end); + space = true; + } + + if (rule->uid_range.start != UID_INVALID && rule->uid_range.end != UID_INVALID) { + assert_cc(sizeof(uid_t) == sizeof(uint32_t)); + fprintf(f, "%suidrange="UID_FMT"-"UID_FMT, + space ? " " : "", + rule->uid_range.start, rule->uid_range.end); + space = true; + } + + if (rule->suppress_prefixlen >= 0) { + fprintf(f, "%ssuppress_prefixlen=%d", + space ? " " : "", + rule->suppress_prefixlen); + space = true; + } + + fprintf(f, "%sinvert_rule=%s table=%"PRIu32"\n", + space ? " " : "", + yes_no(rule->invert_rule), + rule->table); + } + + return 0; +} + +static int routing_policy_rule_read_full_file(const char *state_file, char ***ret) { + _cleanup_strv_free_ char **lines = NULL; + _cleanup_free_ char *s = NULL; + int r; + + assert(state_file); + + r = read_full_file(state_file, &s, NULL); + if (r == -ENOENT) { + *ret = NULL; + return 0; + } + if (r < 0) + return r; + + lines = strv_split_newlines(s); + if (!lines) + return -ENOMEM; + + *ret = TAKE_PTR(lines); + return 0; +} + +int routing_policy_load_rules(const char *state_file, Set **rules) { + _cleanup_strv_free_ char **data = NULL; + char **i; + int r; + + assert(state_file); + assert(rules); + + r = routing_policy_rule_read_full_file(state_file, &data); + if (r < 0) + return log_warning_errno(r, "Failed to read %s, ignoring: %m", state_file); + + STRV_FOREACH(i, data) { + _cleanup_(routing_policy_rule_freep) RoutingPolicyRule *rule = NULL; + const char *p; + + p = startswith(*i, "RULE="); + if (!p) + continue; + + r = routing_policy_rule_new(&rule); + if (r < 0) + return log_oom(); + + for (;;) { + _cleanup_free_ char *a = NULL; + char *b; + + r = extract_first_word(&p, &a, NULL, 0); + if (r < 0) + return log_oom(); + if (r == 0) + break; + + b = strchr(a, '='); + if (!b) { + log_warning_errno(r, "Failed to parse RPDB rule, ignoring: %s", a); + continue; + } + *b++ = '\0'; + + if (streq(a, "family")) { + r = af_from_name(b); + if (r < 0) { + log_warning_errno(r, "Failed to parse RPDB rule family, ignoring: %s", b); + continue; + } + if (rule->family != AF_UNSPEC && rule->family != r) { + log_warning("RPDB rule family is already specified, ignoring assignment: %s", b); + continue; + } + rule->family = r; + } else if (STR_IN_SET(a, "from", "to")) { + union in_addr_union *buffer; + uint8_t *prefixlen; + + if (streq(a, "to")) { + buffer = &rule->to; + prefixlen = &rule->to_prefixlen; + } else { + buffer = &rule->from; + prefixlen = &rule->from_prefixlen; + } + + if (rule->family == AF_UNSPEC) + r = in_addr_prefix_from_string_auto(b, &rule->family, buffer, prefixlen); + else + r = in_addr_prefix_from_string(b, rule->family, buffer, prefixlen); + if (r < 0) { + log_warning_errno(r, "RPDB rule prefix is invalid, ignoring assignment: %s", b); + continue; + } + } else if (streq(a, "tos")) { + r = safe_atou8(b, &rule->tos); + if (r < 0) { + log_warning_errno(r, "Failed to parse RPDB rule TOS, ignoring: %s", b); + continue; + } + } else if (streq(a, "table")) { + r = safe_atou32(b, &rule->table); + if (r < 0) { + log_warning_errno(r, "Failed to parse RPDB rule table, ignoring: %s", b); + continue; + } + } else if (streq(a, "priority")) { + r = safe_atou32(b, &rule->priority); + if (r < 0) { + log_warning_errno(r, "Failed to parse RPDB rule priority, ignoring: %s", b); + continue; + } + } else if (streq(a, "fwmark")) { + r = parse_fwmark_fwmask(b, &rule->fwmark, &rule->fwmask); + if (r < 0) { + log_warning_errno(r, "Failed to parse RPDB rule firewall mark or mask, ignoring: %s", a); + continue; + } + } else if (streq(a, "iif")) { + if (free_and_strdup(&rule->iif, b) < 0) + return log_oom(); + + } else if (streq(a, "oif")) { + + if (free_and_strdup(&rule->oif, b) < 0) + return log_oom(); + } else if (streq(a, "protocol")) { + r = safe_atou8(b, &rule->protocol); + if (r < 0) { + log_warning_errno(r, "Failed to parse RPDB rule protocol, ignoring: %s", b); + continue; + } + } else if (streq(a, "sourceport")) { + uint16_t low, high; + + r = parse_ip_port_range(b, &low, &high); + if (r < 0) { + log_warning_errno(r, "Invalid routing policy rule source port range, ignoring assignment: '%s'", b); + continue; + } + + rule->sport.start = low; + rule->sport.end = high; + } else if (streq(a, "destinationport")) { + uint16_t low, high; + + r = parse_ip_port_range(b, &low, &high); + if (r < 0) { + log_warning_errno(r, "Invalid routing policy rule destination port range, ignoring assignment: '%s'", b); + continue; + } + + rule->dport.start = low; + rule->dport.end = high; + } else if (streq(a, "uidrange")) { + uid_t lower, upper; + + r = parse_uid_range(b, &lower, &upper); + if (r < 0) { + log_warning_errno(r, "Invalid routing policy rule uid range, ignoring assignment: '%s'", b); + continue; + } + + rule->uid_range.start = lower; + rule->uid_range.end = upper; + } else if (streq(a, "suppress_prefixlen")) { + r = parse_ip_prefix_length(b, &rule->suppress_prefixlen); + if (r == -ERANGE) { + log_warning_errno(r, "Prefix length outside of valid range 0-128, ignoring: %s", b); + continue; + } + if (r < 0) { + log_warning_errno(r, "Failed to parse RPDB rule suppress_prefixlen, ignoring: %s", b); + continue; + } + } else if (streq(a, "invert_rule")) { + r = parse_boolean(b); + if (r < 0) { + log_warning_errno(r, "Failed to parse RPDB rule invert_rule, ignoring: %s", b); + continue; + } + rule->invert_rule = r; + } else + log_warning("Unknown RPDB rule, ignoring: %s", a); + } + + r = set_ensure_put(rules, &routing_policy_rule_hash_ops, rule); + if (r < 0) { + log_warning_errno(r, "Failed to add RPDB rule to saved DB, ignoring: %s", *i); + continue; + } + if (r > 0) + rule = NULL; + } + + return 0; +} diff --git a/src/network/networkd-routing-policy-rule.h b/src/network/networkd-routing-policy-rule.h new file mode 100644 index 0000000..baf086f --- /dev/null +++ b/src/network/networkd-routing-policy-rule.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <inttypes.h> +#include <linux/fib_rules.h> +#include <stdbool.h> +#include <stdio.h> + +#include "conf-parser.h" +#include "in-addr-util.h" +#include "networkd-util.h" +#include "set.h" + +typedef struct Network Network; +typedef struct Link Link; +typedef struct Manager Manager; + +typedef struct RoutingPolicyRule { + Manager *manager; + Network *network; + NetworkConfigSection *section; + + bool invert_rule; + + uint8_t tos; + uint8_t protocol; + + uint32_t table; + uint32_t fwmark; + uint32_t fwmask; + uint32_t priority; + + AddressFamily address_family; /* Specified by Family= */ + int family; /* Automatically determined by From= or To= */ + unsigned char to_prefixlen; + unsigned char from_prefixlen; + + char *iif; + char *oif; + + union in_addr_union to; + union in_addr_union from; + + struct fib_rule_port_range sport; + struct fib_rule_port_range dport; + struct fib_rule_uid_range uid_range; + + int suppress_prefixlen; +} RoutingPolicyRule; + +RoutingPolicyRule *routing_policy_rule_free(RoutingPolicyRule *rule); + +void network_drop_invalid_routing_policy_rules(Network *network); + +int link_set_routing_policy_rules(Link *link); + +int manager_rtnl_process_rule(sd_netlink *rtnl, sd_netlink_message *message, Manager *m); + +int routing_policy_serialize_rules(Set *rules, FILE *f); +int routing_policy_load_rules(const char *state_file, Set **rules); + +CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_tos); +CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_table); +CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_fwmark_mask); +CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_prefix); +CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_priority); +CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_device); +CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_port_range); +CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_ip_protocol); +CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_invert); +CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_family); +CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_uid_range); +CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_suppress_prefixlen); diff --git a/src/network/networkd-speed-meter.c b/src/network/networkd-speed-meter.c new file mode 100644 index 0000000..e7f0682 --- /dev/null +++ b/src/network/networkd-speed-meter.c @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> + +#include "sd-event.h" +#include "sd-netlink.h" + +#include "networkd-link-bus.h" +#include "networkd-link.h" +#include "networkd-manager.h" +#include "networkd-speed-meter.h" + +static int process_message(Manager *manager, sd_netlink_message *message) { + uint16_t type; + int ifindex, r; + Link *link; + + r = sd_netlink_message_get_type(message, &type); + if (r < 0) + return r; + + if (type != RTM_NEWLINK) + return 0; + + r = sd_rtnl_message_link_get_ifindex(message, &ifindex); + if (r < 0) + return r; + + link = hashmap_get(manager->links, INT_TO_PTR(ifindex)); + if (!link) + return -ENODEV; + + link->stats_old = link->stats_new; + + r = sd_netlink_message_read(message, IFLA_STATS64, sizeof link->stats_new, &link->stats_new); + if (r < 0) + return r; + + link->stats_updated = true; + + return 0; +} + +static int speed_meter_handler(sd_event_source *s, uint64_t usec, void *userdata) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + Manager *manager = userdata; + sd_netlink_message *i; + usec_t usec_now; + Link *link; + int r; + + assert(s); + assert(userdata); + + r = sd_event_now(sd_event_source_get_event(s), CLOCK_MONOTONIC, &usec_now); + if (r < 0) + return r; + + r = sd_event_source_set_time(s, usec_now + manager->speed_meter_interval_usec); + if (r < 0) + return r; + + manager->speed_meter_usec_old = manager->speed_meter_usec_new; + manager->speed_meter_usec_new = usec_now; + + HASHMAP_FOREACH(link, manager->links) + link->stats_updated = false; + + r = sd_rtnl_message_new_link(manager->rtnl, &req, RTM_GETLINK, 0); + if (r < 0) { + log_warning_errno(r, "Failed to allocate RTM_GETLINK netlink message, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_request_dump(req, true); + if (r < 0) { + log_warning_errno(r, "Failed to set dump flag, ignoring: %m"); + return 0; + } + + r = sd_netlink_call(manager->rtnl, req, 0, &reply); + if (r < 0) { + log_warning_errno(r, "Failed to call RTM_GETLINK, ignoring: %m"); + return 0; + } + + for (i = reply; i; i = sd_netlink_message_next(i)) + (void) process_message(manager, i); + + return 0; +} + +int manager_start_speed_meter(Manager *manager) { + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; + int r; + + assert(manager); + assert(manager->event); + + if (!manager->use_speed_meter) + return 0; + + r = sd_event_add_time(manager->event, &s, CLOCK_MONOTONIC, 0, 0, speed_meter_handler, manager); + if (r < 0) + return r; + + r = sd_event_source_set_enabled(s, SD_EVENT_ON); + if (r < 0) + return r; + + manager->speed_meter_event_source = TAKE_PTR(s); + return 0; +} diff --git a/src/network/networkd-speed-meter.h b/src/network/networkd-speed-meter.h new file mode 100644 index 0000000..4dd024b --- /dev/null +++ b/src/network/networkd-speed-meter.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/* Default interval is 10sec. The speed meter periodically make networkd + * to be woke up. So, too small interval value is not desired. + * We set the minimum value 100msec = 0.1sec. */ +#define SPEED_METER_DEFAULT_TIME_INTERVAL (10 * USEC_PER_SEC) +#define SPEED_METER_MINIMUM_TIME_INTERVAL (100 * USEC_PER_MSEC) + +typedef struct Manager Manager; + +int manager_start_speed_meter(Manager *m); diff --git a/src/network/networkd-sriov.c b/src/network/networkd-sriov.c new file mode 100644 index 0000000..68f43b5 --- /dev/null +++ b/src/network/networkd-sriov.c @@ -0,0 +1,532 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2020 VMware, Inc. */ + +#include "alloc-util.h" +#include "netlink-util.h" +#include "networkd-manager.h" +#include "networkd-sriov.h" +#include "parse-util.h" +#include "set.h" +#include "string-util.h" + +static int sr_iov_new(SRIOV **ret) { + SRIOV *sr_iov; + + sr_iov = new(SRIOV, 1); + if (!sr_iov) + return -ENOMEM; + + *sr_iov = (SRIOV) { + .vf = (uint32_t) -1, + .vlan_proto = ETH_P_8021Q, + .vf_spoof_check_setting = -1, + .trust = -1, + .query_rss = -1, + .link_state = _SR_IOV_LINK_STATE_INVALID, + }; + + *ret = TAKE_PTR(sr_iov); + + return 0; +} + +static int sr_iov_new_static(Network *network, const char *filename, unsigned section_line, SRIOV **ret) { + _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; + _cleanup_(sr_iov_freep) SRIOV *sr_iov = NULL; + SRIOV *existing = NULL; + int r; + + assert(network); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = network_config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + existing = ordered_hashmap_get(network->sr_iov_by_section, n); + if (existing) { + *ret = existing; + return 0; + } + + r = sr_iov_new(&sr_iov); + if (r < 0) + return r; + + sr_iov->network = network; + sr_iov->section = TAKE_PTR(n); + + r = ordered_hashmap_ensure_allocated(&network->sr_iov_by_section, &network_config_hash_ops); + if (r < 0) + return r; + + r = ordered_hashmap_put(network->sr_iov_by_section, sr_iov->section, sr_iov); + if (r < 0) + return r; + + *ret = TAKE_PTR(sr_iov); + return 0; +} + +SRIOV *sr_iov_free(SRIOV *sr_iov) { + if (!sr_iov) + return NULL; + + if (sr_iov->network && sr_iov->section) + ordered_hashmap_remove(sr_iov->network->sr_iov_by_section, sr_iov->section); + + network_config_section_free(sr_iov->section); + + return mfree(sr_iov); +} + +static int sr_iov_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + assert(link->sr_iov_messages > 0); + link->sr_iov_messages--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EEXIST) { + log_link_message_error_errno(link, m, r, "Could not set up SR-IOV"); + link_enter_failed(link); + return 1; + } + + if (link->sr_iov_messages == 0) { + log_link_debug(link, "SR-IOV configured"); + link->sr_iov_configured = true; + link_check_ready(link); + } + + return 1; +} + +static int sr_iov_configure(Link *link, SRIOV *sr_iov) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(link); + assert(link->manager); + assert(link->manager->rtnl); + assert(link->ifindex > 0); + + log_link_debug(link, "Setting SR-IOV virtual function %"PRIu32, sr_iov->vf); + + r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m"); + + r = sd_netlink_message_open_container(req, IFLA_VFINFO_LIST); + if (r < 0) + return log_link_error_errno(link, r, "Could not open IFLA_VFINFO_LIST container: %m"); + + r = sd_netlink_message_open_container(req, IFLA_VF_INFO); + if (r < 0) + return log_link_error_errno(link, r, "Could not open IFLA_VF_INFO container: %m"); + + if (!ether_addr_is_null(&sr_iov->mac)) { + struct ifla_vf_mac ivm = { + .vf = sr_iov->vf, + }; + + memcpy(ivm.mac, &sr_iov->mac, ETH_ALEN); + r = sd_netlink_message_append_data(req, IFLA_VF_MAC, &ivm, sizeof(struct ifla_vf_mac)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_VF_MAC: %m"); + } + + if (sr_iov->vf_spoof_check_setting >= 0) { + struct ifla_vf_spoofchk ivs = { + .vf = sr_iov->vf, + .setting = sr_iov->vf_spoof_check_setting, + }; + + r = sd_netlink_message_append_data(req, IFLA_VF_SPOOFCHK, &ivs, sizeof(struct ifla_vf_spoofchk)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_VF_SPOOFCHK: %m"); + } + + if (sr_iov->query_rss >= 0) { + struct ifla_vf_rss_query_en ivs = { + .vf = sr_iov->vf, + .setting = sr_iov->query_rss, + }; + + r = sd_netlink_message_append_data(req, IFLA_VF_RSS_QUERY_EN, &ivs, sizeof(struct ifla_vf_rss_query_en)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_VF_RSS_QUERY_EN: %m"); + } + + if (sr_iov->trust >= 0) { + struct ifla_vf_trust ivt = { + .vf = sr_iov->vf, + .setting = sr_iov->trust, + }; + + r = sd_netlink_message_append_data(req, IFLA_VF_TRUST, &ivt, sizeof(struct ifla_vf_trust)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_VF_TRUST: %m"); + } + + if (sr_iov->link_state >= 0) { + struct ifla_vf_link_state ivl = { + .vf = sr_iov->vf, + .link_state = sr_iov->link_state, + }; + + r = sd_netlink_message_append_data(req, IFLA_VF_LINK_STATE, &ivl, sizeof(struct ifla_vf_link_state)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_VF_LINK_STATE: %m"); + } + + if (sr_iov->vlan > 0) { + /* Because of padding, first the buffer must be initialized with 0. */ + struct ifla_vf_vlan_info ivvi = {}; + ivvi.vf = sr_iov->vf; + ivvi.vlan = sr_iov->vlan; + ivvi.qos = sr_iov->qos; + ivvi.vlan_proto = htobe16(sr_iov->vlan_proto); + + r = sd_netlink_message_open_container(req, IFLA_VF_VLAN_LIST); + if (r < 0) + return log_link_error_errno(link, r, "Could not open IFLA_VF_VLAN_LIST container: %m"); + + r = sd_netlink_message_append_data(req, IFLA_VF_VLAN_INFO, &ivvi, sizeof(struct ifla_vf_vlan_info)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_VF_VLAN_INFO: %m"); + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not close IFLA_VF_VLAN_LIST container: %m"); + } + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not close IFLA_VF_INFO container: %m"); + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not close IFLA_VFINFO_LIST container: %m"); + + r = netlink_call_async(link->manager->rtnl, NULL, req, sr_iov_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + link->sr_iov_messages++; + + return 0; +} + +int link_configure_sr_iov(Link *link) { + SRIOV *sr_iov; + int r; + + link->sr_iov_configured = false; + link->sr_iov_messages = 0; + + ORDERED_HASHMAP_FOREACH(sr_iov, link->network->sr_iov_by_section) { + r = sr_iov_configure(link, sr_iov); + if (r < 0) + return r; + } + + if (link->sr_iov_messages == 0) + link->sr_iov_configured = true; + else + log_link_debug(link, "Configuring SR-IOV"); + + return 0; +} + +static int sr_iov_section_verify(SRIOV *sr_iov) { + assert(sr_iov); + + if (section_is_invalid(sr_iov->section)) + return -EINVAL; + + if (sr_iov->vf == (uint32_t) -1) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: [SRIOV] section without VirtualFunction= field configured. " + "Ignoring [SRIOV] section from line %u.", + sr_iov->section->filename, sr_iov->section->line); + + return 0; +} + +void network_drop_invalid_sr_iov(Network *network) { + SRIOV *sr_iov; + + assert(network); + + ORDERED_HASHMAP_FOREACH(sr_iov, network->sr_iov_by_section) + if (sr_iov_section_verify(sr_iov) < 0) + sr_iov_free(sr_iov); +} + +int config_parse_sr_iov_uint32( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(sr_iov_free_or_set_invalidp) SRIOV *sr_iov = NULL; + Network *network = data; + uint32_t k; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = sr_iov_new_static(network, filename, section_line, &sr_iov); + if (r < 0) + return r; + + if (isempty(rvalue)) { + if (streq(lvalue, "VirtualFunction")) + sr_iov->vf = (uint32_t) -1; + else if (streq(lvalue, "VLANId")) + sr_iov->vlan = 0; + else if (streq(lvalue, "QualityOfService")) + sr_iov->qos = 0; + else + assert_not_reached("Invalid lvalue"); + + TAKE_PTR(sr_iov); + return 0; + } + + r = safe_atou32(rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse SR-IOV '%s=', ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + if (streq(lvalue, "VLANId")) { + if (k == 0 || k > 4095) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid SR-IOV VLANId: %d", k); + return 0; + } + sr_iov->vlan = k; + } else if (streq(lvalue, "VirtualFunction")) { + if (k >= INT_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid SR-IOV virtual function: %d", k); + return 0; + } + sr_iov->vf = k; + } else if (streq(lvalue, "QualityOfService")) + sr_iov->qos = k; + else + assert_not_reached("Invalid lvalue"); + + TAKE_PTR(sr_iov); + return 0; +} + +int config_parse_sr_iov_vlan_proto( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(sr_iov_free_or_set_invalidp) SRIOV *sr_iov = NULL; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = sr_iov_new_static(network, filename, section_line, &sr_iov); + if (r < 0) + return r; + + if (isempty(rvalue) || streq(rvalue, "802.1Q")) + sr_iov->vlan_proto = ETH_P_8021Q; + else if (streq(rvalue, "802.1ad")) + sr_iov->vlan_proto = ETH_P_8021AD; + else { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid SR-IOV '%s=', ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + TAKE_PTR(sr_iov); + return 0; +} + +int config_parse_sr_iov_link_state( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(sr_iov_free_or_set_invalidp) SRIOV *sr_iov = NULL; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = sr_iov_new_static(network, filename, section_line, &sr_iov); + if (r < 0) + return r; + + /* Unfortunately, SR_IOV_LINK_STATE_DISABLE is 2, not 0. So, we cannot use + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN() macro. */ + + if (isempty(rvalue)) { + sr_iov->link_state = _SR_IOV_LINK_STATE_INVALID; + TAKE_PTR(sr_iov); + return 0; + } + + if (streq(rvalue, "auto")) { + sr_iov->link_state = SR_IOV_LINK_STATE_AUTO; + TAKE_PTR(sr_iov); + return 0; + } + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse SR-IOV '%s=', ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + sr_iov->link_state = r ? SR_IOV_LINK_STATE_ENABLE : SR_IOV_LINK_STATE_DISABLE; + TAKE_PTR(sr_iov); + return 0; +} + +int config_parse_sr_iov_boolean( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(sr_iov_free_or_set_invalidp) SRIOV *sr_iov = NULL; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = sr_iov_new_static(network, filename, section_line, &sr_iov); + if (r < 0) + return r; + + if (isempty(rvalue)) { + if (streq(lvalue, "MACSpoofCheck")) + sr_iov->vf_spoof_check_setting = -1; + else if (streq(lvalue, "QueryReceiveSideScaling")) + sr_iov->query_rss = -1; + else if (streq(lvalue, "Trust")) + sr_iov->trust = -1; + else + assert_not_reached("Invalid lvalue"); + + TAKE_PTR(sr_iov); + return 0; + } + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse '%s=', ignoring: %s", lvalue, rvalue); + return 0; + } + + if (streq(lvalue, "MACSpoofCheck")) + sr_iov->vf_spoof_check_setting = r; + else if (streq(lvalue, "QueryReceiveSideScaling")) + sr_iov->query_rss = r; + else if (streq(lvalue, "Trust")) + sr_iov->trust = r; + else + assert_not_reached("Invalid lvalue"); + + TAKE_PTR(sr_iov); + return 0; +} + +int config_parse_sr_iov_mac( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(sr_iov_free_or_set_invalidp) SRIOV *sr_iov = NULL; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = sr_iov_new_static(network, filename, section_line, &sr_iov); + if (r < 0) + return r; + + if (isempty(rvalue)) { + sr_iov->mac = ETHER_ADDR_NULL; + TAKE_PTR(sr_iov); + return 0; + } + + r = ether_addr_from_string(rvalue, &sr_iov->mac); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse SR-IOV '%s=', ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + TAKE_PTR(sr_iov); + return 0; +} diff --git a/src/network/networkd-sriov.h b/src/network/networkd-sriov.h new file mode 100644 index 0000000..dae5ff0 --- /dev/null +++ b/src/network/networkd-sriov.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2020 VMware, Inc. */ +#pragma once + +#include <linux/if_link.h> + +#include "conf-parser.h" +#include "ether-addr-util.h" +#include "networkd-link.h" +#include "networkd-network.h" +#include "networkd-util.h" + +typedef enum SRIOVLinkState { + SR_IOV_LINK_STATE_AUTO = IFLA_VF_LINK_STATE_AUTO, + SR_IOV_LINK_STATE_ENABLE = IFLA_VF_LINK_STATE_ENABLE, + SR_IOV_LINK_STATE_DISABLE = IFLA_VF_LINK_STATE_DISABLE, + _SR_IOV_LINK_STATE_MAX, + _SR_IOV_LINK_STATE_INVALID = -1, +} SRIOVLinkState; + +typedef struct SRIOV { + NetworkConfigSection *section; + Network *network; + + uint32_t vf; /* 0 - 2147483646 */ + uint32_t vlan; /* 0 - 4095, 0 disables VLAN filter */ + uint32_t qos; + uint16_t vlan_proto; /* ETH_P_8021Q or ETH_P_8021AD */ + int vf_spoof_check_setting; + int query_rss; + int trust; + SRIOVLinkState link_state; + struct ether_addr mac; +} SRIOV; + +SRIOV *sr_iov_free(SRIOV *sr_iov); +int link_configure_sr_iov(Link *link); +void network_drop_invalid_sr_iov(Network *network); + +DEFINE_NETWORK_SECTION_FUNCTIONS(SRIOV, sr_iov_free); + +CONFIG_PARSER_PROTOTYPE(config_parse_sr_iov_uint32); +CONFIG_PARSER_PROTOTYPE(config_parse_sr_iov_boolean); +CONFIG_PARSER_PROTOTYPE(config_parse_sr_iov_link_state); +CONFIG_PARSER_PROTOTYPE(config_parse_sr_iov_vlan_proto); +CONFIG_PARSER_PROTOTYPE(config_parse_sr_iov_mac); diff --git a/src/network/networkd-sysctl.c b/src/network/networkd-sysctl.c new file mode 100644 index 0000000..bde0cec --- /dev/null +++ b/src/network/networkd-sysctl.c @@ -0,0 +1,288 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <netinet/in.h> +#include <linux/if.h> + +#include "missing_network.h" +#include "networkd-link.h" +#include "networkd-network.h" +#include "networkd-sysctl.h" +#include "socket-util.h" +#include "string-table.h" +#include "sysctl-util.h" + +static int link_update_ipv6_sysctl(Link *link) { + assert(link); + + if (link->flags & IFF_LOOPBACK) + return 0; + + if (!link_ipv6_enabled(link)) + return 0; + + return sysctl_write_ip_property_boolean(AF_INET6, link->ifname, "disable_ipv6", false); +} + +static int link_set_proxy_arp(Link *link) { + assert(link); + + if (link->flags & IFF_LOOPBACK) + return 0; + + if (!link->network) + return 0; + + if (link->network->proxy_arp < 0) + return 0; + + return sysctl_write_ip_property_boolean(AF_INET, link->ifname, "proxy_arp", link->network->proxy_arp > 0); +} + +static bool link_ip_forward_enabled(Link *link, int family) { + assert(link); + assert(IN_SET(family, AF_INET, AF_INET6)); + + if (family == AF_INET6 && !socket_ipv6_is_supported()) + return false; + + if (link->flags & IFF_LOOPBACK) + return false; + + if (!link->network) + return false; + + return link->network->ip_forward & (family == AF_INET ? ADDRESS_FAMILY_IPV4 : ADDRESS_FAMILY_IPV6); +} + +static int link_set_ipv4_forward(Link *link) { + assert(link); + + if (!link_ip_forward_enabled(link, AF_INET)) + return 0; + + /* We propagate the forwarding flag from one interface to the + * global setting one way. This means: as long as at least one + * interface was configured at any time that had IP forwarding + * enabled the setting will stay on for good. We do this + * primarily to keep IPv4 and IPv6 packet forwarding behaviour + * somewhat in sync (see below). */ + + return sysctl_write_ip_property(AF_INET, NULL, "ip_forward", "1"); +} + +static int link_set_ipv6_forward(Link *link) { + assert(link); + + if (!link_ip_forward_enabled(link, AF_INET6)) + return 0; + + /* On Linux, the IPv6 stack does not know a per-interface + * packet forwarding setting: either packet forwarding is on + * for all, or off for all. We hence don't bother with a + * per-interface setting, but simply propagate the interface + * flag, if it is set, to the global flag, one-way. Note that + * while IPv4 would allow a per-interface flag, we expose the + * same behaviour there and also propagate the setting from + * one to all, to keep things simple (see above). */ + + return sysctl_write_ip_property(AF_INET6, "all", "forwarding", "1"); +} + +static int link_set_ipv6_privacy_extensions(Link *link) { + assert(link); + + if (!socket_ipv6_is_supported()) + return 0; + + if (link->flags & IFF_LOOPBACK) + return 0; + + if (!link->network) + return 0; + + // this is the special "kernel" value + if (link->network->ipv6_privacy_extensions == _IPV6_PRIVACY_EXTENSIONS_INVALID) + return 0; + + return sysctl_write_ip_property_int(AF_INET6, link->ifname, "use_tempaddr", (int) link->network->ipv6_privacy_extensions); +} + +static int link_set_ipv6_accept_ra(Link *link) { + assert(link); + + /* Make this a NOP if IPv6 is not available */ + if (!socket_ipv6_is_supported()) + return 0; + + if (link->flags & IFF_LOOPBACK) + return 0; + + if (!link->network) + return 0; + + return sysctl_write_ip_property(AF_INET6, link->ifname, "accept_ra", "0"); +} + +static int link_set_ipv6_dad_transmits(Link *link) { + assert(link); + + /* Make this a NOP if IPv6 is not available */ + if (!socket_ipv6_is_supported()) + return 0; + + if (link->flags & IFF_LOOPBACK) + return 0; + + if (!link->network) + return 0; + + if (link->network->ipv6_dad_transmits < 0) + return 0; + + return sysctl_write_ip_property_int(AF_INET6, link->ifname, "dad_transmits", link->network->ipv6_dad_transmits); +} + +static int link_set_ipv6_hop_limit(Link *link) { + assert(link); + + /* Make this a NOP if IPv6 is not available */ + if (!socket_ipv6_is_supported()) + return 0; + + if (link->flags & IFF_LOOPBACK) + return 0; + + if (!link->network) + return 0; + + if (link->network->ipv6_hop_limit < 0) + return 0; + + return sysctl_write_ip_property_int(AF_INET6, link->ifname, "hop_limit", link->network->ipv6_hop_limit); +} + +static int link_set_ipv4_accept_local(Link *link) { + assert(link); + + if (link->flags & IFF_LOOPBACK) + return 0; + + if (link->network->ipv4_accept_local < 0) + return 0; + + return sysctl_write_ip_property_boolean(AF_INET, link->ifname, "accept_local", link->network->ipv4_accept_local > 0); +} + +int link_set_sysctl(Link *link) { + int r; + + assert(link); + + /* If IPv6 configured that is static IPv6 address and IPv6LL autoconfiguration is enabled + * for this interface, then enable IPv6 */ + r = link_update_ipv6_sysctl(link); + if (r < 0) + log_link_warning_errno(link, r, "Cannot enable IPv6, ignoring: %m"); + + r = link_set_proxy_arp(link); + if (r < 0) + log_link_warning_errno(link, r, "Cannot configure proxy ARP for interface, ignoring: %m"); + + r = link_set_ipv4_forward(link); + if (r < 0) + log_link_warning_errno(link, r, "Cannot turn on IPv4 packet forwarding, ignoring: %m"); + + r = link_set_ipv6_forward(link); + if (r < 0) + log_link_warning_errno(link, r, "Cannot configure IPv6 packet forwarding, ignoring: %m");; + + r = link_set_ipv6_privacy_extensions(link); + if (r < 0) + log_link_warning_errno(link, r, "Cannot configure IPv6 privacy extensions for interface, ignoring: %m"); + + r = link_set_ipv6_accept_ra(link); + if (r < 0) + log_link_warning_errno(link, r, "Cannot disable kernel IPv6 accept_ra for interface, ignoring: %m"); + + r = link_set_ipv6_dad_transmits(link); + if (r < 0) + log_link_warning_errno(link, r, "Cannot set IPv6 dad transmits for interface, ignoring: %m"); + + r = link_set_ipv6_hop_limit(link); + if (r < 0) + log_link_warning_errno(link, r, "Cannot set IPv6 hop limit for interface, ignoring: %m"); + + r = link_set_ipv4_accept_local(link); + if (r < 0) + log_link_warning_errno(link, r, "Cannot set IPv4 accept_local flag for interface, ignoring: %m"); + + return 0; +} + +int link_set_ipv6_mtu(Link *link) { + int r; + + assert(link); + + /* Make this a NOP if IPv6 is not available */ + if (!socket_ipv6_is_supported()) + return 0; + + if (link->flags & IFF_LOOPBACK) + return 0; + + if (link->network->ipv6_mtu == 0) + return 0; + + r = sysctl_write_ip_property_uint32(AF_INET6, link->ifname, "mtu", link->network->ipv6_mtu); + if (r < 0) + return r; + + link->ipv6_mtu_set = true; + + return 0; +} + +static const char* const ipv6_privacy_extensions_table[_IPV6_PRIVACY_EXTENSIONS_MAX] = { + [IPV6_PRIVACY_EXTENSIONS_NO] = "no", + [IPV6_PRIVACY_EXTENSIONS_PREFER_PUBLIC] = "prefer-public", + [IPV6_PRIVACY_EXTENSIONS_YES] = "yes", +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(ipv6_privacy_extensions, IPv6PrivacyExtensions, + IPV6_PRIVACY_EXTENSIONS_YES); + +int config_parse_ipv6_privacy_extensions( + const char* unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + IPv6PrivacyExtensions s, *ipv6_privacy_extensions = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(ipv6_privacy_extensions); + + s = ipv6_privacy_extensions_from_string(rvalue); + if (s < 0) { + if (streq(rvalue, "kernel")) + s = _IPV6_PRIVACY_EXTENSIONS_INVALID; + else { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Failed to parse IPv6 privacy extensions option, ignoring: %s", rvalue); + return 0; + } + } + + *ipv6_privacy_extensions = s; + + return 0; +} diff --git a/src/network/networkd-sysctl.h b/src/network/networkd-sysctl.h new file mode 100644 index 0000000..3568900 --- /dev/null +++ b/src/network/networkd-sysctl.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <stdbool.h> + +#include "conf-parser.h" + +typedef struct Link Link; + +typedef enum IPv6PrivacyExtensions { + /* The values map to the kernel's /proc/sys/net/ipv6/conf/xxx/use_tempaddr values */ + IPV6_PRIVACY_EXTENSIONS_NO, + IPV6_PRIVACY_EXTENSIONS_PREFER_PUBLIC, + IPV6_PRIVACY_EXTENSIONS_YES, /* aka prefer-temporary */ + _IPV6_PRIVACY_EXTENSIONS_MAX, + _IPV6_PRIVACY_EXTENSIONS_INVALID = -1, +} IPv6PrivacyExtensions; + +int link_set_sysctl(Link *link); +int link_set_ipv6_mtu(Link *link); + +const char* ipv6_privacy_extensions_to_string(IPv6PrivacyExtensions i) _const_; +IPv6PrivacyExtensions ipv6_privacy_extensions_from_string(const char *s) _pure_; + +CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_privacy_extensions); diff --git a/src/network/networkd-util.c b/src/network/networkd-util.c new file mode 100644 index 0000000..8ddcbb2 --- /dev/null +++ b/src/network/networkd-util.c @@ -0,0 +1,165 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "condition.h" +#include "conf-parser.h" +#include "networkd-util.h" +#include "parse-util.h" +#include "string-table.h" +#include "string-util.h" +#include "util.h" + +static const char* const address_family_table[_ADDRESS_FAMILY_MAX] = { + [ADDRESS_FAMILY_NO] = "no", + [ADDRESS_FAMILY_YES] = "yes", + [ADDRESS_FAMILY_IPV4] = "ipv4", + [ADDRESS_FAMILY_IPV6] = "ipv6", +}; + +static const char* const link_local_address_family_table[_ADDRESS_FAMILY_MAX] = { + [ADDRESS_FAMILY_NO] = "no", + [ADDRESS_FAMILY_YES] = "yes", + [ADDRESS_FAMILY_IPV4] = "ipv4", + [ADDRESS_FAMILY_IPV6] = "ipv6", + [ADDRESS_FAMILY_FALLBACK] = "fallback", + [ADDRESS_FAMILY_FALLBACK_IPV4] = "ipv4-fallback", +}; + +static const char* const routing_policy_rule_address_family_table[_ADDRESS_FAMILY_MAX] = { + [ADDRESS_FAMILY_YES] = "both", + [ADDRESS_FAMILY_IPV4] = "ipv4", + [ADDRESS_FAMILY_IPV6] = "ipv6", +}; + +static const char* const duplicate_address_detection_address_family_table[_ADDRESS_FAMILY_MAX] = { + [ADDRESS_FAMILY_NO] = "none", + [ADDRESS_FAMILY_YES] = "both", + [ADDRESS_FAMILY_IPV4] = "ipv4", + [ADDRESS_FAMILY_IPV6] = "ipv6", +}; + +static const char* const dhcp_lease_server_type_table[_SD_DHCP_LEASE_SERVER_TYPE_MAX] = { + [SD_DHCP_LEASE_DNS] = "DNS servers", + [SD_DHCP_LEASE_NTP] = "NTP servers", + [SD_DHCP_LEASE_SIP] = "SIP servers", + [SD_DHCP_LEASE_POP3] = "POP3 servers", + [SD_DHCP_LEASE_SMTP] = "SMTP servers", + [SD_DHCP_LEASE_LPR] = "LPR servers", +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(address_family, AddressFamily, ADDRESS_FAMILY_YES); +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(link_local_address_family, AddressFamily, ADDRESS_FAMILY_YES); +DEFINE_STRING_TABLE_LOOKUP(routing_policy_rule_address_family, AddressFamily); +DEFINE_STRING_TABLE_LOOKUP(duplicate_address_detection_address_family, AddressFamily); +DEFINE_CONFIG_PARSE_ENUM(config_parse_link_local_address_family, link_local_address_family, + AddressFamily, "Failed to parse option"); +DEFINE_STRING_TABLE_LOOKUP(dhcp_lease_server_type, sd_dhcp_lease_server_type); + +int config_parse_address_family_with_kernel( + const char* unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + AddressFamily *fwd = data, s; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + /* This function is mostly obsolete now. It simply redirects + * "kernel" to "no". In older networkd versions we used to + * distinguish IPForward=off from IPForward=kernel, where the + * former would explicitly turn off forwarding while the + * latter would simply not touch the setting. But that logic + * is gone, hence silently accept the old setting, but turn it + * to "no". */ + + s = address_family_from_string(rvalue); + if (s < 0) { + if (streq(rvalue, "kernel")) + s = ADDRESS_FAMILY_NO; + else { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Failed to parse IPForward= option, ignoring: %s", rvalue); + return 0; + } + } + + *fwd = s; + + return 0; +} + +/* Router lifetime can be set with netlink interface since kernel >= 4.5 + * so for the supported kernel we don't need to expire routes in userspace */ +int kernel_route_expiration_supported(void) { + static int cached = -1; + int r; + + if (cached < 0) { + Condition c = { + .type = CONDITION_KERNEL_VERSION, + .parameter = (char *) ">= 4.5" + }; + r = condition_test(&c, NULL); + if (r < 0) + return r; + + cached = r; + } + return cached; +} + +static void network_config_hash_func(const NetworkConfigSection *c, struct siphash *state) { + siphash24_compress_string(c->filename, state); + siphash24_compress(&c->line, sizeof(c->line), state); +} + +static int network_config_compare_func(const NetworkConfigSection *x, const NetworkConfigSection *y) { + int r; + + r = strcmp(x->filename, y->filename); + if (r != 0) + return r; + + return CMP(x->line, y->line); +} + +DEFINE_HASH_OPS(network_config_hash_ops, NetworkConfigSection, network_config_hash_func, network_config_compare_func); + +int network_config_section_new(const char *filename, unsigned line, NetworkConfigSection **s) { + NetworkConfigSection *cs; + + cs = malloc0(offsetof(NetworkConfigSection, filename) + strlen(filename) + 1); + if (!cs) + return -ENOMEM; + + strcpy(cs->filename, filename); + cs->line = line; + + *s = TAKE_PTR(cs); + + return 0; +} + +void network_config_section_free(NetworkConfigSection *cs) { + free(cs); +} + +unsigned hashmap_find_free_section_line(Hashmap *hashmap) { + NetworkConfigSection *cs; + unsigned n = 0; + void *entry; + + HASHMAP_FOREACH_KEY(entry, cs, hashmap) + if (n < cs->line) + n = cs->line; + + return n + 1; +} diff --git a/src/network/networkd-util.h b/src/network/networkd-util.h new file mode 100644 index 0000000..6100a00 --- /dev/null +++ b/src/network/networkd-util.h @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-dhcp-lease.h" +#include "sd-netlink.h" + +#include "conf-parser.h" +#include "hashmap.h" +#include "log.h" +#include "macro.h" +#include "string-util.h" + +typedef enum AddressFamily { + /* This is a bitmask, though it usually doesn't feel that way! */ + ADDRESS_FAMILY_NO = 0, + ADDRESS_FAMILY_IPV4 = 1 << 0, + ADDRESS_FAMILY_IPV6 = 1 << 1, + ADDRESS_FAMILY_YES = ADDRESS_FAMILY_IPV4 | ADDRESS_FAMILY_IPV6, + ADDRESS_FAMILY_FALLBACK_IPV4 = 1 << 2, + ADDRESS_FAMILY_FALLBACK = ADDRESS_FAMILY_FALLBACK_IPV4 | ADDRESS_FAMILY_IPV6, + _ADDRESS_FAMILY_MAX, + _ADDRESS_FAMILY_INVALID = -1, +} AddressFamily; + +typedef struct NetworkConfigSection { + unsigned line; + bool invalid; + char filename[]; +} NetworkConfigSection; + +CONFIG_PARSER_PROTOTYPE(config_parse_link_local_address_family); +CONFIG_PARSER_PROTOTYPE(config_parse_address_family_with_kernel); + +const char *address_family_to_string(AddressFamily b) _const_; +AddressFamily address_family_from_string(const char *s) _pure_; + +const char *link_local_address_family_to_string(AddressFamily b) _const_; +AddressFamily link_local_address_family_from_string(const char *s) _pure_; + +const char *routing_policy_rule_address_family_to_string(AddressFamily b) _const_; +AddressFamily routing_policy_rule_address_family_from_string(const char *s) _pure_; + +const char *duplicate_address_detection_address_family_to_string(AddressFamily b) _const_; +AddressFamily duplicate_address_detection_address_family_from_string(const char *s) _pure_; + +const char *dhcp_lease_server_type_to_string(sd_dhcp_lease_server_type t) _const_; +sd_dhcp_lease_server_type dhcp_lease_server_type_from_string(const char *s) _pure_; + +int kernel_route_expiration_supported(void); + +int network_config_section_new(const char *filename, unsigned line, NetworkConfigSection **s); +void network_config_section_free(NetworkConfigSection *network); +DEFINE_TRIVIAL_CLEANUP_FUNC(NetworkConfigSection*, network_config_section_free); +extern const struct hash_ops network_config_hash_ops; +unsigned hashmap_find_free_section_line(Hashmap *hashmap); + +static inline bool section_is_invalid(NetworkConfigSection *section) { + /* If this returns false, then it does _not_ mean the section is valid. */ + + if (!section) + return false; + + return section->invalid; +} + +#define DEFINE_NETWORK_SECTION_FUNCTIONS(type, free_func) \ + static inline void free_func##_or_set_invalid(type *p) { \ + assert(p); \ + \ + if (p->section) \ + p->section->invalid = true; \ + else \ + free_func(p); \ + } \ + DEFINE_TRIVIAL_CLEANUP_FUNC(type*, free_func); \ + DEFINE_TRIVIAL_CLEANUP_FUNC(type*, free_func##_or_set_invalid); + +static inline int log_message_warning_errno(sd_netlink_message *m, int err, const char *msg) { + const char *err_msg = NULL; + + (void) sd_netlink_message_read_string(m, NLMSGERR_ATTR_MSG, &err_msg); + return log_warning_errno(err, "%s: %s%s%m", msg, strempty(err_msg), err_msg ? " " : ""); +} diff --git a/src/network/networkd-wifi.c b/src/network/networkd-wifi.c new file mode 100644 index 0000000..0f2def7 --- /dev/null +++ b/src/network/networkd-wifi.c @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <net/ethernet.h> +#include <linux/nl80211.h> + +#include "sd-bus.h" + +#include "bus-util.h" +#include "ether-addr-util.h" +#include "netlink-internal.h" +#include "netlink-util.h" +#include "networkd-link.h" +#include "networkd-manager.h" +#include "networkd-wifi.h" +#include "string-util.h" +#include "wifi-util.h" + +int wifi_get_info(Link *link) { + const char *type; + int r, s = 0; + + assert(link); + + if (!link->sd_device) + return 0; + + r = sd_device_get_devtype(link->sd_device, &type); + if (r == -ENOENT) + return 0; + else if (r < 0) + return r; + + if (!streq(type, "wlan")) + return 0; + + _cleanup_free_ char *ssid = NULL; + r = wifi_get_interface(link->manager->genl, link->ifindex, &link->wlan_iftype, &ssid); + if (r < 0) + return r; + if (r > 0 && streq_ptr(link->ssid, ssid)) + r = 0; + free_and_replace(link->ssid, ssid); + + if (link->wlan_iftype == NL80211_IFTYPE_STATION) { + struct ether_addr old_bssid = link->bssid; + s = wifi_get_station(link->manager->genl, link->ifindex, &link->bssid); + if (s < 0) + return s; + if (s > 0 && memcmp(&old_bssid, &link->bssid, sizeof old_bssid) == 0) + s = 0; + } + + if (r > 0 || s > 0) { + char buf[ETHER_ADDR_TO_STRING_MAX]; + + if (link->wlan_iftype == NL80211_IFTYPE_STATION && link->ssid) + log_link_info(link, "Connected WiFi access point: %s (%s)", + link->ssid, ether_addr_to_string(&link->bssid, buf)); + return 1; + } + return 0; +} diff --git a/src/network/networkd-wifi.h b/src/network/networkd-wifi.h new file mode 100644 index 0000000..ab868eb --- /dev/null +++ b/src/network/networkd-wifi.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-bus.h" + +typedef struct Link Link; + +int wifi_get_info(Link *link); diff --git a/src/network/networkd.c b/src/network/networkd.c new file mode 100644 index 0000000..b448d9b --- /dev/null +++ b/src/network/networkd.c @@ -0,0 +1,110 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <netinet/in.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "sd-daemon.h" +#include "sd-event.h" + +#include "capability-util.h" +#include "daemon-util.h" +#include "main-func.h" +#include "mkdir.h" +#include "networkd-conf.h" +#include "networkd-manager.h" +#include "signal-util.h" +#include "user-util.h" + +static int run(int argc, char *argv[]) { + _cleanup_(manager_freep) Manager *m = NULL; + _cleanup_(notify_on_cleanup) const char *notify_message = NULL; + int r; + + log_setup_service(); + + umask(0022); + + if (argc != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); + + /* Drop privileges, but only if we have been started as root. If we are not running as root we assume all + * privileges are already dropped and we can't create our runtime directory. */ + if (geteuid() == 0) { + const char *user = "systemd-network"; + uid_t uid; + gid_t gid; + + r = get_user_creds(&user, &uid, &gid, NULL, NULL, 0); + if (r < 0) + return log_error_errno(r, "Cannot resolve user name %s: %m", user); + + /* Create runtime directory. This is not necessary when networkd is + * started with "RuntimeDirectory=systemd/netif", or after + * systemd-tmpfiles-setup.service. */ + r = mkdir_safe_label("/run/systemd/netif", 0755, uid, gid, MKDIR_WARN_MODE); + if (r < 0) + log_warning_errno(r, "Could not create runtime directory: %m"); + + r = drop_privileges(uid, gid, + (1ULL << CAP_NET_ADMIN) | + (1ULL << CAP_NET_BIND_SERVICE) | + (1ULL << CAP_NET_BROADCAST) | + (1ULL << CAP_NET_RAW)); + if (r < 0) + return log_error_errno(r, "Failed to drop privileges: %m"); + } + + /* Always create the directories people can create inotify watches in. + * It is necessary to create the following subdirectories after drop_privileges() + * to support old kernels not supporting AmbientCapabilities=. */ + r = mkdir_safe_label("/run/systemd/netif/links", 0755, UID_INVALID, GID_INVALID, MKDIR_WARN_MODE); + if (r < 0) + log_warning_errno(r, "Could not create runtime directory 'links': %m"); + + r = mkdir_safe_label("/run/systemd/netif/leases", 0755, UID_INVALID, GID_INVALID, MKDIR_WARN_MODE); + if (r < 0) + log_warning_errno(r, "Could not create runtime directory 'leases': %m"); + + r = mkdir_safe_label("/run/systemd/netif/lldp", 0755, UID_INVALID, GID_INVALID, MKDIR_WARN_MODE); + if (r < 0) + log_warning_errno(r, "Could not create runtime directory 'lldp': %m"); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); + + r = manager_new(&m); + if (r < 0) + return log_error_errno(r, "Could not create manager: %m"); + + r = manager_connect_bus(m); + if (r < 0) + return log_error_errno(r, "Could not connect to bus: %m"); + + r = manager_parse_config_file(m); + if (r < 0) + log_warning_errno(r, "Failed to parse configuration file: %m"); + + r = manager_load_config(m); + if (r < 0) + return log_error_errno(r, "Could not load configuration files: %m"); + + r = manager_enumerate(m); + if (r < 0) + return r; + + r = manager_start(m); + if (r < 0) + return log_error_errno(r, "Could not start manager: %m"); + + log_info("Enumeration completed"); + + notify_message = notify_start(NOTIFY_READY, NOTIFY_STOPPING); + + r = sd_event_loop(m->event); + if (r < 0) + return log_error_errno(r, "Event loop failed: %m"); + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/network/networkd.conf b/src/network/networkd.conf new file mode 100644 index 0000000..5339e5e --- /dev/null +++ b/src/network/networkd.conf @@ -0,0 +1,21 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# Entries in this file show the compile time defaults. +# You can change settings by editing this file. +# Defaults can be restored by simply deleting this file. +# +# See networkd.conf(5) for details + +[Network] +#SpeedMeter=no +#SpeedMeterIntervalSec=10sec +#ManageForeignRoutes=yes + +[DHCP] +#DUIDType=vendor +#DUIDRawData= diff --git a/src/network/org.freedesktop.network1.conf b/src/network/org.freedesktop.network1.conf new file mode 100644 index 0000000..366c630 --- /dev/null +++ b/src/network/org.freedesktop.network1.conf @@ -0,0 +1,27 @@ +<?xml version="1.0"?> <!--*-nxml-*--> +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> + +<!-- + This file is part of systemd. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. +--> + +<busconfig> + + <policy user="systemd-network"> + <allow own="org.freedesktop.network1"/> + <allow send_destination="org.freedesktop.network1"/> + <allow receive_sender="org.freedesktop.network1"/> + </policy> + + <policy context="default"> + <allow send_destination="org.freedesktop.network1"/> + <allow receive_sender="org.freedesktop.network1"/> + </policy> + +</busconfig> diff --git a/src/network/org.freedesktop.network1.policy b/src/network/org.freedesktop.network1.policy new file mode 100644 index 0000000..9e27f72 --- /dev/null +++ b/src/network/org.freedesktop.network1.policy @@ -0,0 +1,186 @@ +<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*--> +<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" + "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd"> + +<!-- + SPDX-License-Identifier: LGPL-2.1-or-later + + This file is part of systemd. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. +--> + +<policyconfig> + + <vendor>The systemd Project</vendor> + <vendor_url>http://www.freedesktop.org/wiki/Software/systemd</vendor_url> + + <action id="org.freedesktop.network1.set-ntp-servers"> + <description gettext-domain="systemd">Set NTP servers</description> + <message gettext-domain="systemd">Authentication is required to set NTP servers.</message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + <annotate key="org.freedesktop.policykit.owner">unix-user:systemd-network</annotate> + </action> + + <action id="org.freedesktop.network1.set-dns-servers"> + <description gettext-domain="systemd">Set DNS servers</description> + <message gettext-domain="systemd">Authentication is required to set DNS servers.</message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + <annotate key="org.freedesktop.policykit.owner">unix-user:systemd-network</annotate> + </action> + + <action id="org.freedesktop.network1.set-domains"> + <description gettext-domain="systemd">Set domains</description> + <message gettext-domain="systemd">Authentication is required to set domains.</message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + <annotate key="org.freedesktop.policykit.owner">unix-user:systemd-network</annotate> + </action> + + <action id="org.freedesktop.network1.set-default-route"> + <description gettext-domain="systemd">Set default route</description> + <message gettext-domain="systemd">Authentication is required to set default route.</message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + <annotate key="org.freedesktop.policykit.owner">unix-user:systemd-network</annotate> + </action> + + <action id="org.freedesktop.network1.set-llmnr"> + <description gettext-domain="systemd">Enable/disable LLMNR</description> + <message gettext-domain="systemd">Authentication is required to enable or disable LLMNR.</message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + <annotate key="org.freedesktop.policykit.owner">unix-user:systemd-network</annotate> + </action> + + <action id="org.freedesktop.network1.set-mdns"> + <description gettext-domain="systemd">Enable/disable multicast DNS</description> + <message gettext-domain="systemd">Authentication is required to enable or disable multicast DNS.</message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + <annotate key="org.freedesktop.policykit.owner">unix-user:systemd-network</annotate> + </action> + + <action id="org.freedesktop.network1.set-dns-over-tls"> + <description gettext-domain="systemd">Enable/disable DNS over TLS</description> + <message gettext-domain="systemd">Authentication is required to enable or disable DNS over TLS.</message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + <annotate key="org.freedesktop.policykit.owner">unix-user:systemd-network</annotate> + </action> + + <action id="org.freedesktop.network1.set-dnssec"> + <description gettext-domain="systemd">Enable/disable DNSSEC</description> + <message gettext-domain="systemd">Authentication is required to enable or disable DNSSEC.</message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + <annotate key="org.freedesktop.policykit.owner">unix-user:systemd-network</annotate> + </action> + + <action id="org.freedesktop.network1.set-dnssec-negative-trust-anchors"> + <description gettext-domain="systemd">Set DNSSEC Negative Trust Anchors</description> + <message gettext-domain="systemd">Authentication is required to set DNSSEC Negative Trust Anchors.</message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + <annotate key="org.freedesktop.policykit.owner">unix-user:systemd-network</annotate> + </action> + + <action id="org.freedesktop.network1.revert-ntp"> + <description gettext-domain="systemd">Revert NTP settings</description> + <message gettext-domain="systemd">Authentication is required to reset NTP settings.</message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + <annotate key="org.freedesktop.policykit.owner">unix-user:systemd-network</annotate> + </action> + + <action id="org.freedesktop.network1.revert-dns"> + <description gettext-domain="systemd">Revert DNS settings</description> + <message gettext-domain="systemd">Authentication is required to reset DNS settings.</message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + <annotate key="org.freedesktop.policykit.owner">unix-user:systemd-network</annotate> + </action> + + <action id="org.freedesktop.network1.forcerenew"> + <description gettext-domain="systemd">DHCP server sends force renew message</description> + <message gettext-domain="systemd">Authentication is required to send force renew message.</message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + <annotate key="org.freedesktop.policykit.owner">unix-user:systemd-network</annotate> + </action> + + <action id="org.freedesktop.network1.renew"> + <description gettext-domain="systemd">Renew dynamic addresses</description> + <message gettext-domain="systemd">Authentication is required to renew dynamic addresses.</message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + <annotate key="org.freedesktop.policykit.owner">unix-user:systemd-network</annotate> + </action> + + <action id="org.freedesktop.network1.reload"> + <description gettext-domain="systemd">Reload network settings</description> + <message gettext-domain="systemd">Authentication is required to reload network settings.</message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + <annotate key="org.freedesktop.policykit.owner">unix-user:systemd-network</annotate> + </action> + + <action id="org.freedesktop.network1.reconfigure"> + <description gettext-domain="systemd">Reconfigure network interface</description> + <message gettext-domain="systemd">Authentication is required to reconfigure network interface.</message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + <annotate key="org.freedesktop.policykit.owner">unix-user:systemd-network</annotate> + </action> + +</policyconfig> diff --git a/src/network/org.freedesktop.network1.service b/src/network/org.freedesktop.network1.service new file mode 100644 index 0000000..ddbf3eb --- /dev/null +++ b/src/network/org.freedesktop.network1.service @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[D-BUS Service] +Name=org.freedesktop.network1 +Exec=/bin/false +User=root +SystemdService=dbus-org.freedesktop.network1.service diff --git a/src/network/systemd-networkd.pkla b/src/network/systemd-networkd.pkla new file mode 100644 index 0000000..4d1bb45 --- /dev/null +++ b/src/network/systemd-networkd.pkla @@ -0,0 +1,4 @@ +[Allow systemd-networkd to set timezone and transient hostname] +Identity=unix-user:systemd-network +Action=org.freedesktop.hostname1.set-hostname;org.freedesktop.hostname1.get-product-uuid;org.freedesktop.timedate1.set-timezone; +ResultAny=yes diff --git a/src/network/systemd-networkd.rules b/src/network/systemd-networkd.rules new file mode 100644 index 0000000..b9077c1 --- /dev/null +++ b/src/network/systemd-networkd.rules @@ -0,0 +1,10 @@ +// Allow systemd-networkd to set timezone, get product UUID, +// and transient hostname +polkit.addRule(function(action, subject) { + if ((action.id == "org.freedesktop.hostname1.set-hostname" || + action.id == "org.freedesktop.hostname1.get-product-uuid" || + action.id == "org.freedesktop.timedate1.set-timezone") && + subject.user == "systemd-network") { + return polkit.Result.YES; + } +}); diff --git a/src/network/tc/cake.c b/src/network/tc/cake.c new file mode 100644 index 0000000..76fb718 --- /dev/null +++ b/src/network/tc/cake.c @@ -0,0 +1,163 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2020 VMware, Inc. */ + +#include <linux/pkt_sched.h> + +#include "alloc-util.h" +#include "cake.h" +#include "conf-parser.h" +#include "netlink-util.h" +#include "parse-util.h" +#include "qdisc.h" +#include "string-util.h" + +static int cake_fill_message(Link *link, QDisc *qdisc, sd_netlink_message *req) { + CommonApplicationsKeptEnhanced *c; + int r; + + assert(link); + assert(qdisc); + assert(req); + + c = CAKE(qdisc); + + r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "cake"); + if (r < 0) + return log_link_error_errno(link, r, "Could not open container TCA_OPTIONS: %m"); + + if (c->bandwidth > 0) { + r = sd_netlink_message_append_u64(req, TCA_CAKE_BASE_RATE64, c->bandwidth); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_CAKE_BASE_RATE64 attribute: %m"); + } + + r = sd_netlink_message_append_s32(req, TCA_CAKE_OVERHEAD, c->overhead); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_CAKE_OVERHEAD attribute: %m"); + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not close container TCA_OPTIONS: %m"); + + return 0; +} + +int config_parse_cake_bandwidth( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + CommonApplicationsKeptEnhanced *c; + Network *network = data; + uint64_t k; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_CAKE, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + c = CAKE(qdisc); + + if (isempty(rvalue)) { + c->bandwidth = 0; + + qdisc = NULL; + return 0; + } + + r = parse_size(rvalue, 1000, &k); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + c->bandwidth = k/8; + qdisc = NULL; + + return 0; +} + +int config_parse_cake_overhead( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + CommonApplicationsKeptEnhanced *c; + Network *network = data; + int32_t v; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_CAKE, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + c = CAKE(qdisc); + + if (isempty(rvalue)) { + c->overhead = 0; + qdisc = NULL; + return 0; + } + + r = safe_atoi32(rvalue, &v); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + if (v < -64 || v > 256) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + c->overhead = v; + qdisc = NULL; + return 0; +} + +const QDiscVTable cake_vtable = { + .object_size = sizeof(CommonApplicationsKeptEnhanced), + .tca_kind = "cake", + .fill_message = cake_fill_message, +}; diff --git a/src/network/tc/cake.h b/src/network/tc/cake.h new file mode 100644 index 0000000..1da28b7 --- /dev/null +++ b/src/network/tc/cake.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2020 VMware, Inc. */ +#pragma once + +#include "conf-parser.h" +#include "qdisc.h" + +typedef struct CommonApplicationsKeptEnhanced { + QDisc meta; + + int overhead; + uint64_t bandwidth; + +} CommonApplicationsKeptEnhanced; + +DEFINE_QDISC_CAST(CAKE, CommonApplicationsKeptEnhanced); +extern const QDiscVTable cake_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_cake_bandwidth); +CONFIG_PARSER_PROTOTYPE(config_parse_cake_overhead); diff --git a/src/network/tc/codel.c b/src/network/tc/codel.c new file mode 100644 index 0000000..807c247 --- /dev/null +++ b/src/network/tc/codel.c @@ -0,0 +1,255 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2019 VMware, Inc. */ + +#include <linux/pkt_sched.h> + +#include "alloc-util.h" +#include "conf-parser.h" +#include "netlink-util.h" +#include "parse-util.h" +#include "qdisc.h" +#include "string-util.h" + +static int controlled_delay_init(QDisc *qdisc) { + ControlledDelay *cd; + + assert(qdisc); + + cd = CODEL(qdisc); + + cd->ce_threshold_usec = USEC_INFINITY; + cd->ecn = -1; + + return 0; +} + +static int controlled_delay_fill_message(Link *link, QDisc *qdisc, sd_netlink_message *req) { + ControlledDelay *cd; + int r; + + assert(link); + assert(qdisc); + assert(req); + + cd = CODEL(qdisc); + + r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "codel"); + if (r < 0) + return log_link_error_errno(link, r, "Could not open container TCA_OPTIONS: %m"); + + if (cd->packet_limit > 0) { + r = sd_netlink_message_append_u32(req, TCA_CODEL_LIMIT, cd->packet_limit); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_CODEL_LIMIT attribute: %m"); + } + + if (cd->interval_usec > 0) { + r = sd_netlink_message_append_u32(req, TCA_CODEL_INTERVAL, cd->interval_usec); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_CODEL_INTERVAL attribute: %m"); + } + + if (cd->target_usec > 0) { + r = sd_netlink_message_append_u32(req, TCA_CODEL_TARGET, cd->target_usec); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_CODEL_TARGET attribute: %m"); + } + + if (cd->ecn >= 0) { + r = sd_netlink_message_append_u32(req, TCA_CODEL_ECN, cd->ecn); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_CODEL_ECN attribute: %m"); + } + + if (cd->ce_threshold_usec != USEC_INFINITY) { + r = sd_netlink_message_append_u32(req, TCA_CODEL_CE_THRESHOLD, cd->ce_threshold_usec); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_CODEL_CE_THRESHOLD attribute: %m"); + } + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not close container TCA_OPTIONS: %m"); + + return 0; +} + +int config_parse_controlled_delay_u32( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + ControlledDelay *cd; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_CODEL, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + cd = CODEL(qdisc); + + if (isempty(rvalue)) { + cd->packet_limit = 0; + + qdisc = NULL; + return 0; + } + + r = safe_atou32(rvalue, &cd->packet_limit); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + qdisc = NULL; + + return 0; +} + +int config_parse_controlled_delay_usec( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + ControlledDelay *cd; + Network *network = data; + usec_t *p; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_CODEL, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + cd = CODEL(qdisc); + + if (streq(lvalue, "TargetSec")) + p = &cd->target_usec; + else if (streq(lvalue, "IntervalSec")) + p = &cd->interval_usec; + else if (streq(lvalue, "CEThresholdSec")) + p = &cd->ce_threshold_usec; + else + assert_not_reached("Invalid lvalue"); + + if (isempty(rvalue)) { + if (streq(lvalue, "CEThresholdSec")) + *p = USEC_INFINITY; + else + *p = 0; + + qdisc = NULL; + return 0; + } + + r = parse_sec(rvalue, p); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + qdisc = NULL; + + return 0; +} + +int config_parse_controlled_delay_bool( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + ControlledDelay *cd; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_CODEL, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + cd = CODEL(qdisc); + + if (isempty(rvalue)) { + cd->ecn = -1; + + qdisc = NULL; + return 0; + } + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + cd->ecn = r; + qdisc = NULL; + + return 0; +} + +const QDiscVTable codel_vtable = { + .object_size = sizeof(ControlledDelay), + .tca_kind = "codel", + .init = controlled_delay_init, + .fill_message = controlled_delay_fill_message, +}; diff --git a/src/network/tc/codel.h b/src/network/tc/codel.h new file mode 100644 index 0000000..4fe5283 --- /dev/null +++ b/src/network/tc/codel.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2019 VMware, Inc. */ +#pragma once + +#include "conf-parser.h" +#include "qdisc.h" +#include "time-util.h" + +typedef struct ControlledDelay { + QDisc meta; + + uint32_t packet_limit; + usec_t interval_usec; + usec_t target_usec; + usec_t ce_threshold_usec; + int ecn; +} ControlledDelay; + +DEFINE_QDISC_CAST(CODEL, ControlledDelay); +extern const QDiscVTable codel_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_controlled_delay_u32); +CONFIG_PARSER_PROTOTYPE(config_parse_controlled_delay_usec); +CONFIG_PARSER_PROTOTYPE(config_parse_controlled_delay_bool); diff --git a/src/network/tc/drr.c b/src/network/tc/drr.c new file mode 100644 index 0000000..86b7f43 --- /dev/null +++ b/src/network/tc/drr.c @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2020 VMware, Inc. */ + +#include <linux/pkt_sched.h> + +#include "alloc-util.h" +#include "conf-parser.h" +#include "drr.h" +#include "netlink-util.h" +#include "parse-util.h" +#include "string-util.h" + +const QDiscVTable drr_vtable = { + .object_size = sizeof(DeficitRoundRobinScheduler), + .tca_kind = "drr", +}; + +static int drr_class_fill_message(Link *link, TClass *tclass, sd_netlink_message *req) { + DeficitRoundRobinSchedulerClass *drr; + int r; + + assert(link); + assert(tclass); + assert(req); + + drr = TCLASS_TO_DRR(tclass); + + r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "drr"); + if (r < 0) + return log_link_error_errno(link, r, "Could not open container TCA_OPTIONS: %m"); + + if (drr->quantum > 0) { + r = sd_netlink_message_append_u32(req, TCA_DRR_QUANTUM, drr->quantum); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_DRR_QUANTUM, attribute: %m"); + } + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not close container TCA_OPTIONS: %m"); + + return 0; +} + +int config_parse_drr_size( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(tclass_free_or_set_invalidp) TClass *tclass = NULL; + DeficitRoundRobinSchedulerClass *drr; + Network *network = data; + uint64_t u; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = tclass_new_static(TCLASS_KIND_DRR, network, filename, section_line, &tclass); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to create traffic control class, ignoring assignment: %m"); + return 0; + } + + drr = TCLASS_TO_DRR(tclass); + + if (isempty(rvalue)) { + drr->quantum = 0; + + tclass = NULL; + return 0; + } + + r = parse_size(rvalue, 1024, &u); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + if (u > UINT32_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + drr->quantum = (uint32_t) u; + + tclass = NULL; + return 0; +} + +const TClassVTable drr_tclass_vtable = { + .object_size = sizeof(DeficitRoundRobinSchedulerClass), + .tca_kind = "drr", + .fill_message = drr_class_fill_message, +}; diff --git a/src/network/tc/drr.h b/src/network/tc/drr.h new file mode 100644 index 0000000..c96cc4d --- /dev/null +++ b/src/network/tc/drr.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2020 VMware, Inc. */ +#pragma once + +#include "qdisc.h" + +typedef struct DeficitRoundRobinScheduler { + QDisc meta; +} DeficitRoundRobinScheduler; + +DEFINE_QDISC_CAST(DRR, DeficitRoundRobinScheduler); +extern const QDiscVTable drr_vtable; + +typedef struct DeficitRoundRobinSchedulerClass { + TClass meta; + + uint32_t quantum; +} DeficitRoundRobinSchedulerClass; + +DEFINE_TCLASS_CAST(DRR, DeficitRoundRobinSchedulerClass); +extern const TClassVTable drr_tclass_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_drr_size); diff --git a/src/network/tc/ets.c b/src/network/tc/ets.c new file mode 100644 index 0000000..8214a57 --- /dev/null +++ b/src/network/tc/ets.c @@ -0,0 +1,344 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <linux/pkt_sched.h> + +#include "alloc-util.h" +#include "conf-parser.h" +#include "ets.h" +#include "memory-util.h" +#include "netlink-util.h" +#include "parse-util.h" +#include "qdisc.h" +#include "string-util.h" +#include "tc-util.h" + +static int enhanced_transmission_selection_fill_message(Link *link, QDisc *qdisc, sd_netlink_message *req) { + EnhancedTransmissionSelection *ets; + int r; + + assert(link); + assert(qdisc); + assert(req); + + ets = ETS(qdisc); + + r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "ets"); + if (r < 0) + return log_link_error_errno(link, r, "Could not open container TCA_OPTIONS: %m"); + + r = sd_netlink_message_append_u8(req, TCA_ETS_NBANDS, ets->n_bands); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_ETS_NBANDS attribute: %m"); + + if (ets->n_strict > 0) { + r = sd_netlink_message_append_u8(req, TCA_ETS_NSTRICT, ets->n_strict); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_ETS_NSTRICT attribute: %m"); + } + + if (ets->n_quanta > 0) { + r = sd_netlink_message_open_container(req, TCA_ETS_QUANTA); + if (r < 0) + return log_link_error_errno(link, r, "Could not open container TCA_ETS_QUANTA: %m"); + + for (unsigned i = 0; i < ets->n_quanta; i++) { + r = sd_netlink_message_append_u32(req, TCA_ETS_QUANTA_BAND, ets->quanta[i]); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_ETS_QUANTA_BAND attribute: %m"); + } + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not close container TCA_ETS_QUANTA: %m"); + } + + if (ets->n_prio > 0) { + r = sd_netlink_message_open_container(req, TCA_ETS_PRIOMAP); + if (r < 0) + return log_link_error_errno(link, r, "Could not open container TCA_ETS_PRIOMAP: %m"); + + for (unsigned i = 0; i < ets->n_prio; i++) { + r = sd_netlink_message_append_u8(req, TCA_ETS_PRIOMAP_BAND, ets->prio[i]); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_ETS_PRIOMAP_BAND attribute: %m"); + } + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not close container TCA_ETS_PRIOMAP: %m"); + } + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not close container TCA_OPTIONS: %m"); + + return 0; +} + +int config_parse_ets_u8( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + EnhancedTransmissionSelection *ets; + Network *network = data; + uint8_t v, *p; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_ETS, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + ets = ETS(qdisc); + if (streq(lvalue, "Bands")) + p = &ets->n_bands; + else if (streq(lvalue, "StrictBands")) + p = &ets->n_strict; + else + assert_not_reached("Invalid lvalue."); + + if (isempty(rvalue)) { + *p = 0; + + qdisc = NULL; + return 0; + } + + r = safe_atou8(rvalue, &v); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + if (v > TCQ_ETS_MAX_BANDS) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid '%s='. The value must be <= %d, ignoring assignment: %s", + lvalue, TCQ_ETS_MAX_BANDS, rvalue); + return 0; + } + + *p = v; + qdisc = NULL; + + return 0; +} + +int config_parse_ets_quanta( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + EnhancedTransmissionSelection *ets; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_ETS, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + ets = ETS(qdisc); + + if (isempty(rvalue)) { + memzero(ets->quanta, sizeof(uint32_t) * TCQ_ETS_MAX_BANDS); + ets->n_quanta = 0; + + qdisc = NULL; + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *word = NULL; + uint64_t v; + + r = extract_first_word(&p, &word, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to extract next value, ignoring: %m"); + break; + } + if (r == 0) + break; + + r = parse_size(word, 1024, &v); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, word); + continue; + } + if (v == 0 || v > UINT32_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid '%s=', ignoring assignment: %s", + lvalue, word); + continue; + } + if (ets->n_quanta >= TCQ_ETS_MAX_BANDS) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Too many quanta in '%s=', ignoring assignment: %s", + lvalue, word); + continue; + } + + ets->quanta[ets->n_quanta++] = v; + } + + qdisc = NULL; + + return 0; +} + +int config_parse_ets_prio( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + EnhancedTransmissionSelection *ets; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_ETS, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + ets = ETS(qdisc); + + if (isempty(rvalue)) { + memzero(ets->prio, sizeof(uint8_t) * (TC_PRIO_MAX + 1)); + ets->n_prio = 0; + + qdisc = NULL; + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *word = NULL; + uint8_t v; + + r = extract_first_word(&p, &word, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to extract next value, ignoring: %m"); + break; + } + if (r == 0) + break; + + r = safe_atou8(word, &v); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, word); + continue; + } + if (ets->n_prio > TC_PRIO_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Too many priomap in '%s=', ignoring assignment: %s", + lvalue, word); + continue; + } + + ets->prio[ets->n_prio++] = v; + } + + qdisc = NULL; + + return 0; +} + +static int enhanced_transmission_selection_verify(QDisc *qdisc) { + EnhancedTransmissionSelection *ets; + + assert(qdisc); + + ets = ETS(qdisc); + + if (ets->n_bands == 0) + ets->n_bands = ets->n_strict + ets->n_quanta; + + if (ets->n_bands == 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: At least one of Band=, Strict=, or Quanta= must be specified. " + "Ignoring [EnhancedTransmissionSelection] section from line %u.", + qdisc->section->filename, qdisc->section->line); + + if (ets->n_bands < ets->n_strict + ets->n_quanta) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Not enough total bands to cover all the strict bands and quanta. " + "Ignoring [EnhancedTransmissionSelection] section from line %u.", + qdisc->section->filename, qdisc->section->line); + + for (unsigned i = 0; i < ets->n_prio; i++) + if (ets->prio[i] >= ets->n_bands) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: PriorityMap= element is out of bands. " + "Ignoring [EnhancedTransmissionSelection] section from line %u.", + qdisc->section->filename, qdisc->section->line); + + return 0; +} + +const QDiscVTable ets_vtable = { + .object_size = sizeof(EnhancedTransmissionSelection), + .tca_kind = "ets", + .fill_message = enhanced_transmission_selection_fill_message, + .verify = enhanced_transmission_selection_verify, +}; diff --git a/src/network/tc/ets.h b/src/network/tc/ets.h new file mode 100644 index 0000000..b6dd428 --- /dev/null +++ b/src/network/tc/ets.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <linux/pkt_sched.h> + +#include "conf-parser.h" +#include "qdisc.h" + +typedef struct EnhancedTransmissionSelection { + QDisc meta; + + uint8_t n_bands; + uint8_t n_strict; + unsigned n_quanta; + uint32_t quanta[TCQ_ETS_MAX_BANDS]; + unsigned n_prio; + uint8_t prio[TC_PRIO_MAX + 1]; +} EnhancedTransmissionSelection; + +DEFINE_QDISC_CAST(ETS, EnhancedTransmissionSelection); +extern const QDiscVTable ets_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_ets_u8); +CONFIG_PARSER_PROTOTYPE(config_parse_ets_quanta); +CONFIG_PARSER_PROTOTYPE(config_parse_ets_prio); diff --git a/src/network/tc/fifo.c b/src/network/tc/fifo.c new file mode 100644 index 0000000..8b1fa6e --- /dev/null +++ b/src/network/tc/fifo.c @@ -0,0 +1,187 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2020 VMware, Inc. */ + +#include <linux/pkt_sched.h> + +#include "alloc-util.h" +#include "conf-parser.h" +#include "fifo.h" +#include "netlink-util.h" +#include "parse-util.h" +#include "string-util.h" + +static int fifo_fill_message(Link *link, QDisc *qdisc, sd_netlink_message *req) { + struct tc_fifo_qopt opt = {}; + FirstInFirstOut *fifo; + int r; + + assert(link); + assert(qdisc); + assert(req); + + switch(qdisc->kind) { + case QDISC_KIND_PFIFO: + fifo = PFIFO(qdisc); + break; + case QDISC_KIND_BFIFO: + fifo = BFIFO(qdisc); + break; + case QDISC_KIND_PFIFO_HEAD_DROP: + fifo = PFIFO_HEAD_DROP(qdisc); + break; + default: + assert_not_reached("Invalid QDisc kind."); + } + + opt.limit = fifo->limit; + + r = sd_netlink_message_append_data(req, TCA_OPTIONS, &opt, sizeof(struct tc_fifo_qopt)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_OPTIONS attribute: %m"); + + return 0; +} + +int config_parse_pfifo_size( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + Network *network = data; + FirstInFirstOut *fifo; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(ltype, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + switch(qdisc->kind) { + case QDISC_KIND_PFIFO: + fifo = PFIFO(qdisc); + break; + case QDISC_KIND_PFIFO_HEAD_DROP: + fifo = PFIFO_HEAD_DROP(qdisc); + break; + default: + assert_not_reached("Invalid QDisc kind."); + } + + if (isempty(rvalue)) { + fifo->limit = 0; + + qdisc = NULL; + return 0; + } + + r = safe_atou32(rvalue, &fifo->limit); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + qdisc = NULL; + return 0; +} + +int config_parse_bfifo_size( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + Network *network = data; + FirstInFirstOut *fifo; + uint64_t u; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_BFIFO, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + fifo = BFIFO(qdisc); + + if (isempty(rvalue)) { + fifo->limit = 0; + + qdisc = NULL; + return 0; + } + + r = parse_size(rvalue, 1024, &u); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + if (u > UINT32_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + fifo->limit = (uint32_t) u; + + qdisc = NULL; + return 0; +} + +const QDiscVTable pfifo_vtable = { + .object_size = sizeof(FirstInFirstOut), + .tca_kind = "pfifo", + .fill_message = fifo_fill_message, +}; + +const QDiscVTable bfifo_vtable = { + .object_size = sizeof(FirstInFirstOut), + .tca_kind = "bfifo", + .fill_message = fifo_fill_message, +}; + +const QDiscVTable pfifo_head_drop_vtable = { + .object_size = sizeof(FirstInFirstOut), + .tca_kind = "pfifo_head_drop", + .fill_message = fifo_fill_message, +}; + +const QDiscVTable pfifo_fast_vtable = { + .object_size = sizeof(FirstInFirstOut), + .tca_kind = "pfifo_fast", +}; diff --git a/src/network/tc/fifo.h b/src/network/tc/fifo.h new file mode 100644 index 0000000..b9bbd09 --- /dev/null +++ b/src/network/tc/fifo.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2020 VMware, Inc. */ +#pragma once + +#include "conf-parser.h" +#include "qdisc.h" + +typedef struct FirstInFirstOut { + QDisc meta; + + uint32_t limit; +} FirstInFirstOut; + +DEFINE_QDISC_CAST(PFIFO, FirstInFirstOut); +DEFINE_QDISC_CAST(BFIFO, FirstInFirstOut); +DEFINE_QDISC_CAST(PFIFO_HEAD_DROP, FirstInFirstOut); +DEFINE_QDISC_CAST(PFIFO_FAST, FirstInFirstOut); + +extern const QDiscVTable pfifo_vtable; +extern const QDiscVTable bfifo_vtable; +extern const QDiscVTable pfifo_head_drop_vtable; +extern const QDiscVTable pfifo_fast_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_pfifo_size); +CONFIG_PARSER_PROTOTYPE(config_parse_bfifo_size); diff --git a/src/network/tc/fq-codel.c b/src/network/tc/fq-codel.c new file mode 100644 index 0000000..958f65a --- /dev/null +++ b/src/network/tc/fq-codel.c @@ -0,0 +1,355 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2019 VMware, Inc. */ + +#include <linux/pkt_sched.h> + +#include "alloc-util.h" +#include "conf-parser.h" +#include "netlink-util.h" +#include "parse-util.h" +#include "qdisc.h" +#include "string-util.h" +#include "strv.h" + +static int fair_queueing_controlled_delay_init(QDisc *qdisc) { + FairQueueingControlledDelay *fqcd; + + assert(qdisc); + + fqcd = FQ_CODEL(qdisc); + + fqcd->memory_limit = UINT32_MAX; + fqcd->ce_threshold_usec = USEC_INFINITY; + fqcd->ecn = -1; + + return 0; +} + +static int fair_queueing_controlled_delay_fill_message(Link *link, QDisc *qdisc, sd_netlink_message *req) { + FairQueueingControlledDelay *fqcd; + int r; + + assert(link); + assert(qdisc); + assert(req); + + fqcd = FQ_CODEL(qdisc); + + r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "fq_codel"); + if (r < 0) + return log_link_error_errno(link, r, "Could not open container TCA_OPTIONS: %m"); + + if (fqcd->packet_limit > 0) { + r = sd_netlink_message_append_u32(req, TCA_FQ_CODEL_LIMIT, fqcd->packet_limit); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_FQ_CODEL_LIMIT attribute: %m"); + } + + if (fqcd->flows > 0) { + r = sd_netlink_message_append_u32(req, TCA_FQ_CODEL_FLOWS, fqcd->flows); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_FQ_CODEL_FLOWS attribute: %m"); + } + + if (fqcd->quantum > 0) { + r = sd_netlink_message_append_u32(req, TCA_FQ_CODEL_QUANTUM, fqcd->quantum); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_FQ_CODEL_QUANTUM attribute: %m"); + } + + if (fqcd->interval_usec > 0) { + r = sd_netlink_message_append_u32(req, TCA_FQ_CODEL_INTERVAL, fqcd->interval_usec); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_FQ_CODEL_INTERVAL attribute: %m"); + } + + if (fqcd->target_usec > 0) { + r = sd_netlink_message_append_u32(req, TCA_FQ_CODEL_TARGET, fqcd->target_usec); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_FQ_CODEL_TARGET attribute: %m"); + } + + if (fqcd->ecn >= 0) { + r = sd_netlink_message_append_u32(req, TCA_FQ_CODEL_ECN, fqcd->ecn); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_FQ_CODEL_ECN attribute: %m"); + } + + if (fqcd->ce_threshold_usec != USEC_INFINITY) { + r = sd_netlink_message_append_u32(req, TCA_FQ_CODEL_CE_THRESHOLD, fqcd->ce_threshold_usec); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_FQ_CODEL_CE_THRESHOLD attribute: %m"); + } + + if (fqcd->memory_limit != UINT32_MAX) { + r = sd_netlink_message_append_u32(req, TCA_FQ_CODEL_MEMORY_LIMIT, fqcd->memory_limit); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_FQ_CODEL_MEMORY_LIMIT attribute: %m"); + } + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not close container TCA_OPTIONS: %m"); + + return 0; +} + +int config_parse_fair_queueing_controlled_delay_u32( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + FairQueueingControlledDelay *fqcd; + Network *network = data; + uint32_t *p; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_FQ_CODEL, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + fqcd = FQ_CODEL(qdisc); + + if (streq(lvalue, "PacketLimit")) + p = &fqcd->packet_limit; + else if (streq(lvalue, "Flows")) + p = &fqcd->flows; + else + assert_not_reached("Invalid lvalue."); + + if (isempty(rvalue)) { + *p = 0; + + qdisc = NULL; + return 0; + } + + r = safe_atou32(rvalue, p); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + qdisc = NULL; + + return 0; +} + +int config_parse_fair_queueing_controlled_delay_usec( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + FairQueueingControlledDelay *fqcd; + Network *network = data; + usec_t *p; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_FQ_CODEL, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + fqcd = FQ_CODEL(qdisc); + + if (streq(lvalue, "TargetSec")) + p = &fqcd->target_usec; + else if (streq(lvalue, "IntervalSec")) + p = &fqcd->interval_usec; + else if (streq(lvalue, "CEThresholdSec")) + p = &fqcd->ce_threshold_usec; + else + assert_not_reached("Invalid lvalue."); + + if (isempty(rvalue)) { + if (streq(lvalue, "CEThresholdSec")) + *p = USEC_INFINITY; + else + *p = 0; + + qdisc = NULL; + return 0; + } + + r = parse_sec(rvalue, p); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + qdisc = NULL; + + return 0; +} + +int config_parse_fair_queueing_controlled_delay_bool( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + FairQueueingControlledDelay *fqcd; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_FQ_CODEL, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + fqcd = FQ_CODEL(qdisc); + + if (isempty(rvalue)) { + fqcd->ecn = -1; + + qdisc = NULL; + return 0; + } + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + fqcd->ecn = r; + qdisc = NULL; + + return 0; +} + +int config_parse_fair_queueing_controlled_delay_size( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + FairQueueingControlledDelay *fqcd; + Network *network = data; + uint64_t sz; + uint32_t *p; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_FQ_CODEL, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + fqcd = FQ_CODEL(qdisc); + + if (STR_IN_SET(lvalue, "MemoryLimitBytes", "MemoryLimit")) + p = &fqcd->memory_limit; + else if (STR_IN_SET(lvalue, "QuantumBytes", "Quantum")) + p = &fqcd->quantum; + else + assert_not_reached("Invalid lvalue."); + + if (isempty(rvalue)) { + if (STR_IN_SET(lvalue, "MemoryLimitBytes", "MemoryLimit")) + *p = UINT32_MAX; + else + *p = 0; + + qdisc = NULL; + return 0; + } + + r = parse_size(rvalue, 1024, &sz); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + if (sz >= UINT32_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Specified '%s=' is too large, ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + *p = sz; + qdisc = NULL; + + return 0; +} + +const QDiscVTable fq_codel_vtable = { + .object_size = sizeof(FairQueueingControlledDelay), + .tca_kind = "fq_codel", + .init = fair_queueing_controlled_delay_init, + .fill_message = fair_queueing_controlled_delay_fill_message, +}; diff --git a/src/network/tc/fq-codel.h b/src/network/tc/fq-codel.h new file mode 100644 index 0000000..2553c59 --- /dev/null +++ b/src/network/tc/fq-codel.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2019 VMware, Inc. */ +#pragma once + +#include "conf-parser.h" +#include "qdisc.h" +#include "time-util.h" + +typedef struct FairQueueingControlledDelay { + QDisc meta; + + uint32_t packet_limit; + uint32_t flows; + uint32_t quantum; + uint32_t memory_limit; + usec_t target_usec; + usec_t interval_usec; + usec_t ce_threshold_usec; + int ecn; +} FairQueueingControlledDelay; + +DEFINE_QDISC_CAST(FQ_CODEL, FairQueueingControlledDelay); +extern const QDiscVTable fq_codel_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_fair_queueing_controlled_delay_u32); +CONFIG_PARSER_PROTOTYPE(config_parse_fair_queueing_controlled_delay_usec); +CONFIG_PARSER_PROTOTYPE(config_parse_fair_queueing_controlled_delay_bool); +CONFIG_PARSER_PROTOTYPE(config_parse_fair_queueing_controlled_delay_size); diff --git a/src/network/tc/fq-pie.c b/src/network/tc/fq-pie.c new file mode 100644 index 0000000..c7d7623 --- /dev/null +++ b/src/network/tc/fq-pie.c @@ -0,0 +1,103 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2020 VMware, Inc. */ + +#include <linux/pkt_sched.h> + +#include "alloc-util.h" +#include "conf-parser.h" +#include "fq-pie.h" +#include "netlink-util.h" +#include "parse-util.h" +#include "string-util.h" + +static int fq_pie_fill_message(Link *link, QDisc *qdisc, sd_netlink_message *req) { + FlowQueuePIE *fq_pie; + int r; + + assert(link); + assert(qdisc); + assert(req); + + fq_pie = FQ_PIE(qdisc); + + r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "fq_pie"); + if (r < 0) + return log_link_error_errno(link, r, "Could not open container TCA_OPTIONS: %m"); + + if (fq_pie->packet_limit > 0) { + r = sd_netlink_message_append_u32(req, TCA_FQ_PIE_LIMIT, fq_pie->packet_limit); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_FQ_PIE_PLIMIT attribute: %m"); + } + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not close container TCA_OPTIONS: %m"); + + return 0; +} + +int config_parse_fq_pie_packet_limit( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + FlowQueuePIE *fq_pie; + Network *network = data; + uint32_t val; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_FQ_PIE, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + return log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + + fq_pie = FQ_PIE(qdisc); + + if (isempty(rvalue)) { + fq_pie->packet_limit = 0; + + qdisc = NULL; + return 0; + } + + r = safe_atou32(rvalue, &val); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + if (val == 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + fq_pie->packet_limit = val; + qdisc = NULL; + + return 0; +} + +const QDiscVTable fq_pie_vtable = { + .object_size = sizeof(FlowQueuePIE), + .tca_kind = "fq_pie", + .fill_message = fq_pie_fill_message, +}; diff --git a/src/network/tc/fq-pie.h b/src/network/tc/fq-pie.h new file mode 100644 index 0000000..51fb626 --- /dev/null +++ b/src/network/tc/fq-pie.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2020 VMware, Inc. */ +#pragma once + +#include "conf-parser.h" +#include "qdisc.h" + +typedef struct FlowQueuePIE { + QDisc meta; + + uint32_t packet_limit; +} FlowQueuePIE; + +DEFINE_QDISC_CAST(FQ_PIE, FlowQueuePIE); +extern const QDiscVTable fq_pie_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_fq_pie_packet_limit); diff --git a/src/network/tc/fq.c b/src/network/tc/fq.c new file mode 100644 index 0000000..d48aea8 --- /dev/null +++ b/src/network/tc/fq.c @@ -0,0 +1,420 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2019 VMware, Inc. */ + +#include <linux/pkt_sched.h> + +#include "alloc-util.h" +#include "conf-parser.h" +#include "fq.h" +#include "netlink-util.h" +#include "parse-util.h" +#include "string-util.h" +#include "strv.h" + +static int fair_queueing_init(QDisc *qdisc) { + FairQueueing *fq; + + assert(qdisc); + + fq = FQ(qdisc); + + fq->pacing = -1; + fq->ce_threshold_usec = USEC_INFINITY; + + return 0; +} + +static int fair_queueing_fill_message(Link *link, QDisc *qdisc, sd_netlink_message *req) { + FairQueueing *fq; + int r; + + assert(link); + assert(qdisc); + assert(req); + + fq = FQ(qdisc); + + r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "fq"); + if (r < 0) + return log_link_error_errno(link, r, "Could not open container TCA_OPTIONS: %m"); + + if (fq->packet_limit > 0) { + r = sd_netlink_message_append_u32(req, TCA_FQ_PLIMIT, fq->packet_limit); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_FQ_PLIMIT attribute: %m"); + } + + if (fq->flow_limit > 0) { + r = sd_netlink_message_append_u32(req, TCA_FQ_FLOW_PLIMIT, fq->flow_limit); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_FQ_FLOW_PLIMIT attribute: %m"); + } + + if (fq->quantum > 0) { + r = sd_netlink_message_append_u32(req, TCA_FQ_QUANTUM, fq->quantum); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_FQ_QUANTUM attribute: %m"); + } + + if (fq->initial_quantum > 0) { + r = sd_netlink_message_append_u32(req, TCA_FQ_INITIAL_QUANTUM, fq->initial_quantum); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_FQ_INITIAL_QUANTUM attribute: %m"); + } + + if (fq->pacing >= 0) { + r = sd_netlink_message_append_u32(req, TCA_FQ_RATE_ENABLE, fq->pacing); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_FQ_RATE_ENABLE attribute: %m"); + } + + if (fq->max_rate > 0) { + r = sd_netlink_message_append_u32(req, TCA_FQ_FLOW_MAX_RATE, fq->max_rate); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_FQ_FLOW_MAX_RATE attribute: %m"); + } + + if (fq->buckets > 0) { + uint32_t l; + + l = log2u(fq->buckets); + r = sd_netlink_message_append_u32(req, TCA_FQ_BUCKETS_LOG, l); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_FQ_BUCKETS_LOG attribute: %m"); + } + + if (fq->orphan_mask > 0) { + r = sd_netlink_message_append_u32(req, TCA_FQ_ORPHAN_MASK, fq->orphan_mask); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_FQ_ORPHAN_MASK attribute: %m"); + } + + if (fq->ce_threshold_usec != USEC_INFINITY) { + r = sd_netlink_message_append_u32(req, TCA_FQ_CE_THRESHOLD, fq->ce_threshold_usec); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_FQ_CE_THRESHOLD attribute: %m"); + } + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not close container TCA_OPTIONS: %m"); + + return 0; +} + +int config_parse_fair_queueing_u32( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + FairQueueing *fq; + Network *network = data; + uint32_t *p; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_FQ, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + fq = FQ(qdisc); + + if (streq(lvalue, "PacketLimit")) + p = &fq->packet_limit; + else if (streq(lvalue, "FlowLimit")) + p = &fq->flow_limit; + else if (streq(lvalue, "Buckets")) + p = &fq->buckets; + else if (streq(lvalue, "OrphanMask")) + p = &fq->orphan_mask; + else + assert_not_reached("Invalid lvalue"); + + if (isempty(rvalue)) { + *p = 0; + + qdisc = NULL; + return 0; + } + + r = safe_atou32(rvalue, p); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + qdisc = NULL; + + return 0; +} + +int config_parse_fair_queueing_size( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + FairQueueing *fq; + Network *network = data; + uint64_t sz; + uint32_t *p; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_FQ, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + fq = FQ(qdisc); + + if (STR_IN_SET(lvalue, "QuantumBytes", "Quantum")) + p = &fq->quantum; + else if (STR_IN_SET(lvalue, "InitialQuantumBytes", "InitialQuantum")) + p = &fq->initial_quantum; + else + assert_not_reached("Invalid lvalue"); + + if (isempty(rvalue)) { + *p = 0; + + qdisc = NULL; + return 0; + } + + r = parse_size(rvalue, 1024, &sz); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + if (sz > UINT32_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Specified '%s=' is too large, ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + *p = sz; + qdisc = NULL; + + return 0; +} + +int config_parse_fair_queueing_bool( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + FairQueueing *fq; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_FQ, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + fq = FQ(qdisc); + + if (isempty(rvalue)) { + fq->pacing = -1; + + qdisc = NULL; + return 0; + } + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + fq->pacing = r; + qdisc = NULL; + + return 0; +} + +int config_parse_fair_queueing_usec( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + FairQueueing *fq; + Network *network = data; + usec_t sec; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_FQ, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + fq = FQ(qdisc); + + if (isempty(rvalue)) { + fq->ce_threshold_usec = USEC_INFINITY; + + qdisc = NULL; + return 0; + } + + r = parse_sec(rvalue, &sec); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + if (sec > UINT32_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Specified '%s=' is too large, ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + fq->ce_threshold_usec = sec; + qdisc = NULL; + + return 0; +} + +int config_parse_fair_queueing_max_rate( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + FairQueueing *fq; + Network *network = data; + uint64_t sz; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_FQ, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + fq = FQ(qdisc); + + if (isempty(rvalue)) { + fq->max_rate = 0; + + qdisc = NULL; + return 0; + } + + r = parse_size(rvalue, 1000, &sz); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + if (sz / 8 > UINT32_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Specified '%s=' is too large, ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + fq->max_rate = sz / 8; + qdisc = NULL; + + return 0; +} + +const QDiscVTable fq_vtable = { + .init = fair_queueing_init, + .object_size = sizeof(FairQueueing), + .tca_kind = "fq", + .fill_message = fair_queueing_fill_message, +}; diff --git a/src/network/tc/fq.h b/src/network/tc/fq.h new file mode 100644 index 0000000..77469c4 --- /dev/null +++ b/src/network/tc/fq.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2019 VMware, Inc. */ +#pragma once + +#include "conf-parser.h" +#include "qdisc.h" + +typedef struct FairQueueing { + QDisc meta; + + uint32_t packet_limit; + uint32_t flow_limit; + uint32_t quantum; + uint32_t initial_quantum; + uint32_t max_rate; + uint32_t buckets; + uint32_t orphan_mask; + int pacing; + usec_t ce_threshold_usec; +} FairQueueing; + +DEFINE_QDISC_CAST(FQ, FairQueueing); +extern const QDiscVTable fq_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_fair_queueing_u32); +CONFIG_PARSER_PROTOTYPE(config_parse_fair_queueing_size); +CONFIG_PARSER_PROTOTYPE(config_parse_fair_queueing_bool); +CONFIG_PARSER_PROTOTYPE(config_parse_fair_queueing_usec); +CONFIG_PARSER_PROTOTYPE(config_parse_fair_queueing_max_rate); diff --git a/src/network/tc/gred.c b/src/network/tc/gred.c new file mode 100644 index 0000000..46a9ead --- /dev/null +++ b/src/network/tc/gred.c @@ -0,0 +1,196 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2020 VMware, Inc. */ + +#include <linux/pkt_sched.h> + +#include "alloc-util.h" +#include "conf-parser.h" +#include "netlink-util.h" +#include "parse-util.h" +#include "qdisc.h" +#include "string-util.h" + +static int generic_random_early_detection_init(QDisc *qdisc) { + GenericRandomEarlyDetection *gred; + + assert(qdisc); + + gred = GRED(qdisc); + + gred->grio = -1; + + return 0; +} + +static int generic_random_early_detection_fill_message(Link *link, QDisc *qdisc, sd_netlink_message *req) { + GenericRandomEarlyDetection *gred; + struct tc_gred_sopt opt = {}; + int r; + + assert(link); + assert(qdisc); + assert(req); + + gred = GRED(qdisc); + + opt.DPs = gred->virtual_queues; + opt.def_DP = gred->default_virtual_queue; + + if (gred->grio >= 0) + opt.grio = gred->grio; + + r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "gred"); + if (r < 0) + return log_link_error_errno(link, r, "Could not open container TCA_OPTIONS: %m"); + + r = sd_netlink_message_append_data(req, TCA_GRED_DPS, &opt, sizeof(struct tc_gred_sopt)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_GRED_DPS attribute: %m"); + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not close container TCA_OPTIONS: %m"); + + return 0; +} + +static int generic_random_early_detection_verify(QDisc *qdisc) { + GenericRandomEarlyDetection *gred = GRED(qdisc); + + if (gred->default_virtual_queue >= gred->virtual_queues) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: DefaultVirtualQueue= must be less than VirtualQueues=. " + "Ignoring [GenericRandomEarlyDetection] section from line %u.", + qdisc->section->filename, qdisc->section->line); + + return 0; +} + +int config_parse_generic_random_early_detection_u32( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + GenericRandomEarlyDetection *gred; + Network *network = data; + uint32_t *p; + uint32_t v; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_GRED, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + gred = GRED(qdisc); + + if (streq(lvalue, "VirtualQueues")) + p = &gred->virtual_queues; + else if (streq(lvalue, "DefaultVirtualQueue")) + p = &gred->default_virtual_queue; + else + assert_not_reached("Invalid lvalue."); + + if (isempty(rvalue)) { + *p = 0; + + qdisc = NULL; + return 0; + } + + r = safe_atou32(rvalue, &v); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + if (v > MAX_DPs) + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid '%s=', ignoring assignment: %s", + lvalue, rvalue); + + *p = v; + qdisc = NULL; + + return 0; +} +int config_parse_generic_random_early_detection_bool( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + GenericRandomEarlyDetection *gred; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_GRED, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + gred = GRED(qdisc); + + if (isempty(rvalue)) { + gred->grio = -1; + + qdisc = NULL; + return 0; + } + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + gred->grio = r; + qdisc = NULL; + + return 0; +} + +const QDiscVTable gred_vtable = { + .object_size = sizeof(GenericRandomEarlyDetection), + .tca_kind = "gred", + .init = generic_random_early_detection_init, + .fill_message = generic_random_early_detection_fill_message, + .verify = generic_random_early_detection_verify, +}; diff --git a/src/network/tc/gred.h b/src/network/tc/gred.h new file mode 100644 index 0000000..c084ff1 --- /dev/null +++ b/src/network/tc/gred.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2020 VMware, Inc. */ +#pragma once + +#include "conf-parser.h" +#include "qdisc.h" + +typedef struct GenericRandomEarlyDetection { + QDisc meta; + + uint32_t virtual_queues; + uint32_t default_virtual_queue; + int grio; +} GenericRandomEarlyDetection; + +DEFINE_QDISC_CAST(GRED, GenericRandomEarlyDetection); +extern const QDiscVTable gred_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_generic_random_early_detection_u32); +CONFIG_PARSER_PROTOTYPE(config_parse_generic_random_early_detection_bool); diff --git a/src/network/tc/hhf.c b/src/network/tc/hhf.c new file mode 100644 index 0000000..69c02f4 --- /dev/null +++ b/src/network/tc/hhf.c @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2020 VMware, Inc. */ + +#include <linux/pkt_sched.h> + +#include "alloc-util.h" +#include "conf-parser.h" +#include "hhf.h" +#include "netlink-util.h" +#include "parse-util.h" +#include "string-util.h" +#include "util.h" + +static int heavy_hitter_filter_fill_message(Link *link, QDisc *qdisc, sd_netlink_message *req) { + HeavyHitterFilter *hhf; + int r; + + assert(link); + assert(qdisc); + assert(req); + + hhf = HHF(qdisc); + + r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "hhf"); + if (r < 0) + return log_link_error_errno(link, r, "Could not open container TCA_OPTIONS: %m"); + + if (hhf->packet_limit > 0) { + r = sd_netlink_message_append_u32(req, TCA_HHF_BACKLOG_LIMIT, hhf->packet_limit); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_HHF_BACKLOG_LIMIT attribute: %m"); + } + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not close container TCA_OPTIONS: %m"); + + return 0; +} + +int config_parse_heavy_hitter_filter_packet_limit( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + HeavyHitterFilter *hhf; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_HHF, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + hhf = HHF(qdisc); + + if (isempty(rvalue)) { + hhf->packet_limit = 0; + + qdisc = NULL; + return 0; + } + + r = safe_atou32(rvalue, &hhf->packet_limit); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + qdisc = NULL; + + return 0; +} + +const QDiscVTable hhf_vtable = { + .object_size = sizeof(HeavyHitterFilter), + .tca_kind = "hhf", + .fill_message = heavy_hitter_filter_fill_message, +}; diff --git a/src/network/tc/hhf.h b/src/network/tc/hhf.h new file mode 100644 index 0000000..04caaa8 --- /dev/null +++ b/src/network/tc/hhf.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2020 VMware, Inc. */ +#pragma once + +#include "conf-parser.h" +#include "qdisc.h" + +typedef struct HeavyHitterFilter { + QDisc meta; + + uint32_t packet_limit; +} HeavyHitterFilter; + +DEFINE_QDISC_CAST(HHF, HeavyHitterFilter); +extern const QDiscVTable hhf_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_heavy_hitter_filter_packet_limit); diff --git a/src/network/tc/htb.c b/src/network/tc/htb.c new file mode 100644 index 0000000..0969587 --- /dev/null +++ b/src/network/tc/htb.c @@ -0,0 +1,489 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <linux/pkt_sched.h> + +#include "alloc-util.h" +#include "conf-parser.h" +#include "netlink-util.h" +#include "parse-util.h" +#include "qdisc.h" +#include "htb.h" +#include "string-util.h" +#include "tc-util.h" + +#define HTB_DEFAULT_RATE_TO_QUANTUM 10 +#define HTB_DEFAULT_MTU 1600 /* Ethernet packet length */ + +static int hierarchy_token_bucket_fill_message(Link *link, QDisc *qdisc, sd_netlink_message *req) { + HierarchyTokenBucket *htb; + struct tc_htb_glob opt = { + .version = 3, + }; + int r; + + assert(link); + assert(qdisc); + assert(req); + + htb = HTB(qdisc); + + opt.rate2quantum = htb->rate_to_quantum; + opt.defcls = htb->default_class; + + r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "htb"); + if (r < 0) + return log_link_error_errno(link, r, "Could not open container TCA_OPTIONS: %m"); + + r = sd_netlink_message_append_data(req, TCA_HTB_INIT, &opt, sizeof(opt)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_HTB_INIT attribute: %m"); + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not close container TCA_OPTIONS: %m"); + return 0; +} + +int config_parse_hierarchy_token_bucket_default_class( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + HierarchyTokenBucket *htb; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_HTB, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + htb = HTB(qdisc); + + if (isempty(rvalue)) { + htb->default_class = 0; + + qdisc = NULL; + return 0; + } + + r = safe_atou32_full(rvalue, 16, &htb->default_class); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + qdisc = NULL; + + return 0; +} + +int config_parse_hierarchy_token_bucket_u32( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + HierarchyTokenBucket *htb; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_HTB, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + htb = HTB(qdisc); + + if (isempty(rvalue)) { + htb->rate_to_quantum = HTB_DEFAULT_RATE_TO_QUANTUM; + + qdisc = NULL; + return 0; + } + + r = safe_atou32(rvalue, &htb->rate_to_quantum); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + qdisc = NULL; + + return 0; +} + +static int hierarchy_token_bucket_init(QDisc *qdisc) { + HierarchyTokenBucket *htb; + + assert(qdisc); + + htb = HTB(qdisc); + + htb->rate_to_quantum = HTB_DEFAULT_RATE_TO_QUANTUM; + + return 0; +} + +const QDiscVTable htb_vtable = { + .object_size = sizeof(HierarchyTokenBucket), + .tca_kind = "htb", + .fill_message = hierarchy_token_bucket_fill_message, + .init = hierarchy_token_bucket_init, +}; + +static int hierarchy_token_bucket_class_fill_message(Link *link, TClass *tclass, sd_netlink_message *req) { + HierarchyTokenBucketClass *htb; + struct tc_htb_opt opt = {}; + uint32_t rtab[256], ctab[256]; + int r; + + assert(link); + assert(tclass); + assert(req); + + htb = TCLASS_TO_HTB(tclass); + + opt.prio = htb->priority; + opt.quantum = htb->quantum; + opt.rate.rate = (htb->rate >= (1ULL << 32)) ? ~0U : htb->rate; + opt.ceil.rate = (htb->ceil_rate >= (1ULL << 32)) ? ~0U : htb->ceil_rate; + opt.rate.overhead = htb->overhead; + opt.ceil.overhead = htb->overhead; + + r = tc_transmit_time(htb->rate, htb->buffer, &opt.buffer); + if (r < 0) + return log_link_error_errno(link, r, "Failed to calculate buffer size: %m"); + + r = tc_transmit_time(htb->ceil_rate, htb->ceil_buffer, &opt.cbuffer); + if (r < 0) + return log_link_error_errno(link, r, "Failed to calculate ceil buffer size: %m"); + + r = tc_fill_ratespec_and_table(&opt.rate, rtab, htb->mtu); + if (r < 0) + return log_link_error_errno(link, r, "Failed to calculate rate table: %m"); + + r = tc_fill_ratespec_and_table(&opt.ceil, ctab, htb->mtu); + if (r < 0) + return log_link_error_errno(link, r, "Failed to calculate ceil rate table: %m"); + + r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "htb"); + if (r < 0) + return log_link_error_errno(link, r, "Could not open container TCA_OPTIONS: %m"); + + r = sd_netlink_message_append_data(req, TCA_HTB_PARMS, &opt, sizeof(opt)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_HTB_PARMS attribute: %m"); + + if (htb->rate >= (1ULL << 32)) { + r = sd_netlink_message_append_u64(req, TCA_HTB_RATE64, htb->rate); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_HTB_RATE64 attribute: %m"); + } + + if (htb->ceil_rate >= (1ULL << 32)) { + r = sd_netlink_message_append_u64(req, TCA_HTB_CEIL64, htb->ceil_rate); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_HTB_CEIL64 attribute: %m"); + } + + r = sd_netlink_message_append_data(req, TCA_HTB_RTAB, rtab, sizeof(rtab)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_HTB_RTAB attribute: %m"); + + r = sd_netlink_message_append_data(req, TCA_HTB_CTAB, ctab, sizeof(ctab)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_HTB_CTAB attribute: %m"); + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not close container TCA_OPTIONS: %m"); + return 0; +} + +int config_parse_hierarchy_token_bucket_class_u32( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(tclass_free_or_set_invalidp) TClass *tclass = NULL; + HierarchyTokenBucketClass *htb; + Network *network = data; + uint32_t v; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = tclass_new_static(TCLASS_KIND_HTB, network, filename, section_line, &tclass); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to create traffic control class, ignoring assignment: %m"); + return 0; + } + + htb = TCLASS_TO_HTB(tclass); + + if (isempty(rvalue)) { + htb->priority = 0; + tclass = NULL; + return 0; + } + + r = safe_atou32(rvalue, &v); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + htb->priority = v; + tclass = NULL; + + return 0; +} + +int config_parse_hierarchy_token_bucket_class_size( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(tclass_free_or_set_invalidp) TClass *tclass = NULL; + HierarchyTokenBucketClass *htb; + Network *network = data; + uint64_t v; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = tclass_new_static(TCLASS_KIND_HTB, network, filename, section_line, &tclass); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to create traffic control class, ignoring assignment: %m"); + return 0; + } + + htb = TCLASS_TO_HTB(tclass); + + if (isempty(rvalue)) { + if (streq(lvalue, "QuantumBytes")) + htb->quantum = 0; + else if (streq(lvalue, "MTUBytes")) + htb->mtu = HTB_DEFAULT_MTU; + else if (streq(lvalue, "OverheadBytes")) + htb->overhead = 0; + else if (streq(lvalue, "BufferBytes")) + htb->buffer = 0; + else if (streq(lvalue, "CeilBufferBytes")) + htb->ceil_buffer = 0; + else + assert_not_reached("Invalid lvalue"); + + tclass = NULL; + return 0; + } + + r = parse_size(rvalue, 1024, &v); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + if ((streq(lvalue, "OverheadBytes") && v > UINT16_MAX) || v > UINT32_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + if (streq(lvalue, "QuantumBytes")) + htb->quantum = v; + else if (streq(lvalue, "OverheadBytes")) + htb->overhead = v; + else if (streq(lvalue, "MTUBytes")) + htb->mtu = v; + else if (streq(lvalue, "BufferBytes")) + htb->buffer = v; + else if (streq(lvalue, "CeilBufferBytes")) + htb->ceil_buffer = v; + else + assert_not_reached("Invalid lvalue"); + + tclass = NULL; + + return 0; +} + +int config_parse_hierarchy_token_bucket_class_rate( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(tclass_free_or_set_invalidp) TClass *tclass = NULL; + HierarchyTokenBucketClass *htb; + Network *network = data; + uint64_t *v; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = tclass_new_static(TCLASS_KIND_HTB, network, filename, section_line, &tclass); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to create traffic control class, ignoring assignment: %m"); + return 0; + } + + htb = TCLASS_TO_HTB(tclass); + if (streq(lvalue, "Rate")) + v = &htb->rate; + else if (streq(lvalue, "CeilRate")) + v = &htb->ceil_rate; + else + assert_not_reached("Invalid lvalue"); + + if (isempty(rvalue)) { + *v = 0; + + tclass = NULL; + return 0; + } + + r = parse_size(rvalue, 1000, v); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + *v /= 8; + tclass = NULL; + + return 0; +} + +static int hierarchy_token_bucket_class_init(TClass *tclass) { + HierarchyTokenBucketClass *htb; + + assert(tclass); + + htb = TCLASS_TO_HTB(tclass); + + htb->mtu = HTB_DEFAULT_MTU; + + return 0; +} + +static int hierarchy_token_bucket_class_verify(TClass *tclass) { + HierarchyTokenBucketClass *htb; + uint32_t hz; + int r; + + assert(tclass); + + htb = TCLASS_TO_HTB(tclass); + + if (htb->rate == 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Rate= is mandatory. " + "Ignoring [HierarchyTokenBucketClass] section from line %u.", + tclass->section->filename, tclass->section->line); + + /* if CeilRate= setting is missing, use the same as Rate= */ + if (htb->ceil_rate == 0) + htb->ceil_rate = htb->rate; + + r = tc_init(NULL, &hz); + if (r < 0) + return log_error_errno(r, "Failed to read /proc/net/psched: %m"); + + if (htb->buffer == 0) + htb->buffer = htb->rate / hz + htb->mtu; + if (htb->ceil_buffer == 0) + htb->ceil_buffer = htb->ceil_rate / hz + htb->mtu; + + return 0; +} + +const TClassVTable htb_tclass_vtable = { + .object_size = sizeof(HierarchyTokenBucketClass), + .tca_kind = "htb", + .fill_message = hierarchy_token_bucket_class_fill_message, + .init = hierarchy_token_bucket_class_init, + .verify = hierarchy_token_bucket_class_verify, +}; diff --git a/src/network/tc/htb.h b/src/network/tc/htb.h new file mode 100644 index 0000000..55644db --- /dev/null +++ b/src/network/tc/htb.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "conf-parser.h" +#include "qdisc.h" +#include "tclass.h" + +typedef struct HierarchyTokenBucket { + QDisc meta; + + uint32_t default_class; + uint32_t rate_to_quantum; +} HierarchyTokenBucket; + +DEFINE_QDISC_CAST(HTB, HierarchyTokenBucket); +extern const QDiscVTable htb_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_hierarchy_token_bucket_default_class); +CONFIG_PARSER_PROTOTYPE(config_parse_hierarchy_token_bucket_u32); + +typedef struct HierarchyTokenBucketClass { + TClass meta; + + uint32_t priority; + uint32_t quantum; + uint32_t mtu; + uint16_t overhead; + uint64_t rate; + uint32_t buffer; + uint64_t ceil_rate; + uint32_t ceil_buffer; +} HierarchyTokenBucketClass; + +DEFINE_TCLASS_CAST(HTB, HierarchyTokenBucketClass); +extern const TClassVTable htb_tclass_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_hierarchy_token_bucket_class_u32); +CONFIG_PARSER_PROTOTYPE(config_parse_hierarchy_token_bucket_class_size); +CONFIG_PARSER_PROTOTYPE(config_parse_hierarchy_token_bucket_class_rate); diff --git a/src/network/tc/netem.c b/src/network/tc/netem.c new file mode 100644 index 0000000..454e556 --- /dev/null +++ b/src/network/tc/netem.c @@ -0,0 +1,236 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2019 VMware, Inc. */ + +#include <linux/pkt_sched.h> + +#include "alloc-util.h" +#include "conf-parser.h" +#include "netem.h" +#include "netlink-util.h" +#include "networkd-manager.h" +#include "parse-util.h" +#include "qdisc.h" +#include "strv.h" +#include "tc-util.h" + +static int network_emulator_fill_message(Link *link, QDisc *qdisc, sd_netlink_message *req) { + struct tc_netem_qopt opt = { + .limit = 1000, + }; + NetworkEmulator *ne; + int r; + + assert(link); + assert(qdisc); + assert(req); + + ne = NETEM(qdisc); + + if (ne->limit > 0) + opt.limit = ne->limit; + + if (ne->loss > 0) + opt.loss = ne->loss; + + if (ne->duplicate > 0) + opt.duplicate = ne->duplicate; + + if (ne->delay != USEC_INFINITY) { + r = tc_time_to_tick(ne->delay, &opt.latency); + if (r < 0) + return log_link_error_errno(link, r, "Failed to calculate latency in TCA_OPTION: %m"); + } + + if (ne->jitter != USEC_INFINITY) { + r = tc_time_to_tick(ne->jitter, &opt.jitter); + if (r < 0) + return log_link_error_errno(link, r, "Failed to calculate jitter in TCA_OPTION: %m"); + } + + r = sd_netlink_message_append_data(req, TCA_OPTIONS, &opt, sizeof(struct tc_netem_qopt)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_OPTION attribute: %m"); + + return 0; +} + +int config_parse_network_emulator_delay( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + Network *network = data; + NetworkEmulator *ne; + usec_t u; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_NETEM, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + ne = NETEM(qdisc); + + if (isempty(rvalue)) { + if (STR_IN_SET(lvalue, "DelaySec", "NetworkEmulatorDelaySec")) + ne->delay = USEC_INFINITY; + else if (STR_IN_SET(lvalue, "DelayJitterSec", "NetworkEmulatorDelayJitterSec")) + ne->jitter = USEC_INFINITY; + + qdisc = NULL; + return 0; + } + + r = parse_sec(rvalue, &u); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + if (STR_IN_SET(lvalue, "DelaySec", "NetworkEmulatorDelaySec")) + ne->delay = u; + else if (STR_IN_SET(lvalue, "DelayJitterSec", "NetworkEmulatorDelayJitterSec")) + ne->jitter = u; + + qdisc = NULL; + + return 0; +} + +int config_parse_network_emulator_rate( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + Network *network = data; + NetworkEmulator *ne; + uint32_t rate; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_NETEM, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + ne = NETEM(qdisc); + + if (isempty(rvalue)) { + if (STR_IN_SET(lvalue, "LossRate", "NetworkEmulatorLossRate")) + ne->loss = 0; + else if (STR_IN_SET(lvalue, "DuplicateRate", "NetworkEmulatorDuplicateRate")) + ne->duplicate = 0; + + qdisc = NULL; + return 0; + } + + r = parse_tc_percent(rvalue, &rate); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + if (STR_IN_SET(lvalue, "LossRate", "NetworkEmulatorLossRate")) + ne->loss = rate; + else if (STR_IN_SET(lvalue, "DuplicateRate", "NetworkEmulatorDuplicateRate")) + ne->duplicate = rate; + + qdisc = NULL; + return 0; +} + +int config_parse_network_emulator_packet_limit( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + Network *network = data; + NetworkEmulator *ne; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_NETEM, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + ne = NETEM(qdisc); + + if (isempty(rvalue)) { + ne->limit = 0; + qdisc = NULL; + + return 0; + } + + r = safe_atou(rvalue, &ne->limit); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + qdisc = NULL; + return 0; +} + +const QDiscVTable netem_vtable = { + .object_size = sizeof(NetworkEmulator), + .tca_kind = "netem", + .fill_message = network_emulator_fill_message, +}; diff --git a/src/network/tc/netem.h b/src/network/tc/netem.h new file mode 100644 index 0000000..d58d5ac --- /dev/null +++ b/src/network/tc/netem.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2019 VMware, Inc. */ +#pragma once + +#include "conf-parser.h" +#include "qdisc.h" +#include "time-util.h" + +typedef struct NetworkEmulator { + QDisc meta; + + usec_t delay; + usec_t jitter; + + uint32_t limit; + uint32_t loss; + uint32_t duplicate; +} NetworkEmulator; + +DEFINE_QDISC_CAST(NETEM, NetworkEmulator); +extern const QDiscVTable netem_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_network_emulator_delay); +CONFIG_PARSER_PROTOTYPE(config_parse_network_emulator_rate); +CONFIG_PARSER_PROTOTYPE(config_parse_network_emulator_packet_limit); diff --git a/src/network/tc/pie.c b/src/network/tc/pie.c new file mode 100644 index 0000000..695a381 --- /dev/null +++ b/src/network/tc/pie.c @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2020 VMware, Inc. */ + +#include <linux/pkt_sched.h> + +#include "alloc-util.h" +#include "conf-parser.h" +#include "pie.h" +#include "netlink-util.h" +#include "parse-util.h" +#include "string-util.h" + +static int pie_fill_message(Link *link, QDisc *qdisc, sd_netlink_message *req) { + ProportionalIntegralControllerEnhanced *pie; + int r; + + assert(link); + assert(qdisc); + assert(req); + + pie = PIE(qdisc); + + r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "pie"); + if (r < 0) + return log_link_error_errno(link, r, "Could not open container TCA_OPTIONS: %m"); + + if (pie->packet_limit > 0) { + r = sd_netlink_message_append_u32(req, TCA_PIE_LIMIT, pie->packet_limit); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_PIE_PLIMIT attribute: %m"); + } + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not close container TCA_OPTIONS: %m"); + + return 0; +} + +int config_parse_pie_packet_limit( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + ProportionalIntegralControllerEnhanced *pie; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_PIE, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + pie = PIE(qdisc); + + if (isempty(rvalue)) { + pie->packet_limit = 0; + + qdisc = NULL; + return 0; + } + + r = safe_atou32(rvalue, &pie->packet_limit); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + qdisc = NULL; + + return 0; +} + +const QDiscVTable pie_vtable = { + .object_size = sizeof(ProportionalIntegralControllerEnhanced), + .tca_kind = "pie", + .fill_message = pie_fill_message, +}; diff --git a/src/network/tc/pie.h b/src/network/tc/pie.h new file mode 100644 index 0000000..40a114e --- /dev/null +++ b/src/network/tc/pie.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2020 VMware, Inc. */ +#pragma once + +#include "conf-parser.h" +#include "qdisc.h" + +typedef struct ProportionalIntegralControllerEnhanced { + QDisc meta; + + uint32_t packet_limit; +} ProportionalIntegralControllerEnhanced; + +DEFINE_QDISC_CAST(PIE, ProportionalIntegralControllerEnhanced); +extern const QDiscVTable pie_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_pie_packet_limit); diff --git a/src/network/tc/qdisc.c b/src/network/tc/qdisc.c new file mode 100644 index 0000000..2add128 --- /dev/null +++ b/src/network/tc/qdisc.c @@ -0,0 +1,381 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2019 VMware, Inc. */ + +#include <linux/pkt_sched.h> + +#include "alloc-util.h" +#include "conf-parser.h" +#include "in-addr-util.h" +#include "netlink-util.h" +#include "networkd-manager.h" +#include "parse-util.h" +#include "qdisc.h" +#include "set.h" +#include "string-util.h" +#include "strv.h" +#include "tc-util.h" + +const QDiscVTable * const qdisc_vtable[_QDISC_KIND_MAX] = { + [QDISC_KIND_BFIFO] = &bfifo_vtable, + [QDISC_KIND_CAKE] = &cake_vtable, + [QDISC_KIND_CODEL] = &codel_vtable, + [QDISC_KIND_DRR] = &drr_vtable, + [QDISC_KIND_ETS] = &ets_vtable, + [QDISC_KIND_FQ] = &fq_vtable, + [QDISC_KIND_FQ_CODEL] = &fq_codel_vtable, + [QDISC_KIND_FQ_PIE] = &fq_pie_vtable, + [QDISC_KIND_GRED] = &gred_vtable, + [QDISC_KIND_HHF] = &hhf_vtable, + [QDISC_KIND_HTB] = &htb_vtable, + [QDISC_KIND_NETEM] = &netem_vtable, + [QDISC_KIND_PIE] = &pie_vtable, + [QDISC_KIND_QFQ] = &qfq_vtable, + [QDISC_KIND_PFIFO] = &pfifo_vtable, + [QDISC_KIND_PFIFO_FAST] = &pfifo_fast_vtable, + [QDISC_KIND_PFIFO_HEAD_DROP] = &pfifo_head_drop_vtable, + [QDISC_KIND_SFB] = &sfb_vtable, + [QDISC_KIND_SFQ] = &sfq_vtable, + [QDISC_KIND_TBF] = &tbf_vtable, + [QDISC_KIND_TEQL] = &teql_vtable, +}; + +static int qdisc_new(QDiscKind kind, QDisc **ret) { + _cleanup_(qdisc_freep) QDisc *qdisc = NULL; + int r; + + if (kind == _QDISC_KIND_INVALID) { + qdisc = new(QDisc, 1); + if (!qdisc) + return -ENOMEM; + + *qdisc = (QDisc) { + .meta.kind = TC_KIND_QDISC, + .family = AF_UNSPEC, + .parent = TC_H_ROOT, + .kind = kind, + }; + } else { + qdisc = malloc0(qdisc_vtable[kind]->object_size); + if (!qdisc) + return -ENOMEM; + + qdisc->meta.kind = TC_KIND_QDISC, + qdisc->family = AF_UNSPEC; + qdisc->parent = TC_H_ROOT; + qdisc->kind = kind; + + if (QDISC_VTABLE(qdisc)->init) { + r = QDISC_VTABLE(qdisc)->init(qdisc); + if (r < 0) + return r; + } + } + + *ret = TAKE_PTR(qdisc); + + return 0; +} + +int qdisc_new_static(QDiscKind kind, Network *network, const char *filename, unsigned section_line, QDisc **ret) { + _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; + _cleanup_(qdisc_freep) QDisc *qdisc = NULL; + TrafficControl *existing; + QDisc *q = NULL; + int r; + + assert(network); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = network_config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + existing = ordered_hashmap_get(network->tc_by_section, n); + if (existing) { + if (existing->kind != TC_KIND_QDISC) + return -EINVAL; + + q = TC_TO_QDISC(existing); + + if (q->kind != _QDISC_KIND_INVALID && + kind != _QDISC_KIND_INVALID && + q->kind != kind) + return -EINVAL; + + if (q->kind == kind || kind == _QDISC_KIND_INVALID) { + *ret = q; + return 0; + } + } + + r = qdisc_new(kind, &qdisc); + if (r < 0) + return r; + + if (q) { + qdisc->family = q->family; + qdisc->handle = q->handle; + qdisc->parent = q->parent; + qdisc->tca_kind = TAKE_PTR(q->tca_kind); + + qdisc_free(q); + } + + qdisc->network = network; + qdisc->section = TAKE_PTR(n); + + r = ordered_hashmap_ensure_allocated(&network->tc_by_section, &network_config_hash_ops); + if (r < 0) + return r; + + r = ordered_hashmap_put(network->tc_by_section, qdisc->section, TC(qdisc)); + if (r < 0) + return r; + + *ret = TAKE_PTR(qdisc); + return 0; +} + +void qdisc_free(QDisc *qdisc) { + if (!qdisc) + return; + + if (qdisc->network && qdisc->section) + ordered_hashmap_remove(qdisc->network->tc_by_section, qdisc->section); + + network_config_section_free(qdisc->section); + + free(qdisc->tca_kind); + free(qdisc); +} + +static int qdisc_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + assert(link->tc_messages > 0); + link->tc_messages--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EEXIST) { + log_link_message_error_errno(link, m, r, "Could not set QDisc"); + link_enter_failed(link); + return 1; + } + + if (link->tc_messages == 0) { + log_link_debug(link, "Traffic control configured"); + link->tc_configured = true; + link_check_ready(link); + } + + return 1; +} + +int qdisc_configure(Link *link, QDisc *qdisc) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(link); + assert(link->manager); + assert(link->manager->rtnl); + assert(link->ifindex > 0); + + r = sd_rtnl_message_new_qdisc(link->manager->rtnl, &req, RTM_NEWQDISC, qdisc->family, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not create RTM_NEWQDISC message: %m"); + + r = sd_rtnl_message_set_qdisc_parent(req, qdisc->parent); + if (r < 0) + return log_link_error_errno(link, r, "Could not create tcm_parent message: %m"); + + if (qdisc->handle != TC_H_UNSPEC) { + r = sd_rtnl_message_set_qdisc_handle(req, qdisc->handle); + if (r < 0) + return log_link_error_errno(link, r, "Could not set tcm_handle message: %m"); + } + + if (QDISC_VTABLE(qdisc)) { + if (QDISC_VTABLE(qdisc)->fill_tca_kind) { + r = QDISC_VTABLE(qdisc)->fill_tca_kind(link, qdisc, req); + if (r < 0) + return r; + } else { + r = sd_netlink_message_append_string(req, TCA_KIND, QDISC_VTABLE(qdisc)->tca_kind); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_KIND attribute: %m"); + } + + if (QDISC_VTABLE(qdisc)->fill_message) { + r = QDISC_VTABLE(qdisc)->fill_message(link, qdisc, req); + if (r < 0) + return r; + } + } else { + r = sd_netlink_message_append_string(req, TCA_KIND, qdisc->tca_kind); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_KIND attribute: %m"); + } + + r = netlink_call_async(link->manager->rtnl, NULL, req, qdisc_handler, link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + link->tc_messages++; + + return 0; +} + +int qdisc_section_verify(QDisc *qdisc, bool *has_root, bool *has_clsact) { + int r; + + assert(qdisc); + assert(has_root); + assert(has_clsact); + + if (section_is_invalid(qdisc->section)) + return -EINVAL; + + if (QDISC_VTABLE(qdisc) && QDISC_VTABLE(qdisc)->verify) { + r = QDISC_VTABLE(qdisc)->verify(qdisc); + if (r < 0) + return r; + } + + if (qdisc->parent == TC_H_ROOT) { + if (*has_root) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: More than one root qdisc section is defined. " + "Ignoring the qdisc section from line %u.", + qdisc->section->filename, qdisc->section->line); + *has_root = true; + } else if (qdisc->parent == TC_H_CLSACT) { /* TC_H_CLSACT == TC_H_INGRESS */ + if (*has_clsact) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: More than one clsact or ingress qdisc section is defined. " + "Ignoring the qdisc section from line %u.", + qdisc->section->filename, qdisc->section->line); + *has_clsact = true; + } + + return 0; +} + +int config_parse_qdisc_parent( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(ltype, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + if (streq(rvalue, "root")) { + qdisc->parent = TC_H_ROOT; + if (qdisc->handle == 0) + qdisc->handle = TC_H_UNSPEC; + } else if (streq(rvalue, "clsact")) { + qdisc->parent = TC_H_CLSACT; + qdisc->handle = TC_H_MAKE(TC_H_CLSACT, 0); + } else if (streq(rvalue, "ingress")) { + qdisc->parent = TC_H_INGRESS; + qdisc->handle = TC_H_MAKE(TC_H_INGRESS, 0); + } else { + r = parse_handle(rvalue, &qdisc->parent); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse 'Parent=', ignoring assignment: %s", + rvalue); + return 0; + } + } + + if (STR_IN_SET(rvalue, "clsact", "ingress")) { + r = free_and_strdup(&qdisc->tca_kind, rvalue); + if (r < 0) + return log_oom(); + } else + qdisc->tca_kind = mfree(qdisc->tca_kind); + + qdisc = NULL; + + return 0; +} + +int config_parse_qdisc_handle( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + Network *network = data; + uint16_t n; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(ltype, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + if (isempty(rvalue)) { + qdisc->handle = TC_H_UNSPEC; + qdisc = NULL; + return 0; + } + + r = safe_atou16_full(rvalue, 16, &n); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse 'Handle=', ignoring assignment: %s", + rvalue); + return 0; + } + + qdisc->handle = (uint32_t) n << 16; + qdisc = NULL; + + return 0; +} diff --git a/src/network/tc/qdisc.h b/src/network/tc/qdisc.h new file mode 100644 index 0000000..f9a9954 --- /dev/null +++ b/src/network/tc/qdisc.h @@ -0,0 +1,107 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2019 VMware, Inc. */ +#pragma once + +#include "conf-parser.h" +#include "networkd-link.h" +#include "networkd-network.h" +#include "networkd-util.h" +#include "tc.h" + +typedef enum QDiscKind { + QDISC_KIND_BFIFO, + QDISC_KIND_CAKE, + QDISC_KIND_CODEL, + QDISC_KIND_DRR, + QDISC_KIND_ETS, + QDISC_KIND_FQ, + QDISC_KIND_FQ_CODEL, + QDISC_KIND_FQ_PIE, + QDISC_KIND_GRED, + QDISC_KIND_HHF, + QDISC_KIND_HTB, + QDISC_KIND_NETEM, + QDISC_KIND_PFIFO, + QDISC_KIND_PFIFO_FAST, + QDISC_KIND_PFIFO_HEAD_DROP, + QDISC_KIND_PIE, + QDISC_KIND_QFQ, + QDISC_KIND_SFB, + QDISC_KIND_SFQ, + QDISC_KIND_TBF, + QDISC_KIND_TEQL, + _QDISC_KIND_MAX, + _QDISC_KIND_INVALID = -1, +} QDiscKind; + +typedef struct QDisc { + TrafficControl meta; + + NetworkConfigSection *section; + Network *network; + + int family; + uint32_t handle; + uint32_t parent; + + char *tca_kind; + QDiscKind kind; +} QDisc; + +typedef struct QDiscVTable { + size_t object_size; + const char *tca_kind; + /* called in qdisc_new() */ + int (*init)(QDisc *qdisc); + int (*fill_tca_kind)(Link *link, QDisc *qdisc, sd_netlink_message *m); + int (*fill_message)(Link *link, QDisc *qdisc, sd_netlink_message *m); + int (*verify)(QDisc *qdisc); +} QDiscVTable; + +extern const QDiscVTable * const qdisc_vtable[_QDISC_KIND_MAX]; + +#define QDISC_VTABLE(q) ((q)->kind != _QDISC_KIND_INVALID ? qdisc_vtable[(q)->kind] : NULL) + +/* For casting a qdisc into the various qdisc kinds */ +#define DEFINE_QDISC_CAST(UPPERCASE, MixedCase) \ + static inline MixedCase* UPPERCASE(QDisc *q) { \ + if (_unlikely_(!q || q->kind != QDISC_KIND_##UPPERCASE)) \ + return NULL; \ + \ + return (MixedCase*) q; \ + } + +/* For casting the various qdisc kinds into a qdisc */ +#define QDISC(q) (&(q)->meta) + +void qdisc_free(QDisc *qdisc); +int qdisc_new_static(QDiscKind kind, Network *network, const char *filename, unsigned section_line, QDisc **ret); + +int qdisc_configure(Link *link, QDisc *qdisc); +int qdisc_section_verify(QDisc *qdisc, bool *has_root, bool *has_clsact); + +DEFINE_NETWORK_SECTION_FUNCTIONS(QDisc, qdisc_free); + +DEFINE_TC_CAST(QDISC, QDisc); + +CONFIG_PARSER_PROTOTYPE(config_parse_qdisc_parent); +CONFIG_PARSER_PROTOTYPE(config_parse_qdisc_handle); + +#include "cake.h" +#include "codel.h" +#include "ets.h" +#include "fifo.h" +#include "fq-codel.h" +#include "fq-pie.h" +#include "fq.h" +#include "gred.h" +#include "hhf.h" +#include "htb.h" +#include "pie.h" +#include "qfq.h" +#include "netem.h" +#include "drr.h" +#include "sfb.h" +#include "sfq.h" +#include "tbf.h" +#include "teql.h" diff --git a/src/network/tc/qfq.c b/src/network/tc/qfq.c new file mode 100644 index 0000000..320f2c1 --- /dev/null +++ b/src/network/tc/qfq.c @@ -0,0 +1,178 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2020 VMware, Inc. */ + +#include <linux/pkt_sched.h> + +#include "parse-util.h" +#include "qdisc.h" +#include "qfq.h" +#include "string-util.h" + +#define QFQ_MAX_WEIGHT (1 << 10) +#define QFQ_MIN_MAX_PACKET 512 +#define QFQ_MAX_MAX_PACKET (1 << 16) + +const QDiscVTable qfq_vtable = { + .object_size = sizeof(QuickFairQueueing), + .tca_kind = "qfq", +}; + +static int quick_fair_queueing_class_fill_message(Link *link, TClass *tclass, sd_netlink_message *req) { + QuickFairQueueingClass *qfq; + int r; + + assert(link); + assert(tclass); + assert(req); + + qfq = TCLASS_TO_QFQ(tclass); + + r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "qfq"); + if (r < 0) + return log_link_error_errno(link, r, "Could not open container TCA_OPTIONS: %m"); + + if (qfq->weight > 0) { + r = sd_netlink_message_append_u32(req, TCA_QFQ_WEIGHT, qfq->weight); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_QFQ_WEIGHT attribute: %m"); + } + + if (qfq->max_packet > 0) { + r = sd_netlink_message_append_u32(req, TCA_QFQ_LMAX, qfq->max_packet); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_QFQ_LMAX attribute: %m"); + } + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not close container TCA_OPTIONS: %m"); + return 0; +} + +int config_parse_quick_fair_queueing_weight( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(tclass_free_or_set_invalidp) TClass *tclass = NULL; + QuickFairQueueingClass *qfq; + Network *network = data; + uint32_t v; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = tclass_new_static(TCLASS_KIND_QFQ, network, filename, section_line, &tclass); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to create traffic control class, ignoring assignment: %m"); + return 0; + } + + qfq = TCLASS_TO_QFQ(tclass); + + if (isempty(rvalue)) { + qfq->weight = 0; + tclass = NULL; + return 0; + } + + r = safe_atou32(rvalue, &v); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + if (v == 0 || v > QFQ_MAX_WEIGHT) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + qfq->weight = v; + tclass = NULL; + + return 0; +} + +int config_parse_quick_fair_queueing_max_packet( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(tclass_free_or_set_invalidp) TClass *tclass = NULL; + QuickFairQueueingClass *qfq; + Network *network = data; + uint64_t v; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = tclass_new_static(TCLASS_KIND_QFQ, network, filename, section_line, &tclass); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to create traffic control class, ignoring assignment: %m"); + return 0; + } + + qfq = TCLASS_TO_QFQ(tclass); + + if (isempty(rvalue)) { + qfq->max_packet = 0; + tclass = NULL; + return 0; + } + + r = parse_size(rvalue, 1024, &v); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + if (v < QFQ_MIN_MAX_PACKET || v > QFQ_MAX_MAX_PACKET) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + qfq->max_packet = (uint32_t) v; + tclass = NULL; + + return 0; +} + +const TClassVTable qfq_tclass_vtable = { + .object_size = sizeof(QuickFairQueueingClass), + .tca_kind = "qfq", + .fill_message = quick_fair_queueing_class_fill_message, +}; diff --git a/src/network/tc/qfq.h b/src/network/tc/qfq.h new file mode 100644 index 0000000..0f013a9 --- /dev/null +++ b/src/network/tc/qfq.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2020 VMware, Inc. */ +#pragma once + +#include "conf-parser.h" +#include "qdisc.h" + +typedef struct QuickFairQueueing { + QDisc meta; +} QuickFairQueueing; + +DEFINE_QDISC_CAST(QFQ, QuickFairQueueing); +extern const QDiscVTable qfq_vtable; + +typedef struct QuickFairQueueingClass { + TClass meta; + + uint32_t weight; + uint32_t max_packet; +} QuickFairQueueingClass; + +DEFINE_TCLASS_CAST(QFQ, QuickFairQueueingClass); +extern const TClassVTable qfq_tclass_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_quick_fair_queueing_weight); +CONFIG_PARSER_PROTOTYPE(config_parse_quick_fair_queueing_max_packet); diff --git a/src/network/tc/sfb.c b/src/network/tc/sfb.c new file mode 100644 index 0000000..674fdf6 --- /dev/null +++ b/src/network/tc/sfb.c @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2020 VMware, Inc. */ + +#include <linux/pkt_sched.h> + +#include "alloc-util.h" +#include "conf-parser.h" +#include "netlink-util.h" +#include "parse-util.h" +#include "qdisc.h" +#include "sfb.h" +#include "string-util.h" + +static int stochastic_fair_blue_fill_message(Link *link, QDisc *qdisc, sd_netlink_message *req) { + StochasticFairBlue *sfb; + struct tc_sfb_qopt opt = { + .rehash_interval = 600*1000, + .warmup_time = 60*1000, + .penalty_rate = 10, + .penalty_burst = 20, + .increment = (SFB_MAX_PROB + 1000) / 2000, + .decrement = (SFB_MAX_PROB + 10000) / 20000, + .max = 25, + .bin_size = 20, + }; + int r; + + assert(link); + assert(qdisc); + assert(req); + + sfb = SFB(qdisc); + + opt.limit = sfb->packet_limit; + + r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "sfb"); + if (r < 0) + return log_link_error_errno(link, r, "Could not open container TCA_OPTIONS: %m"); + + r = sd_netlink_message_append_data(req, TCA_SFB_PARMS, &opt, sizeof(struct tc_sfb_qopt)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_SFB_PARMS attribute: %m"); + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not close container TCA_OPTIONS: %m"); + + return 0; +} + +int config_parse_stochastic_fair_blue_u32( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + StochasticFairBlue *sfb; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_SFB, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + sfb = SFB(qdisc); + + if (isempty(rvalue)) { + sfb->packet_limit = 0; + + qdisc = NULL; + return 0; + } + + r = safe_atou32(rvalue, &sfb->packet_limit); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + qdisc = NULL; + + return 0; +} + +const QDiscVTable sfb_vtable = { + .object_size = sizeof(StochasticFairBlue), + .tca_kind = "sfb", + .fill_message = stochastic_fair_blue_fill_message, +}; diff --git a/src/network/tc/sfb.h b/src/network/tc/sfb.h new file mode 100644 index 0000000..628df35 --- /dev/null +++ b/src/network/tc/sfb.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2020 VMware, Inc. */ +#pragma once + +#include "conf-parser.h" +#include "qdisc.h" + +typedef struct StochasticFairBlue { + QDisc meta; + + uint32_t packet_limit; +} StochasticFairBlue; + +DEFINE_QDISC_CAST(SFB, StochasticFairBlue); +extern const QDiscVTable sfb_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_stochastic_fair_blue_u32); diff --git a/src/network/tc/sfq.c b/src/network/tc/sfq.c new file mode 100644 index 0000000..387be83 --- /dev/null +++ b/src/network/tc/sfq.c @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2019 VMware, Inc. */ + +#include <linux/pkt_sched.h> + +#include "alloc-util.h" +#include "conf-parser.h" +#include "netlink-util.h" +#include "parse-util.h" +#include "qdisc.h" +#include "sfq.h" +#include "string-util.h" + +static int stochastic_fairness_queueing_fill_message(Link *link, QDisc *qdisc, sd_netlink_message *req) { + StochasticFairnessQueueing *sfq; + struct tc_sfq_qopt_v1 opt = {}; + int r; + + assert(link); + assert(qdisc); + assert(req); + + sfq = SFQ(qdisc); + + opt.v0.perturb_period = sfq->perturb_period / USEC_PER_SEC; + + r = sd_netlink_message_append_data(req, TCA_OPTIONS, &opt, sizeof(struct tc_sfq_qopt_v1)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_OPTIONS attribute: %m"); + + return 0; +} + +int config_parse_stochastic_fairness_queueing_perturb_period( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + StochasticFairnessQueueing *sfq; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_SFQ, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + sfq = SFQ(qdisc); + + if (isempty(rvalue)) { + sfq->perturb_period = 0; + + qdisc = NULL; + return 0; + } + + r = parse_sec(rvalue, &sfq->perturb_period); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + qdisc = NULL; + + return 0; +} + +const QDiscVTable sfq_vtable = { + .object_size = sizeof(StochasticFairnessQueueing), + .tca_kind = "sfq", + .fill_message = stochastic_fairness_queueing_fill_message, +}; diff --git a/src/network/tc/sfq.h b/src/network/tc/sfq.h new file mode 100644 index 0000000..1626775 --- /dev/null +++ b/src/network/tc/sfq.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2019 VMware, Inc. */ +#pragma once + +#include "conf-parser.h" +#include "qdisc.h" +#include "time-util.h" + +typedef struct StochasticFairnessQueueing { + QDisc meta; + + usec_t perturb_period; +} StochasticFairnessQueueing; + +DEFINE_QDISC_CAST(SFQ, StochasticFairnessQueueing); +extern const QDiscVTable sfq_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_stochastic_fairness_queueing_perturb_period); diff --git a/src/network/tc/tbf.c b/src/network/tc/tbf.c new file mode 100644 index 0000000..2d84c5a --- /dev/null +++ b/src/network/tc/tbf.c @@ -0,0 +1,346 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2019 VMware, Inc. */ + +#include <linux/pkt_sched.h> +#include <math.h> + +#include "alloc-util.h" +#include "conf-parser.h" +#include "netem.h" +#include "netlink-util.h" +#include "networkd-manager.h" +#include "parse-util.h" +#include "qdisc.h" +#include "string-util.h" +#include "strv.h" +#include "tc-util.h" + +static int token_bucket_filter_fill_message(Link *link, QDisc *qdisc, sd_netlink_message *req) { + uint32_t rtab[256], ptab[256]; + struct tc_tbf_qopt opt = {}; + TokenBucketFilter *tbf; + int r; + + assert(link); + assert(qdisc); + assert(req); + + tbf = TBF(qdisc); + + opt.rate.rate = tbf->rate >= (1ULL << 32) ? ~0U : tbf->rate; + opt.peakrate.rate = tbf->peak_rate >= (1ULL << 32) ? ~0U : tbf->peak_rate; + + if (tbf->limit > 0) + opt.limit = tbf->limit; + else { + double lim, lim2; + + lim = tbf->rate * (double) tbf->latency / USEC_PER_SEC + tbf->burst; + if (tbf->peak_rate > 0) { + lim2 = tbf->peak_rate * (double) tbf->latency / USEC_PER_SEC + tbf->mtu; + lim = MIN(lim, lim2); + } + opt.limit = lim; + } + + opt.rate.mpu = tbf->mpu; + + r = tc_fill_ratespec_and_table(&opt.rate, rtab, tbf->mtu); + if (r < 0) + return log_link_error_errno(link, r, "Failed to calculate ratespec: %m"); + + r = tc_transmit_time(opt.rate.rate, tbf->burst, &opt.buffer); + if (r < 0) + return log_link_error_errno(link, r, "Failed to calculate buffer size: %m"); + + if (opt.peakrate.rate > 0) { + opt.peakrate.mpu = tbf->mpu; + + r = tc_fill_ratespec_and_table(&opt.peakrate, ptab, tbf->mtu); + if (r < 0) + return log_link_error_errno(link, r, "Failed to calculate ratespec: %m"); + + r = tc_transmit_time(opt.peakrate.rate, tbf->mtu, &opt.mtu); + if (r < 0) + return log_link_error_errno(link, r, "Failed to calculate mtu size: %m"); + } + + r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "tbf"); + if (r < 0) + return log_link_error_errno(link, r, "Could not open container TCA_OPTIONS: %m"); + + r = sd_netlink_message_append_data(req, TCA_TBF_PARMS, &opt, sizeof(struct tc_tbf_qopt)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_TBF_PARMS attribute: %m"); + + r = sd_netlink_message_append_data(req, TCA_TBF_BURST, &tbf->burst, sizeof(tbf->burst)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_TBF_BURST attribute: %m"); + + if (tbf->rate >= (1ULL << 32)) { + r = sd_netlink_message_append_u64(req, TCA_TBF_RATE64, tbf->rate); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_TBF_RATE64 attribute: %m"); + } + + r = sd_netlink_message_append_data(req, TCA_TBF_RTAB, rtab, sizeof(rtab)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_TBF_RTAB attribute: %m"); + + if (opt.peakrate.rate > 0) { + if (tbf->peak_rate >= (1ULL << 32)) { + r = sd_netlink_message_append_u64(req, TCA_TBF_PRATE64, tbf->peak_rate); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_TBF_PRATE64 attribute: %m"); + } + + r = sd_netlink_message_append_u32(req, TCA_TBF_PBURST, tbf->mtu); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_TBF_PBURST attribute: %m"); + + r = sd_netlink_message_append_data(req, TCA_TBF_PTAB, ptab, sizeof(ptab)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_TBF_PTAB attribute: %m"); + } + + r = sd_netlink_message_close_container(req); + if (r < 0) + return log_link_error_errno(link, r, "Could not close container TCA_OPTIONS: %m"); + + return 0; +} + +int config_parse_token_bucket_filter_size( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + Network *network = data; + TokenBucketFilter *tbf; + uint64_t k; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_TBF, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + tbf = TBF(qdisc); + + if (isempty(rvalue)) { + if (STR_IN_SET(lvalue, "BurstBytes", "Burst")) + tbf->burst = 0; + else if (STR_IN_SET(lvalue, "LimitBytes", "LimitSize")) + tbf->limit = 0; + else if (streq(lvalue, "MTUBytes")) + tbf->mtu = 0; + else if (streq(lvalue, "MPUBytes")) + tbf->mpu = 0; + else + assert_not_reached("unknown lvalue"); + + qdisc = NULL; + return 0; + } + + r = parse_size(rvalue, 1024, &k); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + if (STR_IN_SET(lvalue, "BurstBytes", "Burst")) + tbf->burst = k; + else if (STR_IN_SET(lvalue, "LimitBytes", "LimitSize")) + tbf->limit = k; + else if (streq(lvalue, "MPUBytes")) + tbf->mpu = k; + else if (streq(lvalue, "MTUBytes")) + tbf->mtu = k; + else + assert_not_reached("unknown lvalue"); + + qdisc = NULL; + + return 0; +} + +int config_parse_token_bucket_filter_rate( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + Network *network = data; + TokenBucketFilter *tbf; + uint64_t k, *p; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_TBF, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + tbf = TBF(qdisc); + if (streq(lvalue, "Rate")) + p = &tbf->rate; + else if (streq(lvalue, "PeakRate")) + p = &tbf->peak_rate; + else + assert_not_reached("unknown lvalue"); + + if (isempty(rvalue)) { + *p = 0; + + qdisc = NULL; + return 0; + } + + r = parse_size(rvalue, 1000, &k); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + *p = k / 8; + + qdisc = NULL; + + return 0; +} + +int config_parse_token_bucket_filter_latency( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + Network *network = data; + TokenBucketFilter *tbf; + usec_t u; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_TBF, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + tbf = TBF(qdisc); + + if (isempty(rvalue)) { + tbf->latency = 0; + + qdisc = NULL; + return 0; + } + + r = parse_sec(rvalue, &u); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + tbf->latency = u; + + qdisc = NULL; + + return 0; +} + +static int token_bucket_filter_verify(QDisc *qdisc) { + TokenBucketFilter *tbf = TBF(qdisc); + + if (tbf->limit > 0 && tbf->latency > 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Specifying both LimitSize= and LatencySec= is not allowed. " + "Ignoring [TokenBucketFilter] section from line %u.", + qdisc->section->filename, qdisc->section->line); + + if (tbf->limit == 0 && tbf->latency == 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Either LimitSize= or LatencySec= is required. " + "Ignoring [TokenBucketFilter] section from line %u.", + qdisc->section->filename, qdisc->section->line); + + if (tbf->rate == 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Rate= is mandatory. " + "Ignoring [TokenBucketFilter] section from line %u.", + qdisc->section->filename, qdisc->section->line); + + if (tbf->burst == 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Burst= is mandatory. " + "Ignoring [TokenBucketFilter] section from line %u.", + qdisc->section->filename, qdisc->section->line); + + if (tbf->peak_rate > 0 && tbf->mtu == 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: MTUBytes= is mandatory when PeakRate= is specified. " + "Ignoring [TokenBucketFilter] section from line %u.", + qdisc->section->filename, qdisc->section->line); + + return 0; +} + +const QDiscVTable tbf_vtable = { + .object_size = sizeof(TokenBucketFilter), + .tca_kind = "tbf", + .fill_message = token_bucket_filter_fill_message, + .verify = token_bucket_filter_verify +}; diff --git a/src/network/tc/tbf.h b/src/network/tc/tbf.h new file mode 100644 index 0000000..6b4b017 --- /dev/null +++ b/src/network/tc/tbf.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2019 VMware, Inc. */ +#pragma once + +#include "conf-parser.h" +#include "qdisc.h" +#include "time-util.h" + +typedef struct TokenBucketFilter { + QDisc meta; + + uint64_t rate; + uint64_t peak_rate; + uint32_t burst; + uint32_t mtu; + usec_t latency; + size_t limit; + size_t mpu; +} TokenBucketFilter; + +DEFINE_QDISC_CAST(TBF, TokenBucketFilter); +extern const QDiscVTable tbf_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_token_bucket_filter_latency); +CONFIG_PARSER_PROTOTYPE(config_parse_token_bucket_filter_size); +CONFIG_PARSER_PROTOTYPE(config_parse_token_bucket_filter_rate); diff --git a/src/network/tc/tc-util.c b/src/network/tc/tc-util.c new file mode 100644 index 0000000..3e10b50 --- /dev/null +++ b/src/network/tc/tc-util.c @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2019 VMware, Inc. */ + +#include "alloc-util.h" +#include "extract-word.h" +#include "fileio.h" +#include "parse-util.h" +#include "tc-util.h" +#include "time-util.h" + +int tc_init(double *ret_ticks_in_usec, uint32_t *ret_hz) { + static double ticks_in_usec = -1; + static uint32_t hz; + + if (ticks_in_usec < 0) { + uint32_t clock_resolution, ticks_to_usec, usec_to_ticks; + _cleanup_free_ char *line = NULL; + double clock_factor; + int r; + + r = read_one_line_file("/proc/net/psched", &line); + if (r < 0) + return r; + + r = sscanf(line, "%08x%08x%08x%08x", &ticks_to_usec, &usec_to_ticks, &clock_resolution, &hz); + if (r < 4) + return -EIO; + + clock_factor = (double) clock_resolution / USEC_PER_SEC; + ticks_in_usec = (double) ticks_to_usec / usec_to_ticks * clock_factor; + } + + if (ret_ticks_in_usec) + *ret_ticks_in_usec = ticks_in_usec; + if (ret_hz) + *ret_hz = hz; + + return 0; +} + +int tc_time_to_tick(usec_t t, uint32_t *ret) { + double ticks_in_usec; + usec_t a; + int r; + + assert(ret); + + r = tc_init(&ticks_in_usec, NULL); + if (r < 0) + return r; + + a = t * ticks_in_usec; + if (a > UINT32_MAX) + return -ERANGE; + + *ret = a; + return 0; +} + +int parse_tc_percent(const char *s, uint32_t *percent) { + int r; + + assert(s); + assert(percent); + + r = parse_permille(s); + if (r < 0) + return r; + + *percent = (double) r / 1000 * UINT32_MAX; + return 0; +} + +int tc_transmit_time(uint64_t rate, uint32_t size, uint32_t *ret) { + return tc_time_to_tick(USEC_PER_SEC * ((double)size / (double)rate), ret); +} + +int tc_fill_ratespec_and_table(struct tc_ratespec *rate, uint32_t *rtab, uint32_t mtu) { + uint32_t cell_log = 0; + int r; + + if (mtu == 0) + mtu = 2047; + + while ((mtu >> cell_log) > 255) + cell_log++; + + for (size_t i = 0; i < 256; i++) { + uint32_t sz; + + sz = (i + 1) << cell_log; + if (sz < rate->mpu) + sz = rate->mpu; + r = tc_transmit_time(rate->rate, sz, &rtab[i]); + if (r < 0) + return r; + } + + rate->cell_align = -1; + rate->cell_log = cell_log; + rate->linklayer = TC_LINKLAYER_ETHERNET; + return 0; +} + +int parse_handle(const char *t, uint32_t *ret) { + _cleanup_free_ char *word = NULL; + uint16_t major, minor; + int r; + + assert(t); + assert(ret); + + /* Extract the major number. */ + r = extract_first_word(&t, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + if (!t) + return -EINVAL; + + r = safe_atou16_full(word, 16, &major); + if (r < 0) + return r; + + r = safe_atou16_full(t, 16, &minor); + if (r < 0) + return r; + + *ret = ((uint32_t) major << 16) | minor; + return 0; +} diff --git a/src/network/tc/tc-util.h b/src/network/tc/tc-util.h new file mode 100644 index 0000000..83bad8e --- /dev/null +++ b/src/network/tc/tc-util.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2019 VMware, Inc. */ +#pragma once + +#include <linux/pkt_sched.h> + +#include "time-util.h" + +int tc_init(double *ret_ticks_in_usec, uint32_t *ret_hz); +int tc_time_to_tick(usec_t t, uint32_t *ret); +int parse_tc_percent(const char *s, uint32_t *percent); +int tc_transmit_time(uint64_t rate, uint32_t size, uint32_t *ret); +int tc_fill_ratespec_and_table(struct tc_ratespec *rate, uint32_t *rtab, uint32_t mtu); +int parse_handle(const char *t, uint32_t *ret); diff --git a/src/network/tc/tc.c b/src/network/tc/tc.c new file mode 100644 index 0000000..c32b040 --- /dev/null +++ b/src/network/tc/tc.c @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "macro.h" +#include "qdisc.h" +#include "tc.h" +#include "tclass.h" + +void traffic_control_free(TrafficControl *tc) { + if (!tc) + return; + + switch (tc->kind) { + case TC_KIND_QDISC: + qdisc_free(TC_TO_QDISC(tc)); + break; + case TC_KIND_TCLASS: + tclass_free(TC_TO_TCLASS(tc)); + break; + default: + assert_not_reached("Invalid traffic control type"); + } +} + +static int traffic_control_configure(Link *link, TrafficControl *tc) { + assert(link); + assert(tc); + + switch(tc->kind) { + case TC_KIND_QDISC: + return qdisc_configure(link, TC_TO_QDISC(tc)); + case TC_KIND_TCLASS: + return tclass_configure(link, TC_TO_TCLASS(tc)); + default: + assert_not_reached("Invalid traffic control type"); + } +} + +int link_configure_traffic_control(Link *link) { + TrafficControl *tc; + int r; + + link->tc_configured = false; + link->tc_messages = 0; + + ORDERED_HASHMAP_FOREACH(tc, link->network->tc_by_section) { + r = traffic_control_configure(link, tc); + if (r < 0) + return r; + } + + if (link->tc_messages == 0) + link->tc_configured = true; + else + log_link_debug(link, "Configuring traffic control"); + + return 0; +} + +static int traffic_control_section_verify(TrafficControl *tc, bool *qdisc_has_root, bool *qdisc_has_clsact) { + assert(tc); + + switch(tc->kind) { + case TC_KIND_QDISC: + return qdisc_section_verify(TC_TO_QDISC(tc), qdisc_has_root, qdisc_has_clsact); + case TC_KIND_TCLASS: + return tclass_section_verify(TC_TO_TCLASS(tc)); + default: + assert_not_reached("Invalid traffic control type"); + } +} + +void network_drop_invalid_traffic_control(Network *network) { + bool has_root = false, has_clsact = false; + TrafficControl *tc; + + assert(network); + + ORDERED_HASHMAP_FOREACH(tc, network->tc_by_section) + if (traffic_control_section_verify(tc, &has_root, &has_clsact) < 0) + traffic_control_free(tc); +} diff --git a/src/network/tc/tc.h b/src/network/tc/tc.h new file mode 100644 index 0000000..7fbd744 --- /dev/null +++ b/src/network/tc/tc.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "networkd-link.h" + +typedef enum TrafficControlKind { + TC_KIND_QDISC, + TC_KIND_TCLASS, + TC_KIND_FILTER, + _TC_KIND_MAX, + _TC_KIND_INVALID = -1, +} TrafficControlKind; + +typedef struct TrafficControl { + TrafficControlKind kind; +} TrafficControl; + +/* For casting a tc into the various tc kinds */ +#define DEFINE_TC_CAST(UPPERCASE, MixedCase) \ + static inline MixedCase* TC_TO_##UPPERCASE(TrafficControl *tc) { \ + if (_unlikely_(!tc || tc->kind != TC_KIND_##UPPERCASE)) \ + return NULL; \ + \ + return (MixedCase*) tc; \ + } + +/* For casting the various tc kinds into a tc */ +#define TC(tc) (&(tc)->meta) + +void traffic_control_free(TrafficControl *tc); +int link_configure_traffic_control(Link *link); +void network_drop_invalid_traffic_control(Network *network); diff --git a/src/network/tc/tclass.c b/src/network/tc/tclass.c new file mode 100644 index 0000000..21b26b0 --- /dev/null +++ b/src/network/tc/tclass.c @@ -0,0 +1,289 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2019 VMware, Inc. */ + +#include <linux/pkt_sched.h> + +#include "alloc-util.h" +#include "conf-parser.h" +#include "in-addr-util.h" +#include "netlink-util.h" +#include "networkd-manager.h" +#include "parse-util.h" +#include "set.h" +#include "string-util.h" +#include "strv.h" +#include "tc-util.h" +#include "tclass.h" + +const TClassVTable * const tclass_vtable[_TCLASS_KIND_MAX] = { + [TCLASS_KIND_DRR] = &drr_tclass_vtable, + [TCLASS_KIND_HTB] = &htb_tclass_vtable, + [TCLASS_KIND_QFQ] = &qfq_tclass_vtable, +}; + +static int tclass_new(TClassKind kind, TClass **ret) { + _cleanup_(tclass_freep) TClass *tclass = NULL; + int r; + + tclass = malloc0(tclass_vtable[kind]->object_size); + if (!tclass) + return -ENOMEM; + + tclass->meta.kind = TC_KIND_TCLASS, + tclass->parent = TC_H_ROOT; + tclass->kind = kind; + + if (TCLASS_VTABLE(tclass)->init) { + r = TCLASS_VTABLE(tclass)->init(tclass); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(tclass); + + return 0; +} + +int tclass_new_static(TClassKind kind, Network *network, const char *filename, unsigned section_line, TClass **ret) { + _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; + _cleanup_(tclass_freep) TClass *tclass = NULL; + TrafficControl *existing; + int r; + + assert(network); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = network_config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + existing = ordered_hashmap_get(network->tc_by_section, n); + if (existing) { + TClass *t; + + if (existing->kind != TC_KIND_TCLASS) + return -EINVAL; + + t = TC_TO_TCLASS(existing); + + if (t->kind != kind) + return -EINVAL; + + *ret = t; + return 0; + } + + r = tclass_new(kind, &tclass); + if (r < 0) + return r; + + tclass->network = network; + tclass->section = TAKE_PTR(n); + + r = ordered_hashmap_ensure_allocated(&network->tc_by_section, &network_config_hash_ops); + if (r < 0) + return r; + + r = ordered_hashmap_put(network->tc_by_section, tclass->section, tclass); + if (r < 0) + return r; + + *ret = TAKE_PTR(tclass); + return 0; +} + +void tclass_free(TClass *tclass) { + if (!tclass) + return; + + if (tclass->network && tclass->section) + ordered_hashmap_remove(tclass->network->tc_by_section, tclass->section); + + network_config_section_free(tclass->section); + + free(tclass); +} + +static int tclass_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + assert(link->tc_messages > 0); + link->tc_messages--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EEXIST) { + log_link_message_error_errno(link, m, r, "Could not set TClass"); + link_enter_failed(link); + return 1; + } + + if (link->tc_messages == 0) { + log_link_debug(link, "Traffic control configured"); + link->tc_configured = true; + link_check_ready(link); + } + + return 1; +} + +int tclass_configure(Link *link, TClass *tclass) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(link); + assert(link->manager); + assert(link->manager->rtnl); + assert(link->ifindex > 0); + + r = sd_rtnl_message_new_tclass(link->manager->rtnl, &req, RTM_NEWTCLASS, AF_UNSPEC, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not create RTM_NEWTCLASS message: %m"); + + r = sd_rtnl_message_set_tclass_parent(req, tclass->parent); + if (r < 0) + return log_link_error_errno(link, r, "Could not create tcm_parent message: %m"); + + if (tclass->classid != TC_H_UNSPEC) { + r = sd_rtnl_message_set_tclass_handle(req, tclass->classid); + if (r < 0) + return log_link_error_errno(link, r, "Could not set tcm_handle message: %m"); + } + + r = sd_netlink_message_append_string(req, TCA_KIND, TCLASS_VTABLE(tclass)->tca_kind); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_KIND attribute: %m"); + + if (TCLASS_VTABLE(tclass)->fill_message) { + r = TCLASS_VTABLE(tclass)->fill_message(link, tclass, req); + if (r < 0) + return r; + } + + r = netlink_call_async(link->manager->rtnl, NULL, req, tclass_handler, link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + link->tc_messages++; + + return 0; +} + +int tclass_section_verify(TClass *tclass) { + int r; + + assert(tclass); + + if (section_is_invalid(tclass->section)) + return -EINVAL; + + if (TCLASS_VTABLE(tclass)->verify) { + r = TCLASS_VTABLE(tclass)->verify(tclass); + if (r < 0) + return r; + } + + return 0; +} + +int config_parse_tclass_parent( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(tclass_free_or_set_invalidp) TClass *tclass = NULL; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = tclass_new_static(ltype, network, filename, section_line, &tclass); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to create traffic control class, ignoring assignment: %m"); + return 0; + } + + if (streq(rvalue, "root")) + tclass->parent = TC_H_ROOT; + else { + r = parse_handle(rvalue, &tclass->parent); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse 'Parent=', ignoring assignment: %s", + rvalue); + return 0; + } + } + + tclass = NULL; + + return 0; +} + +int config_parse_tclass_classid( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(tclass_free_or_set_invalidp) TClass *tclass = NULL; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = tclass_new_static(ltype, network, filename, section_line, &tclass); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to create traffic control class, ignoring assignment: %m"); + return 0; + } + + if (isempty(rvalue)) { + tclass->classid = TC_H_UNSPEC; + tclass = NULL; + return 0; + } + + r = parse_handle(rvalue, &tclass->classid); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse 'ClassId=', ignoring assignment: %s", + rvalue); + return 0; + } + + tclass = NULL; + + return 0; +} diff --git a/src/network/tc/tclass.h b/src/network/tc/tclass.h new file mode 100644 index 0000000..f02a6a7 --- /dev/null +++ b/src/network/tc/tclass.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright © 2019 VMware, Inc. */ +#pragma once + +#include "conf-parser.h" +#include "networkd-link.h" +#include "networkd-network.h" +#include "networkd-util.h" +#include "tc.h" + +typedef enum TClassKind { + TCLASS_KIND_DRR, + TCLASS_KIND_HTB, + TCLASS_KIND_QFQ, + _TCLASS_KIND_MAX, + _TCLASS_KIND_INVALID = -1, +} TClassKind; + +typedef struct TClass { + TrafficControl meta; + + NetworkConfigSection *section; + Network *network; + + uint32_t classid; + uint32_t parent; + + TClassKind kind; +} TClass; + +typedef struct TClassVTable { + size_t object_size; + const char *tca_kind; + /* called in tclass_new() */ + int (*init)(TClass *tclass); + int (*fill_message)(Link *link, TClass *tclass, sd_netlink_message *m); + int (*verify)(TClass *tclass); +} TClassVTable; + +extern const TClassVTable * const tclass_vtable[_TCLASS_KIND_MAX]; + +#define TCLASS_VTABLE(t) ((t)->kind != _TCLASS_KIND_INVALID ? tclass_vtable[(t)->kind] : NULL) + +/* For casting a tclass into the various tclass kinds */ +#define DEFINE_TCLASS_CAST(UPPERCASE, MixedCase) \ + static inline MixedCase* TCLASS_TO_##UPPERCASE(TClass *t) { \ + if (_unlikely_(!t || t->kind != TCLASS_KIND_##UPPERCASE)) \ + return NULL; \ + \ + return (MixedCase*) t; \ + } + +/* For casting the various tclass kinds into a tclass */ +#define TCLASS(t) (&(t)->meta) + +void tclass_free(TClass *tclass); +int tclass_new_static(TClassKind kind, Network *network, const char *filename, unsigned section_line, TClass **ret); + +int tclass_configure(Link *link, TClass *tclass); +int tclass_section_verify(TClass *tclass); + +DEFINE_NETWORK_SECTION_FUNCTIONS(TClass, tclass_free); + +DEFINE_TC_CAST(TCLASS, TClass); + +CONFIG_PARSER_PROTOTYPE(config_parse_tclass_parent); +CONFIG_PARSER_PROTOTYPE(config_parse_tclass_classid); + +#include "drr.h" +#include "htb.h" +#include "qfq.h" diff --git a/src/network/tc/teql.c b/src/network/tc/teql.c new file mode 100644 index 0000000..0da2fc3 --- /dev/null +++ b/src/network/tc/teql.c @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "macro.h" +#include "netlink-util.h" +#include "parse-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "teql.h" + +static int trivial_link_equalizer_fill_tca_kind(Link *link, QDisc *qdisc, sd_netlink_message *req) { + char kind[STRLEN("teql") + DECIMAL_STR_MAX(unsigned)]; + TrivialLinkEqualizer *teql; + int r; + + assert(link); + assert(qdisc); + assert(req); + + teql = TEQL(qdisc); + + xsprintf(kind, "teql%u", teql->id); + r = sd_netlink_message_append_string(req, TCA_KIND, kind); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_KIND attribute: %m"); + + return 0; +} + +const QDiscVTable teql_vtable = { + .object_size = sizeof(TrivialLinkEqualizer), + .fill_tca_kind = trivial_link_equalizer_fill_tca_kind, +}; + +int config_parse_trivial_link_equalizer_id( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; + TrivialLinkEqualizer *teql; + Network *network = data; + unsigned id; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_TEQL, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + return 0; + } + + teql = TEQL(qdisc); + + if (isempty(rvalue)) { + teql->id = 0; + + qdisc = NULL; + return 0; + } + + r = safe_atou(rvalue, &id); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + if (id > INT_MAX) + log_syntax(unit, LOG_WARNING, filename, line, 0, + "'%s=' is too large, ignoring assignment: %s", + lvalue, rvalue); + + teql->id = id; + + qdisc = NULL; + return 0; +} diff --git a/src/network/tc/teql.h b/src/network/tc/teql.h new file mode 100644 index 0000000..8d0085e --- /dev/null +++ b/src/network/tc/teql.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "conf-parser.h" +#include "qdisc.h" + +typedef struct TrivialLinkEqualizer { + QDisc meta; + + unsigned id; +} TrivialLinkEqualizer; + +DEFINE_QDISC_CAST(TEQL, TrivialLinkEqualizer); +extern const QDiscVTable teql_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_trivial_link_equalizer_id); diff --git a/src/network/test-network-tables.c b/src/network/test-network-tables.c new file mode 100644 index 0000000..475cac7 --- /dev/null +++ b/src/network/test-network-tables.c @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "bond.h" +#include "dhcp6-internal.h" +#include "dhcp6-protocol.h" +#include "ethtool-util.h" +#include "ipvlan.h" +#include "lldp-internal.h" +#include "macvlan.h" +#include "ndisc-internal.h" +#include "netlink-internal.h" +#include "networkd-link.h" +#include "networkd-network.h" +#include "networkd-util.h" +#include "test-tables.h" +#include "tunnel.h" + +int main(int argc, char **argv) { + test_table(bond_ad_select, NETDEV_BOND_AD_SELECT); + test_table(bond_arp_all_targets, NETDEV_BOND_ARP_ALL_TARGETS); + test_table(bond_arp_validate, NETDEV_BOND_ARP_VALIDATE); + test_table(bond_fail_over_mac, NETDEV_BOND_FAIL_OVER_MAC); + test_table(bond_lacp_rate, NETDEV_BOND_LACP_RATE); + test_table(bond_mode, NETDEV_BOND_MODE); + test_table(bond_primary_reselect, NETDEV_BOND_PRIMARY_RESELECT); + test_table(bond_xmit_hash_policy, NETDEV_BOND_XMIT_HASH_POLICY); + test_table(dhcp6_message_status, DHCP6_STATUS); + test_table_sparse(dhcp6_message_type, DHCP6_MESSAGE); /* enum starts from 1 */ + test_table(dhcp_use_domains, DHCP_USE_DOMAINS); + test_table(duplex, DUP); + test_table(ip6tnl_mode, NETDEV_IP6_TNL_MODE); + test_table(ipv6_privacy_extensions, IPV6_PRIVACY_EXTENSIONS); + test_table(ipvlan_flags, NETDEV_IPVLAN_FLAGS); + test_table(link_operstate, LINK_OPERSTATE); + /* test_table(link_state, LINK_STATE); — not a reversible mapping */ + test_table(lldp_mode, LLDP_MODE); + test_table(netdev_kind, NETDEV_KIND); + test_table(nl_union_link_info_data, NL_UNION_LINK_INFO_DATA); + test_table(radv_prefix_delegation, RADV_PREFIX_DELEGATION); + test_table(wol, WOL); + test_table(lldp_event, SD_LLDP_EVENT); + test_table(ndisc_event, SD_NDISC_EVENT); + + test_table_sparse(ipvlan_mode, NETDEV_IPVLAN_MODE); + test_table_sparse(macvlan_mode, NETDEV_MACVLAN_MODE); + test_table_sparse(address_family, ADDRESS_FAMILY); + + return EXIT_SUCCESS; +} diff --git a/src/network/test-network.c b/src/network/test-network.c new file mode 100644 index 0000000..03c9440 --- /dev/null +++ b/src/network/test-network.c @@ -0,0 +1,258 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <arpa/inet.h> +#include <sys/param.h> + +#include "sd-device.h" + +#include "alloc-util.h" +#include "dhcp-lease-internal.h" +#include "ether-addr-util.h" +#include "hostname-util.h" +#include "network-internal.h" +#include "networkd-manager.h" +#include "string-util.h" +#include "tests.h" + +static void test_deserialize_in_addr(void) { + _cleanup_free_ struct in_addr *addresses = NULL; + _cleanup_free_ struct in6_addr *addresses6 = NULL; + union in_addr_union a, b, c, d, e, f; + int size; + const char *addresses_string = "192.168.0.1 0:0:0:0:0:FFFF:204.152.189.116 192.168.0.2 ::1 192.168.0.3 1:0:0:0:0:0:0:8"; + + assert_se(in_addr_from_string(AF_INET, "0:0:0:0:0:FFFF:204.152.189.116", &a) < 0); + assert_se(in_addr_from_string(AF_INET6, "192.168.0.1", &d) < 0); + + assert_se(in_addr_from_string(AF_INET, "192.168.0.1", &a) >= 0); + assert_se(in_addr_from_string(AF_INET, "192.168.0.2", &b) >= 0); + assert_se(in_addr_from_string(AF_INET, "192.168.0.3", &c) >= 0); + assert_se(in_addr_from_string(AF_INET6, "0:0:0:0:0:FFFF:204.152.189.116", &d) >= 0); + assert_se(in_addr_from_string(AF_INET6, "::1", &e) >= 0); + assert_se(in_addr_from_string(AF_INET6, "1:0:0:0:0:0:0:8", &f) >= 0); + + assert_se((size = deserialize_in_addrs(&addresses, addresses_string)) >= 0); + assert_se(size == 3); + assert_se(in_addr_equal(AF_INET, &a, (union in_addr_union *) &addresses[0])); + assert_se(in_addr_equal(AF_INET, &b, (union in_addr_union *) &addresses[1])); + assert_se(in_addr_equal(AF_INET, &c, (union in_addr_union *) &addresses[2])); + + assert_se((size = deserialize_in6_addrs(&addresses6, addresses_string)) >= 0); + assert_se(size == 3); + assert_se(in_addr_equal(AF_INET6, &d, (union in_addr_union *) &addresses6[0])); + assert_se(in_addr_equal(AF_INET6, &e, (union in_addr_union *) &addresses6[1])); + assert_se(in_addr_equal(AF_INET6, &f, (union in_addr_union *) &addresses6[2])); +} + +static void test_deserialize_dhcp_routes(void) { + size_t size, allocated; + + { + _cleanup_free_ struct sd_dhcp_route *routes = NULL; + assert_se(deserialize_dhcp_routes(&routes, &size, &allocated, "") >= 0); + assert_se(size == 0); + } + + { + /* no errors */ + _cleanup_free_ struct sd_dhcp_route *routes = NULL; + const char *routes_string = "192.168.0.0/16,192.168.0.1 10.1.2.0/24,10.1.2.1 0.0.0.0/0,10.0.1.1"; + + assert_se(deserialize_dhcp_routes(&routes, &size, &allocated, routes_string) >= 0); + + assert_se(size == 3); + assert_se(routes[0].dst_addr.s_addr == inet_addr("192.168.0.0")); + assert_se(routes[0].gw_addr.s_addr == inet_addr("192.168.0.1")); + assert_se(routes[0].dst_prefixlen == 16); + + assert_se(routes[1].dst_addr.s_addr == inet_addr("10.1.2.0")); + assert_se(routes[1].gw_addr.s_addr == inet_addr("10.1.2.1")); + assert_se(routes[1].dst_prefixlen == 24); + + assert_se(routes[2].dst_addr.s_addr == inet_addr("0.0.0.0")); + assert_se(routes[2].gw_addr.s_addr == inet_addr("10.0.1.1")); + assert_se(routes[2].dst_prefixlen == 0); + } + + { + /* error in second word */ + _cleanup_free_ struct sd_dhcp_route *routes = NULL; + const char *routes_string = "192.168.0.0/16,192.168.0.1 10.1.2.0#24,10.1.2.1 0.0.0.0/0,10.0.1.1"; + + assert_se(deserialize_dhcp_routes(&routes, &size, &allocated, routes_string) >= 0); + + assert_se(size == 2); + assert_se(routes[0].dst_addr.s_addr == inet_addr("192.168.0.0")); + assert_se(routes[0].gw_addr.s_addr == inet_addr("192.168.0.1")); + assert_se(routes[0].dst_prefixlen == 16); + + assert_se(routes[1].dst_addr.s_addr == inet_addr("0.0.0.0")); + assert_se(routes[1].gw_addr.s_addr == inet_addr("10.0.1.1")); + assert_se(routes[1].dst_prefixlen == 0); + } + + { + /* error in every word */ + _cleanup_free_ struct sd_dhcp_route *routes = NULL; + const char *routes_string = "192.168.0.0/55,192.168.0.1 10.1.2.0#24,10.1.2.1 0.0.0.0/0,10.0.1.X"; + + assert_se(deserialize_dhcp_routes(&routes, &size, &allocated, routes_string) >= 0); + assert_se(size == 0); + } +} + +static int test_load_config(Manager *manager) { + int r; +/* TODO: should_reload, is false if the config dirs do not exist, so + * so we can't do this test here, move it to a test for paths_check_timestamps + * directly + * + * assert_se(network_should_reload(manager) == true); +*/ + + r = manager_load_config(manager); + if (r == -EPERM) + return r; + assert_se(r >= 0); + + assert_se(manager_should_reload(manager) == false); + + return 0; +} + +static void test_network_get(Manager *manager, sd_device *loopback) { + Network *network; + const struct ether_addr mac = ETHER_ADDR_NULL; + int r; + + /* Let's hope that the test machine does not have a .network file that applies to loopback device… + * But it is still possible, so let's allow that case too. */ + r = network_get(manager, 0, loopback, "lo", NULL, NULL, &mac, &mac, 0, NULL, NULL, &network); + if (r == -ENOENT) + /* The expected case */ + assert_se(!network); + else if (r >= 0) + assert_se(network); + else + assert_not_reached("bad error!"); +} + +static void test_address_equality(void) { + _cleanup_(address_freep) Address *a1 = NULL, *a2 = NULL; + + assert_se(address_new(&a1) >= 0); + assert_se(address_new(&a2) >= 0); + + assert_se(address_equal(NULL, NULL)); + assert_se(!address_equal(a1, NULL)); + assert_se(!address_equal(NULL, a2)); + assert_se(address_equal(a1, a2)); + + a1->family = AF_INET; + assert_se(!address_equal(a1, a2)); + + a2->family = AF_INET; + assert_se(address_equal(a1, a2)); + + assert_se(in_addr_from_string(AF_INET, "192.168.3.9", &a1->in_addr) >= 0); + assert_se(!address_equal(a1, a2)); + assert_se(in_addr_from_string(AF_INET, "192.168.3.9", &a2->in_addr) >= 0); + assert_se(address_equal(a1, a2)); + assert_se(in_addr_from_string(AF_INET, "192.168.3.10", &a1->in_addr_peer) >= 0); + assert_se(address_equal(a1, a2)); + assert_se(in_addr_from_string(AF_INET, "192.168.3.11", &a2->in_addr_peer) >= 0); + assert_se(address_equal(a1, a2)); + a1->prefixlen = 10; + assert_se(!address_equal(a1, a2)); + a2->prefixlen = 10; + assert_se(address_equal(a1, a2)); + + a1->family = AF_INET6; + assert_se(!address_equal(a1, a2)); + + a2->family = AF_INET6; + assert_se(in_addr_from_string(AF_INET6, "2001:4ca0:4f01::2", &a1->in_addr) >= 0); + assert_se(in_addr_from_string(AF_INET6, "2001:4ca0:4f01::2", &a2->in_addr) >= 0); + assert_se(address_equal(a1, a2)); + + a2->prefixlen = 8; + assert_se(address_equal(a1, a2)); + + assert_se(in_addr_from_string(AF_INET6, "2001:4ca0:4f01::1", &a2->in_addr) >= 0); + assert_se(!address_equal(a1, a2)); +} + +static void test_dhcp_hostname_shorten_overlong(void) { + int r; + + { + /* simple hostname, no actions, no errors */ + _cleanup_free_ char *shortened = NULL; + r = shorten_overlong("name1", &shortened); + assert_se(r == 0); + assert_se(streq("name1", shortened)); + } + + { + /* simple fqdn, no actions, no errors */ + _cleanup_free_ char *shortened = NULL; + r = shorten_overlong("name1.example.com", &shortened); + assert_se(r == 0); + assert_se(streq("name1.example.com", shortened)); + } + + { + /* overlong fqdn, cut to first dot, no errors */ + _cleanup_free_ char *shortened = NULL; + r = shorten_overlong("name1.test-dhcp-this-one-here-is-a-very-very-long-domain.example.com", &shortened); + assert_se(r == 1); + assert_se(streq("name1", shortened)); + } + + { + /* overlong hostname, cut to HOST_MAX_LEN, no errors */ + _cleanup_free_ char *shortened = NULL; + r = shorten_overlong("test-dhcp-this-one-here-is-a-very-very-long-hostname-without-domainname", &shortened); + assert_se(r == 1); + assert_se(streq("test-dhcp-this-one-here-is-a-very-very-long-hostname-without-dom", shortened)); + } + + { + /* overlong fqdn, cut to first dot, empty result error */ + _cleanup_free_ char *shortened = NULL; + r = shorten_overlong(".test-dhcp-this-one-here-is-a-very-very-long-hostname.example.com", &shortened); + assert_se(r == -EDOM); + assert_se(shortened == NULL); + } + +} + +int main(void) { + _cleanup_(manager_freep) Manager *manager = NULL; + _cleanup_(sd_device_unrefp) sd_device *loopback = NULL; + int ifindex, r; + + test_setup_logging(LOG_INFO); + + test_deserialize_in_addr(); + test_deserialize_dhcp_routes(); + test_address_equality(); + test_dhcp_hostname_shorten_overlong(); + + assert_se(manager_new(&manager) >= 0); + + r = test_load_config(manager); + if (r == -EPERM) + return log_tests_skipped("Cannot load configuration"); + assert_se(r == 0); + + assert_se(sd_device_new_from_syspath(&loopback, "/sys/class/net/lo") >= 0); + assert_se(loopback); + assert_se(sd_device_get_ifindex(loopback, &ifindex) >= 0); + assert_se(ifindex == 1); + + test_network_get(manager, loopback); + + assert_se(manager_enumerate(manager) >= 0); + return 0; +} diff --git a/src/network/test-networkd-conf.c b/src/network/test-networkd-conf.c new file mode 100644 index 0000000..c771007 --- /dev/null +++ b/src/network/test-networkd-conf.c @@ -0,0 +1,260 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "ether-addr-util.h" +#include "hexdecoct.h" +#include "log.h" +#include "macro.h" +#include "set.h" +#include "string-util.h" + +#include "network-internal.h" +#include "networkd-conf.h" +#include "networkd-network.h" + +static void test_config_parse_duid_type_one(const char *rvalue, int ret, DUIDType expected, usec_t expected_time) { + DUID actual = {}; + int r; + + r = config_parse_duid_type("network", "filename", 1, "section", 1, "lvalue", 0, rvalue, &actual, NULL); + log_info_errno(r, "\"%s\" → %d (%m)", rvalue, actual.type); + assert_se(r == ret); + assert_se(expected == actual.type); + if (expected == DUID_TYPE_LLT) + assert_se(expected_time == actual.llt_time); +} + +static void test_config_parse_duid_type(void) { + test_config_parse_duid_type_one("", 0, 0, 0); + test_config_parse_duid_type_one("link-layer-time", 0, DUID_TYPE_LLT, 0); + test_config_parse_duid_type_one("link-layer-time:2000-01-01 00:00:00 UTC", 0, DUID_TYPE_LLT, (usec_t) 946684800000000); + test_config_parse_duid_type_one("vendor", 0, DUID_TYPE_EN, 0); + test_config_parse_duid_type_one("vendor:2000-01-01 00:00:00 UTC", 0, 0, 0); + test_config_parse_duid_type_one("link-layer", 0, DUID_TYPE_LL, 0); + test_config_parse_duid_type_one("link-layer:2000-01-01 00:00:00 UTC", 0, 0, 0); + test_config_parse_duid_type_one("uuid", 0, DUID_TYPE_UUID, 0); + test_config_parse_duid_type_one("uuid:2000-01-01 00:00:00 UTC", 0, 0, 0); + test_config_parse_duid_type_one("foo", 0, 0, 0); + test_config_parse_duid_type_one("foo:2000-01-01 00:00:00 UTC", 0, 0, 0); +} + +static void test_config_parse_duid_rawdata_one(const char *rvalue, int ret, const DUID* expected) { + DUID actual = {}; + int r; + _cleanup_free_ char *d = NULL; + + r = config_parse_duid_rawdata("network", "filename", 1, "section", 1, "lvalue", 0, rvalue, &actual, NULL); + d = hexmem(actual.raw_data, actual.raw_data_len); + log_info_errno(r, "\"%s\" → \"%s\" (%m)", + rvalue, strnull(d)); + assert_se(r == ret); + if (expected) { + assert_se(actual.raw_data_len == expected->raw_data_len); + assert_se(memcmp(actual.raw_data, expected->raw_data, expected->raw_data_len) == 0); + } +} + +static void test_config_parse_hwaddr_one(const char *rvalue, int ret, const struct ether_addr* expected) { + struct ether_addr *actual = NULL; + int r; + + r = config_parse_hwaddr("network", "filename", 1, "section", 1, "lvalue", 0, rvalue, &actual, NULL); + assert_se(ret == r); + if (expected) { + assert_se(actual); + assert_se(ether_addr_equal(expected, actual)); + } else + assert_se(actual == NULL); + + free(actual); +} + +static void test_config_parse_hwaddrs_one(const char *rvalue, const struct ether_addr* list, size_t n) { + _cleanup_set_free_free_ Set *s = NULL; + size_t m; + + assert_se(config_parse_hwaddrs("network", "filename", 1, "section", 1, "lvalue", 0, rvalue, &s, NULL) == 0); + assert_se(set_size(s) == n); + + for (m = 0; m < n; m++) { + _cleanup_free_ struct ether_addr *q = NULL; + + assert_se(q = set_remove(s, &list[m])); + } + + assert_se(set_size(s) == 0); +} + +#define BYTES_0_128 "0:1:2:3:4:5:6:7:8:9:a:b:c:d:e:f:10:11:12:13:14:15:16:17:18:19:1a:1b:1c:1d:1e:1f:20:21:22:23:24:25:26:27:28:29:2a:2b:2c:2d:2e:2f:30:31:32:33:34:35:36:37:38:39:3a:3b:3c:3d:3e:3f:40:41:42:43:44:45:46:47:48:49:4a:4b:4c:4d:4e:4f:50:51:52:53:54:55:56:57:58:59:5a:5b:5c:5d:5e:5f:60:61:62:63:64:65:66:67:68:69:6a:6b:6c:6d:6e:6f:70:71:72:73:74:75:76:77:78:79:7a:7b:7c:7d:7e:7f:80" + +#define BYTES_1_128 {0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f,0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f,0x80} + +static void test_config_parse_duid_rawdata(void) { + test_config_parse_duid_rawdata_one("", 0, &(DUID){}); + test_config_parse_duid_rawdata_one("00:11:22:33:44:55:66:77", 0, + &(DUID){0, 8, {0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77}}); + test_config_parse_duid_rawdata_one("00:11:22:", 0, + &(DUID){0, 3, {0x00,0x11,0x22}}); + test_config_parse_duid_rawdata_one("000:11:22", 0, &(DUID){}); /* error, output is all zeros */ + test_config_parse_duid_rawdata_one("00:111:22", 0, &(DUID){}); + test_config_parse_duid_rawdata_one("0:1:2:3:4:5:6:7", 0, + &(DUID){0, 8, {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}}); + test_config_parse_duid_rawdata_one("11::", 0, &(DUID){0, 1, {0x11}}); /* FIXME: should this be an error? */ + test_config_parse_duid_rawdata_one("abcdef", 0, &(DUID){}); + test_config_parse_duid_rawdata_one(BYTES_0_128, 0, &(DUID){}); + test_config_parse_duid_rawdata_one(&BYTES_0_128[2], 0, &(DUID){0, 128, BYTES_1_128}); +} + +static void test_config_parse_hwaddr(void) { + const struct ether_addr t[] = { + { .ether_addr_octet = { 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff } }, + { .ether_addr_octet = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab } }, + }; + + test_config_parse_hwaddr_one("", 0, NULL); + test_config_parse_hwaddr_one("no:ta:ma:ca:dd:re", 0, NULL); + test_config_parse_hwaddr_one("aa:bb:cc:dd:ee:fx", 0, NULL); + test_config_parse_hwaddr_one("aa:bb:cc:dd:ee:ff", 0, &t[0]); + test_config_parse_hwaddr_one(" aa:bb:cc:dd:ee:ff", 0, &t[0]); + test_config_parse_hwaddr_one("aa:bb:cc:dd:ee:ff \t\n", 0, NULL); + test_config_parse_hwaddr_one("aa:bb:cc:dd:ee:ff \t\nxxx", 0, NULL); + test_config_parse_hwaddr_one("aa:bb:cc: dd:ee:ff", 0, NULL); + test_config_parse_hwaddr_one("aa:bb:cc:d d:ee:ff", 0, NULL); + test_config_parse_hwaddr_one("aa:bb:cc:dd:ee", 0, NULL); + test_config_parse_hwaddr_one("9:aa:bb:cc:dd:ee:ff", 0, NULL); + test_config_parse_hwaddr_one("aa:bb:cc:dd:ee:ff:gg", 0, NULL); + test_config_parse_hwaddr_one("aa:Bb:CC:dd:ee:ff", 0, &t[0]); + test_config_parse_hwaddr_one("01:23:45:67:89:aB", 0, &t[1]); + test_config_parse_hwaddr_one("1:23:45:67:89:aB", 0, &t[1]); + test_config_parse_hwaddr_one("aa-bb-cc-dd-ee-ff", 0, &t[0]); + test_config_parse_hwaddr_one("AA-BB-CC-DD-EE-FF", 0, &t[0]); + test_config_parse_hwaddr_one("01-23-45-67-89-ab", 0, &t[1]); + test_config_parse_hwaddr_one("aabb.ccdd.eeff", 0, &t[0]); + test_config_parse_hwaddr_one("0123.4567.89ab", 0, &t[1]); + test_config_parse_hwaddr_one("123.4567.89ab.", 0, NULL); + test_config_parse_hwaddr_one("aabbcc.ddeeff", 0, NULL); + test_config_parse_hwaddr_one("aabbccddeeff", 0, NULL); + test_config_parse_hwaddr_one("aabbccddee:ff", 0, NULL); + test_config_parse_hwaddr_one("012345.6789ab", 0, NULL); + test_config_parse_hwaddr_one("123.4567.89ab", 0, &t[1]); + + test_config_parse_hwaddrs_one("", t, 0); + test_config_parse_hwaddrs_one("no:ta:ma:ca:dd:re", t, 0); + test_config_parse_hwaddrs_one("aa:bb:cc:dd:ee:fx", t, 0); + test_config_parse_hwaddrs_one("aa:bb:cc:dd:ee:ff", t, 1); + test_config_parse_hwaddrs_one(" aa:bb:cc:dd:ee:ff", t, 1); + test_config_parse_hwaddrs_one("aa:bb:cc:dd:ee:ff \t\n", t, 1); + test_config_parse_hwaddrs_one("aa:bb:cc:dd:ee:ff \t\nxxx", t, 1); + test_config_parse_hwaddrs_one("aa:bb:cc: dd:ee:ff", t, 0); + test_config_parse_hwaddrs_one("aa:bb:cc:d d:ee:ff", t, 0); + test_config_parse_hwaddrs_one("aa:bb:cc:dd:ee", t, 0); + test_config_parse_hwaddrs_one("9:aa:bb:cc:dd:ee:ff", t, 0); + test_config_parse_hwaddrs_one("aa:bb:cc:dd:ee:ff:gg", t, 0); + test_config_parse_hwaddrs_one("aa:Bb:CC:dd:ee:ff", t, 1); + test_config_parse_hwaddrs_one("01:23:45:67:89:aB", &t[1], 1); + test_config_parse_hwaddrs_one("1:23:45:67:89:aB", &t[1], 1); + test_config_parse_hwaddrs_one("aa-bb-cc-dd-ee-ff", t, 1); + test_config_parse_hwaddrs_one("AA-BB-CC-DD-EE-FF", t, 1); + test_config_parse_hwaddrs_one("01-23-45-67-89-ab", &t[1], 1); + test_config_parse_hwaddrs_one("aabb.ccdd.eeff", t, 1); + test_config_parse_hwaddrs_one("0123.4567.89ab", &t[1], 1); + test_config_parse_hwaddrs_one("123.4567.89ab.", t, 0); + test_config_parse_hwaddrs_one("aabbcc.ddeeff", t, 0); + test_config_parse_hwaddrs_one("aabbccddeeff", t, 0); + test_config_parse_hwaddrs_one("aabbccddee:ff", t, 0); + test_config_parse_hwaddrs_one("012345.6789ab", t, 0); + test_config_parse_hwaddrs_one("123.4567.89ab", &t[1], 1); + + test_config_parse_hwaddrs_one("123.4567.89ab aa:bb:cc:dd:ee:ff 01-23-45-67-89-ab aa:Bb:CC:dd:ee:ff", t, 2); + test_config_parse_hwaddrs_one("123.4567.89ab aa:bb:cc:dd:ee:fx hogehoge 01-23-45-67-89-ab aaaa aa:Bb:CC:dd:ee:ff", t, 2); +} + +static void test_config_parse_address_one(const char *rvalue, int family, unsigned n_addresses, const union in_addr_union *u, unsigned char prefixlen) { + _cleanup_(network_unrefp) Network *network = NULL; + + assert_se(network = new0(Network, 1)); + network->n_ref = 1; + assert_se(network->filename = strdup("hogehoge.network")); + assert_se(config_parse_match_ifnames("network", "filename", 1, "section", 1, "Name", 0, "*", &network->match_name, network) == 0); + assert_se(config_parse_address("network", "filename", 1, "section", 1, "Address", 0, rvalue, network, network) == 0); + assert_se(ordered_hashmap_size(network->addresses_by_section) == 1); + assert_se(network_verify(network) >= 0); + assert_se(ordered_hashmap_size(network->addresses_by_section) == n_addresses); + if (n_addresses > 0) { + Address *a; + + assert_se(a = ordered_hashmap_first(network->addresses_by_section)); + assert_se(a->prefixlen == prefixlen); + assert_se(a->family == family); + assert_se(in_addr_equal(family, &a->in_addr, u)); + /* TODO: check Address.in_addr and Address.broadcast */ + } +} + +static void test_config_parse_address(void) { + test_config_parse_address_one("", AF_INET, 0, NULL, 0); + test_config_parse_address_one("/", AF_INET, 0, NULL, 0); + test_config_parse_address_one("/8", AF_INET, 0, NULL, 0); + test_config_parse_address_one("1.2.3.4", AF_INET, 1, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 8); + test_config_parse_address_one("1.2.3.4/0", AF_INET, 1, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 0); + test_config_parse_address_one("1.2.3.4/1", AF_INET, 1, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 1); + test_config_parse_address_one("1.2.3.4/2", AF_INET, 1, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 2); + test_config_parse_address_one("1.2.3.4/32", AF_INET, 1, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 32); + test_config_parse_address_one("1.2.3.4/33", AF_INET, 0, NULL, 0); + test_config_parse_address_one("1.2.3.4/-1", AF_INET, 0, NULL, 0); + + test_config_parse_address_one("", AF_INET6, 0, NULL, 0); + test_config_parse_address_one("/", AF_INET6, 0, NULL, 0); + test_config_parse_address_one("/8", AF_INET6, 0, NULL, 0); + test_config_parse_address_one("::1", AF_INET6, 1, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 0); + test_config_parse_address_one("::1/0", AF_INET6, 1, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 0); + test_config_parse_address_one("::1/1", AF_INET6, 1, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 1); + test_config_parse_address_one("::1/2", AF_INET6, 1, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 2); + test_config_parse_address_one("::1/32", AF_INET6, 1, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 32); + test_config_parse_address_one("::1/33", AF_INET6, 1, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 33); + test_config_parse_address_one("::1/64", AF_INET6, 1, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 64); + test_config_parse_address_one("::1/128", AF_INET6, 1, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 128); + test_config_parse_address_one("::1/129", AF_INET6, 0, NULL, 0); + test_config_parse_address_one("::1/-1", AF_INET6, 0, NULL, 0); +} + +static void test_config_parse_match_ifnames(void) { + _cleanup_strv_free_ char **names = NULL; + + assert_se(config_parse_match_ifnames("network", "filename", 1, "section", 1, "Name", 0, "!hoge hogehoge foo", &names, NULL) == 0); + assert_se(config_parse_match_ifnames("network", "filename", 1, "section", 1, "Name", 0, "!baz", &names, NULL) == 0); + assert_se(config_parse_match_ifnames("network", "filename", 1, "section", 1, "Name", 0, "aaa bbb ccc", &names, NULL) == 0); + + assert_se(strv_equal(names, STRV_MAKE("!hoge", "!hogehoge", "!foo", "!baz", "aaa", "bbb", "ccc"))); +} + +static void test_config_parse_match_strv(void) { + _cleanup_strv_free_ char **names = NULL; + + assert_se(config_parse_match_strv("network", "filename", 1, "section", 1, "Name", 0, "!hoge hogehoge foo", &names, NULL) == 0); + assert_se(config_parse_match_strv("network", "filename", 1, "section", 1, "Name", 0, "!baz", &names, NULL) == 0); + assert_se(config_parse_match_strv("network", "filename", 1, "section", 1, "Name", 0, + "KEY=val \"KEY2=val with space\" \"KEY3=val with \\\"quotation\\\"\"", &names, NULL) == 0); + + assert_se(strv_equal(names, + STRV_MAKE("!hoge", + "!hogehoge", + "!foo", + "!baz", + "KEY=val", + "KEY2=val with space", + "KEY3=val with \\quotation\\"))); +} + +int main(int argc, char **argv) { + log_parse_environment(); + log_open(); + + test_config_parse_duid_type(); + test_config_parse_duid_rawdata(); + test_config_parse_hwaddr(); + test_config_parse_address(); + test_config_parse_match_ifnames(); + test_config_parse_match_strv(); + + return 0; +} diff --git a/src/network/test-routing-policy-rule.c b/src/network/test-routing-policy-rule.c new file mode 100644 index 0000000..8d87cdf --- /dev/null +++ b/src/network/test-routing-policy-rule.c @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "fd-util.h" +#include "fileio.h" +#include "networkd-routing-policy-rule.h" +#include "string-util.h" +#include "tests.h" +#include "tmpfile-util.h" + +static void test_rule_serialization(const char *title, const char *ruleset, const char *expected) { + char pattern[] = "/tmp/systemd-test-routing-policy-rule.XXXXXX", + pattern2[] = "/tmp/systemd-test-routing-policy-rule.XXXXXX", + pattern3[] = "/tmp/systemd-test-routing-policy-rule.XXXXXX"; + const char *cmd; + int fd, fd2, fd3; + _cleanup_fclose_ FILE *f = NULL, *f2 = NULL, *f3 = NULL; + Set *rules = NULL; + _cleanup_free_ char *buf = NULL; + size_t buf_size; + + log_info("========== %s ==========", title); + log_info("put:\n%s\n", ruleset); + + fd = mkostemp_safe(pattern); + assert_se(fd >= 0); + assert_se(f = fdopen(fd, "a+")); + assert_se(write_string_stream(f, ruleset, 0) == 0); + + assert_se(routing_policy_load_rules(pattern, &rules) == 0); + + fd2 = mkostemp_safe(pattern2); + assert_se(fd2 >= 0); + assert_se(f2 = fdopen(fd2, "a+")); + + assert_se(routing_policy_serialize_rules(rules, f2) == 0); + assert_se(fflush_and_check(f2) == 0); + + assert_se(read_full_file(pattern2, &buf, &buf_size) == 0); + + log_info("got:\n%s", buf); + + fd3 = mkostemp_safe(pattern3); + assert_se(fd3 >= 0); + assert_se(f3 = fdopen(fd3, "w")); + assert_se(write_string_stream(f3, expected ?: ruleset, 0) == 0); + + cmd = strjoina("diff -u ", pattern3, " ", pattern2); + log_info("$ %s", cmd); + assert_se(system(cmd) == 0); + + set_free(rules); +} + +int main(int argc, char **argv) { + _cleanup_free_ char *p = NULL; + + test_setup_logging(LOG_DEBUG); + + test_rule_serialization("basic parsing", + "RULE=family=AF_INET from=1.2.3.4/32 to=2.3.4.5/32 tos=5 priority=10 fwmark=1/2 invert_rule=yes table=10", NULL); + + test_rule_serialization("ignored values", + "RULE=something=to=ignore from=1.2.3.4/32 from=1.2.3.4/32" + " \t to=2.3.4.5/24 to=2.3.4.5/32 tos=5 fwmark=2 fwmark=1 table=10 table=20", + "RULE=family=AF_INET from=1.2.3.4/32 to=2.3.4.5/32 tos=5 fwmark=1 invert_rule=no table=20"); + + test_rule_serialization("ipv6", + "RULE=family=AF_INET6 from=1::2/64 to=2::3/64 invert_rule=yes table=6", NULL); + + assert_se(asprintf(&p, "RULE=family=AF_INET6 from=1::2/64 to=2::3/64 invert_rule=no table=%d", RT_TABLE_MAIN) >= 0); + test_rule_serialization("default table", + "RULE=from=1::2/64 to=2::3/64", p); + + test_rule_serialization("incoming interface", + "RULE=from=1::2/64 to=2::3/64 table=1 iif=lo", + "RULE=family=AF_INET6 from=1::2/64 to=2::3/64 iif=lo invert_rule=no table=1"); + + test_rule_serialization("outgoing interface", + "RULE=family=AF_INET6 from=1::2/64 to=2::3/64 oif=eth0 invert_rule=no table=1", NULL); + + test_rule_serialization("freeing interface names", + "RULE=from=1::2/64 to=2::3/64 family=AF_INET6 iif=e0 iif=e1 oif=e0 oif=e1 table=1", + "RULE=family=AF_INET6 from=1::2/64 to=2::3/64 iif=e1 oif=e1 invert_rule=no table=1"); + + test_rule_serialization("ignoring invalid family", + "RULE=from=1::2/64 to=2::3/64 family=AF_UNSEPC family=AF_INET table=1", + "RULE=family=AF_INET6 from=1::2/64 to=2::3/64 invert_rule=no table=1"); + + return 0; +} diff --git a/src/network/wait-online/link.c b/src/network/wait-online/link.c new file mode 100644 index 0000000..529fc9f --- /dev/null +++ b/src/network/wait-online/link.c @@ -0,0 +1,153 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-network.h" + +#include "alloc-util.h" +#include "hashmap.h" +#include "link.h" +#include "manager.h" +#include "string-util.h" + +int link_new(Manager *m, Link **ret, int ifindex, const char *ifname) { + _cleanup_(link_freep) Link *l = NULL; + _cleanup_free_ char *n = NULL; + int r; + + assert(m); + assert(ifindex > 0); + + r = hashmap_ensure_allocated(&m->links, NULL); + if (r < 0) + return r; + + r = hashmap_ensure_allocated(&m->links_by_name, &string_hash_ops); + if (r < 0) + return r; + + n = strdup(ifname); + if (!n) + return -ENOMEM; + + l = new(Link, 1); + if (!l) + return -ENOMEM; + + *l = (Link) { + .manager = m, + .ifname = TAKE_PTR(n), + .ifindex = ifindex, + .required_operstate = LINK_OPERSTATE_RANGE_DEFAULT, + }; + + r = hashmap_put(m->links_by_name, l->ifname, l); + if (r < 0) + return r; + + r = hashmap_put(m->links, INT_TO_PTR(ifindex), l); + if (r < 0) + return r; + + if (ret) + *ret = l; + + TAKE_PTR(l); + return 0; +} + +Link *link_free(Link *l) { + + if (!l) + return NULL; + + if (l->manager) { + hashmap_remove(l->manager->links, INT_TO_PTR(l->ifindex)); + hashmap_remove(l->manager->links_by_name, l->ifname); + } + + free(l->state); + free(l->ifname); + return mfree(l); + } + +int link_update_rtnl(Link *l, sd_netlink_message *m) { + const char *ifname; + int r; + + assert(l); + assert(l->manager); + assert(m); + + r = sd_rtnl_message_link_get_flags(m, &l->flags); + if (r < 0) + return r; + + r = sd_netlink_message_read_string(m, IFLA_IFNAME, &ifname); + if (r < 0) + return r; + + if (!streq(l->ifname, ifname)) { + char *new_ifname; + + new_ifname = strdup(ifname); + if (!new_ifname) + return -ENOMEM; + + assert_se(hashmap_remove(l->manager->links_by_name, l->ifname) == l); + free_and_replace(l->ifname, new_ifname); + + r = hashmap_put(l->manager->links_by_name, l->ifname, l); + if (r < 0) + return r; + } + + return 0; +} + +int link_update_monitor(Link *l) { + _cleanup_free_ char *operstate = NULL, *required_operstate = NULL, *state = NULL; + int r, ret = 0; + + assert(l); + assert(l->ifname); + + r = sd_network_link_get_required_for_online(l->ifindex); + if (r < 0) + ret = log_link_debug_errno(l, r, "Failed to determine whether the link is required for online or not, " + "ignoring: %m"); + else + l->required_for_online = r > 0; + + r = sd_network_link_get_required_operstate_for_online(l->ifindex, &required_operstate); + if (r < 0) + ret = log_link_debug_errno(l, r, "Failed to get required operational state, ignoring: %m"); + else if (isempty(required_operstate)) + l->required_operstate = LINK_OPERSTATE_RANGE_DEFAULT; + else { + r = parse_operational_state_range(required_operstate, &l->required_operstate); + if (r < 0) + ret = log_link_debug_errno(l, SYNTHETIC_ERRNO(EINVAL), + "Failed to parse required operational state, ignoring: %m"); + } + + r = sd_network_link_get_operational_state(l->ifindex, &operstate); + if (r < 0) + ret = log_link_debug_errno(l, r, "Failed to get operational state, ignoring: %m"); + else { + LinkOperationalState s; + + s = link_operstate_from_string(operstate); + if (s < 0) + ret = log_link_debug_errno(l, SYNTHETIC_ERRNO(EINVAL), + "Failed to parse operational state, ignoring: %m"); + else + l->operational_state = s; + } + + r = sd_network_link_get_setup_state(l->ifindex, &state); + if (r < 0) + ret = log_link_debug_errno(l, r, "Failed to get setup state, ignoring: %m"); + else + free_and_replace(l->state, state); + + return ret; +} diff --git a/src/network/wait-online/link.h b/src/network/wait-online/link.h new file mode 100644 index 0000000..3aa8357 --- /dev/null +++ b/src/network/wait-online/link.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-netlink.h" + +#include "log-link.h" +#include "network-util.h" + +typedef struct Link Link; +typedef struct Manager Manager; + +struct Link { + Manager *manager; + + int ifindex; + char *ifname; + unsigned flags; + + bool required_for_online; + LinkOperationalStateRange required_operstate; + LinkOperationalState operational_state; + char *state; +}; + +int link_new(Manager *m, Link **ret, int ifindex, const char *ifname); +Link *link_free(Link *l); +int link_update_rtnl(Link *l, sd_netlink_message *m); +int link_update_monitor(Link *l); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_free); diff --git a/src/network/wait-online/manager.c b/src/network/wait-online/manager.c new file mode 100644 index 0000000..79994bd --- /dev/null +++ b/src/network/wait-online/manager.c @@ -0,0 +1,369 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <netinet/ether.h> +#include <linux/if.h> +#include <fnmatch.h> + +#include "alloc-util.h" +#include "link.h" +#include "manager.h" +#include "netlink-util.h" +#include "network-internal.h" +#include "strv.h" +#include "time-util.h" +#include "util.h" + +static bool manager_ignore_link(Manager *m, Link *link) { + assert(m); + assert(link); + + /* always ignore the loopback interface */ + if (link->flags & IFF_LOOPBACK) + return true; + + /* if interfaces are given on the command line, ignore all others */ + if (m->interfaces && !hashmap_contains(m->interfaces, link->ifname)) + return true; + + if (!link->required_for_online) + return true; + + /* ignore interfaces we explicitly are asked to ignore */ + return strv_fnmatch(m->ignore, link->ifname); +} + +static int manager_link_is_online(Manager *m, Link *l, LinkOperationalStateRange s) { + /* This returns the following: + * -EAGAIN: not processed by udev or networkd + * 0: operstate is not enough + * 1: online */ + + if (!l->state) + return log_link_debug_errno(l, SYNTHETIC_ERRNO(EAGAIN), + "link has not yet been processed by udev"); + + if (STR_IN_SET(l->state, "configuring", "pending")) + return log_link_debug_errno(l, SYNTHETIC_ERRNO(EAGAIN), + "link is being processed by networkd"); + + if (s.min < 0) + s.min = m->required_operstate.min >= 0 ? m->required_operstate.min + : l->required_operstate.min; + + if (s.max < 0) + s.max = m->required_operstate.max >= 0 ? m->required_operstate.max + : l->required_operstate.max; + + if (l->operational_state < s.min || l->operational_state > s.max) { + log_link_debug(l, "Operational state '%s' is not in range ['%s':'%s']", + link_operstate_to_string(l->operational_state), + link_operstate_to_string(s.min), link_operstate_to_string(s.max)); + return 0; + } + + return 1; +} + +bool manager_configured(Manager *m) { + bool one_ready = false; + const char *ifname; + void *p; + Link *l; + int r; + + if (!hashmap_isempty(m->interfaces)) { + /* wait for all the links given on the command line to appear */ + HASHMAP_FOREACH_KEY(p, ifname, m->interfaces) { + LinkOperationalStateRange *range = p; + + l = hashmap_get(m->links_by_name, ifname); + if (!l && range->min == LINK_OPERSTATE_MISSING) { + one_ready = true; + continue; + } + + if (!l) { + log_debug("still waiting for %s", ifname); + if (!m->any) + return false; + continue; + } + + if (manager_link_is_online(m, l, *range) <= 0) { + if (!m->any) + return false; + continue; + } + + one_ready = true; + } + + /* all interfaces given by the command line are online, or + * one of the specified interfaces is online. */ + return one_ready; + } + + /* wait for all links networkd manages to be in admin state 'configured' + * and at least one link to gain a carrier */ + HASHMAP_FOREACH(l, m->links) { + if (manager_ignore_link(m, l)) { + log_link_debug(l, "link is ignored"); + continue; + } + + r = manager_link_is_online(m, l, + (LinkOperationalStateRange) { _LINK_OPERSTATE_INVALID, + _LINK_OPERSTATE_INVALID }); + if (r < 0 && !m->any) + return false; + if (r > 0) + /* we wait for at least one link to be ready, + * regardless of who manages it */ + one_ready = true; + } + + return one_ready; +} + +static int manager_process_link(sd_netlink *rtnl, sd_netlink_message *mm, void *userdata) { + Manager *m = userdata; + uint16_t type; + Link *l; + const char *ifname; + int ifindex, r; + + assert(rtnl); + assert(m); + assert(mm); + + r = sd_netlink_message_get_type(mm, &type); + if (r < 0) { + log_warning_errno(r, "rtnl: Could not get message type, ignoring: %m"); + return 0; + } + + r = sd_rtnl_message_link_get_ifindex(mm, &ifindex); + if (r < 0) { + log_warning_errno(r, "rtnl: Could not get ifindex from link, ignoring: %m"); + return 0; + } else if (ifindex <= 0) { + log_warning("rtnl: received link message with invalid ifindex %d, ignoring", ifindex); + return 0; + } + + r = sd_netlink_message_read_string(mm, IFLA_IFNAME, &ifname); + if (r < 0) { + log_warning_errno(r, "rtnl: Received link message without ifname, ignoring: %m"); + return 0; + } + + l = hashmap_get(m->links, INT_TO_PTR(ifindex)); + + switch (type) { + + case RTM_NEWLINK: + if (!l) { + log_debug("Found link %i", ifindex); + + r = link_new(m, &l, ifindex, ifname); + if (r < 0) + return log_error_errno(r, "Failed to create link object: %m"); + } + + r = link_update_rtnl(l, mm); + if (r < 0) + log_link_warning_errno(l, r, "Failed to process RTNL link message, ignoring: %m"); + + r = link_update_monitor(l); + if (r < 0 && r != -ENODATA) + log_link_warning_errno(l, r, "Failed to update link state, ignoring: %m"); + + break; + + case RTM_DELLINK: + if (l) { + log_link_debug(l, "Removing link"); + link_free(l); + } + + break; + } + + return 0; +} + +static int on_rtnl_event(sd_netlink *rtnl, sd_netlink_message *mm, void *userdata) { + Manager *m = userdata; + int r; + + r = manager_process_link(rtnl, mm, m); + if (r < 0) + return r; + + if (manager_configured(m)) + sd_event_exit(m->event, 0); + + return 1; +} + +static int manager_rtnl_listen(Manager *m) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + sd_netlink_message *i; + int r; + + assert(m); + + /* First, subscribe to interfaces coming and going */ + r = sd_netlink_open(&m->rtnl); + if (r < 0) + return r; + + r = sd_netlink_attach_event(m->rtnl, m->event, 0); + if (r < 0) + return r; + + r = sd_netlink_add_match(m->rtnl, NULL, RTM_NEWLINK, on_rtnl_event, NULL, m, "wait-online-on-NEWLINK"); + if (r < 0) + return r; + + r = sd_netlink_add_match(m->rtnl, NULL, RTM_DELLINK, on_rtnl_event, NULL, m, "wait-online-on-DELLINK"); + if (r < 0) + return r; + + /* Then, enumerate all links */ + r = sd_rtnl_message_new_link(m->rtnl, &req, RTM_GETLINK, 0); + if (r < 0) + return r; + + r = sd_netlink_message_request_dump(req, true); + if (r < 0) + return r; + + r = sd_netlink_call(m->rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (i = reply; i; i = sd_netlink_message_next(i)) { + r = manager_process_link(m->rtnl, i, m); + if (r < 0) + return r; + } + + return r; +} + +static int on_network_event(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + Link *l; + int r; + + assert(m); + + sd_network_monitor_flush(m->network_monitor); + + HASHMAP_FOREACH(l, m->links) { + r = link_update_monitor(l); + if (r < 0 && r != -ENODATA) + log_link_warning_errno(l, r, "Failed to update link state, ignoring: %m"); + } + + if (manager_configured(m)) + sd_event_exit(m->event, 0); + + return 0; +} + +static int manager_network_monitor_listen(Manager *m) { + int r, fd, events; + + assert(m); + + r = sd_network_monitor_new(&m->network_monitor, NULL); + if (r < 0) + return r; + + fd = sd_network_monitor_get_fd(m->network_monitor); + if (fd < 0) + return fd; + + events = sd_network_monitor_get_events(m->network_monitor); + if (events < 0) + return events; + + r = sd_event_add_io(m->event, &m->network_monitor_event_source, + fd, events, &on_network_event, m); + if (r < 0) + return r; + + return 0; +} + +int manager_new(Manager **ret, Hashmap *interfaces, char **ignore, + LinkOperationalStateRange required_operstate, + bool any, usec_t timeout) { + _cleanup_(manager_freep) Manager *m = NULL; + int r; + + assert(ret); + + m = new(Manager, 1); + if (!m) + return -ENOMEM; + + *m = (Manager) { + .interfaces = interfaces, + .ignore = ignore, + .required_operstate = required_operstate, + .any = any, + }; + + r = sd_event_default(&m->event); + if (r < 0) + return r; + + (void) sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL); + (void) sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL); + + if (timeout > 0) { + usec_t usec; + + usec = now(clock_boottime_or_monotonic()) + timeout; + + r = sd_event_add_time(m->event, NULL, clock_boottime_or_monotonic(), usec, 0, NULL, INT_TO_PTR(-ETIMEDOUT)); + if (r < 0) + return r; + } + + sd_event_set_watchdog(m->event, true); + + r = manager_network_monitor_listen(m); + if (r < 0) + return r; + + r = manager_rtnl_listen(m); + if (r < 0) + return r; + + *ret = TAKE_PTR(m); + + return 0; +} + +void manager_free(Manager *m) { + if (!m) + return; + + hashmap_free_with_destructor(m->links, link_free); + hashmap_free(m->links_by_name); + + sd_event_source_unref(m->network_monitor_event_source); + sd_network_monitor_unref(m->network_monitor); + + sd_event_source_unref(m->rtnl_event_source); + sd_netlink_unref(m->rtnl); + + sd_event_unref(m->event); + free(m); + + return; +} diff --git a/src/network/wait-online/manager.h b/src/network/wait-online/manager.h new file mode 100644 index 0000000..f5e8353 --- /dev/null +++ b/src/network/wait-online/manager.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-event.h" +#include "sd-netlink.h" +#include "sd-network.h" + +#include "hashmap.h" +#include "network-util.h" +#include "time-util.h" + +typedef struct Manager Manager; +typedef struct Link Link; + +struct Manager { + Hashmap *links; + Hashmap *links_by_name; + + /* Do not free the two members below. */ + Hashmap *interfaces; + char **ignore; + + LinkOperationalStateRange required_operstate; + bool any; + + sd_netlink *rtnl; + sd_event_source *rtnl_event_source; + + sd_network_monitor *network_monitor; + sd_event_source *network_monitor_event_source; + + sd_event *event; +}; + +void manager_free(Manager *m); +int manager_new(Manager **ret, Hashmap *interfaces, char **ignore, + LinkOperationalStateRange required_operstate, + bool any, usec_t timeout); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); + +bool manager_configured(Manager *m); diff --git a/src/network/wait-online/wait-online.c b/src/network/wait-online/wait-online.c new file mode 100644 index 0000000..c2bdcd4 --- /dev/null +++ b/src/network/wait-online/wait-online.c @@ -0,0 +1,224 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <getopt.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "sd-daemon.h" + +#include "daemon-util.h" +#include "main-func.h" +#include "manager.h" +#include "pretty-print.h" +#include "signal-util.h" +#include "socket-util.h" +#include "strv.h" + +static bool arg_quiet = false; +static usec_t arg_timeout = 120 * USEC_PER_SEC; +static Hashmap *arg_interfaces = NULL; +static char **arg_ignore = NULL; +static LinkOperationalStateRange arg_required_operstate = { _LINK_OPERSTATE_INVALID, _LINK_OPERSTATE_INVALID }; +static bool arg_any = false; + +STATIC_DESTRUCTOR_REGISTER(arg_interfaces, hashmap_free_free_freep); +STATIC_DESTRUCTOR_REGISTER(arg_ignore, strv_freep); + +static int help(void) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("systemd-networkd-wait-online.service", "8", &link); + if (r < 0) + return log_oom(); + + printf("%s [OPTIONS...]\n\n" + "Block until network is configured.\n\n" + " -h --help Show this help\n" + " --version Print version string\n" + " -q --quiet Do not show status information\n" + " -i --interface=INTERFACE[:MIN_OPERSTATE[:MAX_OPERSTATE]]\n" + " Block until at least these interfaces have appeared\n" + " --ignore=INTERFACE Don't take these interfaces into account\n" + " -o --operational-state=MIN_OPERSTATE[:MAX_OPERSTATE]\n" + " Required operational state\n" + " --any Wait until at least one of the interfaces is online\n" + " --timeout=SECS Maximum time to wait for network connectivity\n" + "\nSee the %s for details.\n" + , program_invocation_short_name + , link + ); + + return 0; +} + +static int parse_interface_with_operstate_range(const char *str) { + _cleanup_free_ char *ifname = NULL; + _cleanup_free_ LinkOperationalStateRange *range; + const char *p; + int r; + + assert(str); + + range = new(LinkOperationalStateRange, 1); + if (!range) + return log_oom(); + + p = strchr(str, ':'); + if (p) { + r = parse_operational_state_range(p + 1, range); + if (r < 0) + log_error_errno(r, "Invalid operational state range '%s'", p + 1); + + ifname = strndup(optarg, p - optarg); + } else { + range->min = _LINK_OPERSTATE_INVALID; + range->max = _LINK_OPERSTATE_INVALID; + ifname = strdup(str); + } + if (!ifname) + return log_oom(); + + if (!ifname_valid(ifname)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid interface name '%s'", ifname); + + r = hashmap_ensure_allocated(&arg_interfaces, &string_hash_ops); + if (r < 0) + return log_oom(); + + r = hashmap_put(arg_interfaces, ifname, TAKE_PTR(range)); + if (r < 0) + return log_error_errno(r, "Failed to store interface name: %m"); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Interface name %s is already specified", ifname); + + TAKE_PTR(ifname); + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_IGNORE, + ARG_ANY, + ARG_TIMEOUT, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "quiet", no_argument, NULL, 'q' }, + { "interface", required_argument, NULL, 'i' }, + { "ignore", required_argument, NULL, ARG_IGNORE }, + { "operational-state", required_argument, NULL, 'o' }, + { "any", no_argument, NULL, ARG_ANY }, + { "timeout", required_argument, NULL, ARG_TIMEOUT }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hi:qo:", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case 'q': + arg_quiet = true; + break; + + case ARG_VERSION: + return version(); + + case 'i': + r = parse_interface_with_operstate_range(optarg); + if (r < 0) + return r; + break; + + case ARG_IGNORE: + if (strv_extend(&arg_ignore, optarg) < 0) + return log_oom(); + + break; + + case 'o': { + LinkOperationalStateRange range; + + r = parse_operational_state_range(optarg, &range); + if (r < 0) + return log_error_errno(r, "Invalid operational state range '%s'", optarg); + + arg_required_operstate = range; + + break; + } + case ARG_ANY: + arg_any = true; + break; + + case ARG_TIMEOUT: + r = parse_sec(optarg, &arg_timeout); + if (r < 0) + return r; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +static int run(int argc, char *argv[]) { + _cleanup_(manager_freep) Manager *m = NULL; + _cleanup_(notify_on_cleanup) const char *notify_message = NULL; + int r; + + log_setup_service(); + + umask(0022); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + if (arg_quiet) + log_set_max_level(LOG_ERR); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); + + r = manager_new(&m, arg_interfaces, arg_ignore, arg_required_operstate, arg_any, arg_timeout); + if (r < 0) + return log_error_errno(r, "Could not create manager: %m"); + + if (manager_configured(m)) + goto success; + + notify_message = notify_start("READY=1\n" + "STATUS=Waiting for network connections...", + "STATUS=Failed to wait for network connectivity..."); + + r = sd_event_loop(m->event); + if (r < 0) + return log_error_errno(r, "Event loop failed: %m"); + +success: + notify_message = "STATUS=All interfaces configured..."; + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); |