]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
network-generator: introduce new tool systemd-network-generator
authorYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 12 Jul 2019 18:35:04 +0000 (03:35 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Sun, 14 Jul 2019 00:37:15 +0000 (09:37 +0900)
The new tool parses dracut's command line options and generates
relevant .network, .netdev, and .link files.

meson.build
src/network/generator/main.c [new file with mode: 0644]
src/network/generator/network-generator.c [new file with mode: 0644]
src/network/generator/network-generator.h [new file with mode: 0644]
src/network/meson.build

index 93d69146275cb7d9ab18bc6cc20c007f45a7dbd6..979b1858677a8ff12086edf958a361cc235a6c5b 100644 (file)
@@ -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 (file)
index 0000000..f5f9b04
--- /dev/null
@@ -0,0 +1,206 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#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-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 (file)
index 0000000..0b5af33
--- /dev/null
@@ -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=<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, *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;
+
+        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 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;
+        Iterator i;
+        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, 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 (file)
index 0000000..3d75b13
--- /dev/null
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#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);
index 32317c7c9f8b5ba367188141a75d7efc2dedf92d..514429a16988d28dbe7f5188c8194a44a8c2de95 100644 (file)
@@ -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