From 426c1d385212e11cb497e2a82e0d88f8737307bd Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 13 Jul 2019 03:35:04 +0900 Subject: [PATCH] network-generator: introduce new tool systemd-network-generator The new tool parses dracut's command line options and generates relevant .network, .netdev, and .link files. --- meson.build | 8 + src/network/generator/main.c | 206 ++++ src/network/generator/network-generator.c | 1234 +++++++++++++++++++++ src/network/generator/network-generator.h | 108 ++ src/network/meson.build | 6 + 5 files changed, 1562 insertions(+) create mode 100644 src/network/generator/main.c create mode 100644 src/network/generator/network-generator.c create mode 100644 src/network/generator/network-generator.h diff --git a/meson.build b/meson.build index 93d6914627..979b185867 100644 --- a/meson.build +++ b/meson.build @@ -2710,6 +2710,14 @@ if conf.get('ENABLE_NETWORKD') == 1 install : true, install_dir : rootbindir) public_programs += exe + + executable('systemd-network-generator', + network_generator_sources, + include_directories : includes, + link_with : [libshared], + install_rpath : rootlibexecdir, + install : true, + install_dir : rootlibexecdir) endif executable('systemd-sulogin-shell', diff --git a/src/network/generator/main.c b/src/network/generator/main.c new file mode 100644 index 0000000000..f5f9b04afc --- /dev/null +++ b/src/network/generator/main.c @@ -0,0 +1,206 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include + +#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-initrd-%s.network", + isempty(network->ifname) ? "99" : "98", + 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, "98-initrd-%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, "98-initrd-%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; + Iterator i; + int k, r = 0; + 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, i) { + k = network_save(network, p); + if (k < 0 && r >= 0) + r = k; + } + + HASHMAP_FOREACH(netdev, context->netdevs_by_name, i) { + k = netdev_save(netdev, p); + if (k < 0 && r >= 0) + r = k; + } + + HASHMAP_FOREACH(link, context->links_by_name, i) { + 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" + , 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 0000000000..0b5af33566 --- /dev/null +++ b/src/network/generator/network-generator.c @@ -0,0 +1,1234 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#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=:{dhcp|on|any|dhcp6|auto6}[:[][:]] + ip=:[]:::::{none|off|dhcp|on|any|dhcp6|auto6|ibft}[:[][:]] + ip=:[]:::::{none|off|dhcp|on|any|dhcp6|auto6|ibft}[:[][:]] + rd.route=/:[:] + nameserver= [nameserver= ...] + rd.peerdns=0 + + # .link + ifname=: + + # .netdev + vlan=: + bond=[::[:[:]]] + team=: # not supported + bridge=: + + # ignored + bootdev= + BOOTIF= + 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; + + /* [][:] */ + + 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, *ifname, *dhcp_type, *dns, *p; + unsigned char prefixlen; + int r; + + /* ip=:[]:::::{none|off|dhcp|on|any|dhcp6|auto6|ibft}[:[][:]] + * ip=:[]:::::{none|off|dhcp|on|any|dhcp6|auto6|ibft}[:[][:]] */ + + 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; + + 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 [][:] */ + r = parse_cmdline_ip_mtu_mac(context, ifname, AF_UNSPEC, p + 1); + if (r >= 0) + return 0; + + /* Next, try [][:] */ + 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=:{dhcp|on|any|dhcp6|auto6}[:[][:]] */ + + 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=/:[:] */ + + 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 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=: */ + + 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; + Iterator i; + int r; + + assert(context); + + /* Copy settings about the following options + rd.route=/:[:] + nameserver= [nameserver= ...] + 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, i) { + 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 0000000000..3d75b132de --- /dev/null +++ b/src/network/generator/network-generator.h @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include +#include + +#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/meson.build b/src/network/meson.build index 32317c7c9f..514429a169 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -114,6 +114,12 @@ systemd_networkd_wait_online_sources = files(''' networkctl_sources = files('networkctl.c') +network_generator_sources = files(''' + generator/main.c + generator/network-generator.c + generator/network-generator.h +'''.split()) + network_include_dir = include_directories('.') if conf.get('ENABLE_NETWORKD') == 1