diff --git a/src/network/generator/network-generator.c b/src/network/generator/network-generator.c index ec6652028b..1c3d9a7e05 100644 --- a/src/network/generator/network-generator.c +++ b/src/network/generator/network-generator.c @@ -14,6 +14,7 @@ #include "string-table.h" #include "string-util.h" #include "strv.h" +#include "vlan-util.h" /* # .network @@ -95,7 +96,7 @@ static const char * const networkd_link_local_type_table[_DHCP_TYPE_MAX] = { DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(networkd_link_local_type, DHCPType); -static Address *address_free(Address *address) { +static Address* address_free(Address *address) { if (!address) return NULL; @@ -105,8 +106,14 @@ static Address *address_free(Address *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) { +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); @@ -133,7 +140,7 @@ static int address_new(Network *network, int family, unsigned char prefixlen, return 0; } -static Route *route_free(Route *route) { +static Route* route_free(Route *route) { if (!route) return NULL; @@ -143,8 +150,14 @@ static Route *route_free(Route *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) { +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); @@ -171,7 +184,7 @@ static int route_new(Network *network, int family, unsigned char prefixlen, return 0; } -static Network *network_free(Network *network) { +static Network* network_free(Network *network) { Address *address; Route *route; @@ -181,7 +194,7 @@ static Network *network_free(Network *network) { free(network->ifname); free(network->hostname); strv_free(network->dns); - free(network->vlan); + strv_free(network->vlan); free(network->bridge); free(network->bond); @@ -202,6 +215,7 @@ static int network_new(Context *context, const char *name, Network **ret) { int r; assert(context); + assert(name); if (!isempty(name) && !ifname_valid(name)) return -EINVAL; @@ -231,11 +245,28 @@ static int network_new(Context *context, const char *name, Network **ret) { return 0; } -Network *network_get(Context *context, const char *ifname) { +Network* network_get(Context *context, const char *ifname) { + assert(context); + assert(ifname); return hashmap_get(context->networks_by_name, ifname); } -static NetDev *netdev_free(NetDev *netdev) { +static int network_acquire(Context *context, const char *ifname, Network **ret) { + Network *network; + + assert(context); + assert(ifname); + + network = network_get(context, ifname); + if (!network) + return network_new(context, ifname, ret); + + if (ret) + *ret = network; + return 0; +} + +static NetDev* netdev_free(NetDev *netdev) { if (!netdev) return NULL; @@ -253,6 +284,7 @@ static int netdev_new(Context *context, const char *_kind, const char *_ifname, assert(context); assert(_kind); + assert(_ifname); if (!ifname_valid(_ifname)) return -EINVAL; @@ -285,11 +317,32 @@ static int netdev_new(Context *context, const char *_kind, const char *_ifname, return 0; } -NetDev *netdev_get(Context *context, const char *ifname) { +NetDev* netdev_get(Context *context, const char *ifname) { + assert(context); + assert(ifname); return hashmap_get(context->netdevs_by_name, ifname); } -static Link *link_free(Link *link) { +static int netdev_acquire(Context *context, const char *kind, const char *name, NetDev **ret) { + NetDev *netdev; + + assert(context); + assert(kind); + assert(name); + + netdev = netdev_get(context, name); + if (!netdev) + return netdev_new(context, kind, name, ret); + + if (!streq_ptr(netdev->kind, kind)) + return -EEXIST; /* conflicting netdev already exists. */ + + if (ret) + *ret = netdev; + return 0; +} + +static Link* link_free(Link *link) { if (!link) return NULL; @@ -356,7 +409,7 @@ static int link_new( return 0; } -Link *link_get(Context *context, const char *filename) { +Link* link_get(Context *context, const char *filename) { assert(context); assert(filename); return hashmap_get(context->links_by_filename, filename); @@ -375,12 +428,9 @@ static int network_set_dhcp_type(Context *context, const char *ifname, const cha if (t < 0) return log_debug_errno(t, "Invalid DHCP type '%s'", dhcp_type); - network = network_get(context, ifname); - if (!network) { - r = network_new(context, ifname, &network); - if (r < 0) - return log_debug_errno(r, "Failed to create network for '%s': %m", ifname); - } + r = network_acquire(context, ifname, &network); + if (r < 0) + return log_debug_errno(r, "Failed to acquire network for '%s': %m", ifname); network->dhcp_type = t; return 0; @@ -388,13 +438,20 @@ static int network_set_dhcp_type(Context *context, const char *ifname, const cha static int network_set_hostname(Context *context, const char *ifname, const char *hostname) { Network *network; + int r; assert(context); assert(ifname); - network = network_get(context, ifname); - if (!network) - return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), "No network found for '%s'", ifname); + if (isempty(hostname)) + return 0; + + if (!hostname_is_valid(hostname, 0)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid hostname '%s'.", hostname); + + r = network_acquire(context, ifname, &network); + if (r < 0) + return log_debug_errno(r, "Failed to acquire network for '%s': %m", ifname); return free_and_strdup(&network->hostname, hostname); } @@ -409,9 +466,9 @@ static int network_set_mtu(Context *context, const char *ifname, const char *mtu if (isempty(mtu)) return 0; - network = network_get(context, ifname); - if (!network) - return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), "No network found for '%s'", ifname); + r = network_acquire(context, ifname, &network); + if (r < 0) + return log_debug_errno(r, "Failed to acquire network for '%s': %m", ifname); r = parse_mtu(AF_UNSPEC, mtu, &network->mtu); if (r < 0) @@ -426,22 +483,31 @@ static int network_set_mac_address(Context *context, const char *ifname, const c assert(context); assert(ifname); - assert(mac); - network = network_get(context, ifname); - if (!network) - return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), "No network found for '%s'", ifname); + if (isempty(mac)) + return 0; + + r = network_acquire(context, ifname, &network); + if (r < 0) + return log_debug_errno(r, "Failed to acquire network for '%s': %m", ifname); r = parse_ether_addr(mac, &network->mac); if (r < 0) return log_debug_errno(r, "Invalid MAC address '%s' for '%s'", mac, ifname); - return r; + return 0; } -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) { +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; + int r; assert(context); assert(ifname); @@ -451,15 +517,21 @@ static int network_set_address(Context *context, const char *ifname, int family, if (!in_addr_is_set(family, addr)) return 0; - network = network_get(context, ifname); - if (!network) - return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), "No network found for '%s'", ifname); + r = network_acquire(context, ifname, &network); + if (r < 0) + return log_debug_errno(r, "Failed to acquire network for '%s': %m", ifname); 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) { +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; @@ -471,39 +543,30 @@ static int network_set_route(Context *context, const char *ifname, int family, u !(gateway && in_addr_is_set(family, gateway))) return 0; - network = network_get(context, ifname); - if (!network) { - r = network_new(context, ifname, &network); - if (r < 0) - return log_debug_errno(r, "Failed to create network for '%s': %m", ifname); - } + r = network_acquire(context, ifname, &network); + if (r < 0) + return log_debug_errno(r, "Failed to acquire network for '%s': %m", ifname); return route_new(network, family, prefixlen, dest, gateway, NULL); } -static int network_set_dns(Context *context, const char *ifname, int family, const char *dns) { - union in_addr_union a; +static int network_set_dns(Context *context, const char *ifname, const char *dns) { Network *network; int r; assert(context); assert(ifname); - assert(IN_SET(family, AF_UNSPEC, AF_INET, AF_INET6)); - assert(dns); - if (family == AF_UNSPEC) - r = in_addr_from_string_auto(dns, &family, &a); - else - r = in_addr_from_string(family, dns, &a); + if (isempty(dns)) + return 0; + + r = in_addr_from_string_auto(dns, NULL, NULL); if (r < 0) return log_debug_errno(r, "Invalid DNS address '%s' for '%s'", dns, ifname); - network = network_get(context, ifname); - if (!network) { - r = network_new(context, ifname, &network); - if (r < 0) - return log_debug_errno(r, "Failed to create network for '%s': %m", ifname); - } + r = network_acquire(context, ifname, &network); + if (r < 0) + return log_debug_errno(r, "Failed to acquire network for '%s': %m", ifname); return strv_extend(&network->dns, dns); } @@ -515,12 +578,9 @@ static int network_set_dhcp_use_dns(Context *context, const char *ifname, bool v assert(context); assert(ifname); - network = network_get(context, ifname); - if (!network) { - r = network_new(context, ifname, &network); - if (r < 0) - return log_debug_errno(r, "Failed to create network for '%s': %m", ifname); - } + r = network_acquire(context, ifname, &network); + if (r < 0) + return log_debug_errno(r, "Failed to create network for '%s': %m", ifname); network->dhcp_use_dns = value; @@ -532,16 +592,15 @@ static int network_set_vlan(Context *context, const char *ifname, const char *va int r; assert(context); - assert(ifname); - network = network_get(context, ifname); - if (!network) { - r = network_new(context, ifname, &network); - if (r < 0) - return log_debug_errno(r, "Failed to create network for '%s': %m", ifname); - } + if (isempty(ifname)) + return 0; - return free_and_strdup(&network->vlan, value); + r = network_acquire(context, ifname, &network); + if (r < 0) + return log_debug_errno(r, "Failed to acquire network for '%s': %m", ifname); + + return strv_extend(&network->vlan, value); } static int network_set_bridge(Context *context, const char *ifname, const char *value) { @@ -549,14 +608,13 @@ static int network_set_bridge(Context *context, const char *ifname, const char * int r; assert(context); - assert(ifname); - network = network_get(context, ifname); - if (!network) { - r = network_new(context, ifname, &network); - if (r < 0) - return log_debug_errno(r, "Failed to create network for '%s': %m", ifname); - } + if (isempty(ifname)) + return 0; + + r = network_acquire(context, ifname, &network); + if (r < 0) + return log_debug_errno(r, "Failed to acquire network for '%s': %m", ifname); return free_and_strdup(&network->bridge, value); } @@ -566,171 +624,144 @@ static int network_set_bond(Context *context, const char *ifname, const char *va int r; assert(context); - assert(ifname); - network = network_get(context, ifname); - if (!network) { - r = network_new(context, ifname, &network); - if (r < 0) - return log_debug_errno(r, "Failed to create network for '%s': %m", ifname); - } + if (isempty(ifname)) + return 0; + + r = network_acquire(context, ifname, &network); + if (r < 0) + return log_debug_errno(r, "Failed to acquire network for '%s': %m", ifname); return free_and_strdup(&network->bond, value); } static int parse_cmdline_ip_mtu_mac(Context *context, const char *ifname, const char *value) { - const char *mtu, *p; + _cleanup_free_ char *mtu = NULL; int r; assert(context); assert(ifname); - assert(value); /* [][:] */ - p = strchr(value, ':'); - if (!p) - mtu = value; - else - mtu = strndupa_safe(value, p - value); + const char *p = value; + r = extract_first_word(&p, &mtu, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r <= 0) + return r; r = network_set_mtu(context, ifname, mtu); if (r < 0) return r; - if (!p || isempty(p + 1)) - return 0; - - r = network_set_mac_address(context, ifname, p + 1); - if (r < 0) - return r; - - return 0; + return network_set_mac_address(context, ifname, p); } -static int parse_ip_address_one(int family, const char **value, union in_addr_union *ret) { - const char *p, *q, *buf; +static int extract_ip_address_str(int family, const char **ptr, char **ret) { + _cleanup_free_ char *buf = NULL; + const char *p; int r; - assert(IN_SET(family, AF_INET, AF_INET6)); - assert(value); + assert(IN_SET(family, AF_UNSPEC, AF_INET, AF_INET6)); + assert(ptr); assert(ret); - p = ASSERT_PTR(*value); - - if (p[0] == ':') { - *value = p + 1; + if (isempty(*ptr)) { + *ptr = NULL; + *ret = NULL; return 0; } - if (family == AF_INET6) { - if (p[0] != '[') - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IPv6 address '%s'", p); + if (**ptr != '[') + return extract_first_word(ptr, ret, ":", EXTRACT_DONT_COALESCE_SEPARATORS); - q = strchr(p + 1, ']'); - if (!q) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IPv6 address '%s'", p); + if (family == AF_INET) + return -EINVAL; - if (q[1] != ':') - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IPv6 address '%s'", p); + p = *ptr + 1; + r = extract_first_word(&p, &buf, "]", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; /* Missing "]". */ - buf = strndupa_safe(p + 1, q - p - 1); - p = q + 2; - } else { - q = strchr(p, ':'); - if (!q) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IPv4 address '%s'", p); - - buf = strndupa_safe(p, q - p); - p = q + 1; + if (!isempty(p)) { + if (p[0] != ':') + return -EINVAL; /* Missing ":" after "]". */ + p++; } - r = in_addr_from_string(family, buf, ret); - if (r < 0) - return log_debug_errno(r, "Invalid IP address '%s': %m", buf); - - *value = p; + *ptr = p; + *ret = TAKE_PTR(buf); 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; +static int extract_ip_address(int family, const char **ptr, union in_addr_union *ret) { + _cleanup_free_ char *buf = NULL; + const char *p; + int r, k; assert(IN_SET(family, AF_INET, AF_INET6)); - assert(value); - assert(*value); + assert(ptr); assert(ret); - r = parse_ip_address_one(family, value, &netmask); - if (r > 0) { - if (family == AF_INET6) - /* TODO: Not supported yet. */ - return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "IPv6 prefix length is not supported yet"); - - *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 log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid netmask or prefix length '%s'", *value); - - q = strndupa_safe(*value, p - *value); - r = safe_atou8(q, ret); - if (r < 0) - return log_debug_errno(r, "Invalid netmask or prefix length '%s': %m", q); - - *value = p + 1; - } - - return 0; -} - -static int parse_ip_dns_address_one(Context *context, const char *ifname, const char **value) { - const char *p, *q, *buf; - int r, family; - - assert(context); - assert(ifname); - assert(value); - - p = ASSERT_PTR(*value); - - if (isempty(p)) - return 0; - - if (p[0] == '[') { - q = strchr(p + 1, ']'); - if (!q || !IN_SET(q[1], ':', '\0')) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IP DNS address '%s'", p); - - buf = strndupa_safe(p + 1, q - p - 1); - p = q + 1; - family = AF_INET6; - } else { - q = strchr(p, ':'); - if (!q) - buf = *value; - else - buf = strndupa_safe(*value, q - *value); - - p += strlen(buf); - family = AF_INET; - } - - r = network_set_dns(context, ifname, family, buf); + p = *ptr; + r = extract_ip_address_str(family, &p, &buf); if (r < 0) return r; - *value = p; + if (isempty(buf)) { + *ret = IN_ADDR_NULL; + r = 0; + } else { + assert(r > 0); + k = in_addr_from_string(family, buf, ret); + if (k < 0) + return k; + } + + *ptr = p; + return r; +} + +static int extract_netmask_or_prefixlen(int family, const char **ptr, unsigned char *ret) { + _cleanup_free_ char *buf = NULL; + const char *p; + int r; + + assert(IN_SET(family, AF_INET, AF_INET6)); + assert(ptr); + assert(ret); + + if (family == AF_INET) { + union in_addr_union netmask; + + p = *ptr; + r = extract_ip_address(family, &p, &netmask); + if (r > 0) { + *ptr = p; + *ret = in4_addr_netmask_to_prefixlen(&netmask.in); + return 0; + } + } + + p = *ptr; + r = extract_first_word(&p, &buf, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (isempty(buf)) + *ret = family == AF_INET6 ? 128 : 32; + else { + r = safe_atou8(buf, ret); + if (r < 0) + return r; + } + + *ptr = p; 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, *p; + union in_addr_union addr, peer = {}, gateway = {}; unsigned char prefixlen; int r; @@ -739,59 +770,43 @@ static int parse_cmdline_ip_address(Context *context, int family, const char *va assert(value); /* ip=:[]:::::{none|off|dhcp|on|any|dhcp6|auto6|ibft|link6}[:[][:]] - * ip=:[]:::::{none|off|dhcp|on|any|dhcp6|auto6|ibft|link6}[:[][:]] */ + * ip=:[]:::::{none|off|dhcp|on|any|dhcp6|auto6|ibft|link6}[:[][:]] + * + * Here, only DHCP type is mandatory. */ - r = parse_ip_address_one(family, &value, &addr); + const char *p = value; + r = extract_ip_address(family, &p, &addr); if (r < 0) - return r; - r = parse_ip_address_one(family, &value, &peer); + return log_debug_errno(r, "Failed to parse IP address in ip=%s: %m", value); + r = extract_ip_address(family, &p, &peer); if (r < 0) - return r; - r = parse_ip_address_one(family, &value, &gateway); + return log_debug_errno(r, "Failed to parse peer address in ip=%s: %m", value); + r = extract_ip_address(family, &p, &gateway); if (r < 0) - return r; - r = parse_netmask_or_prefixlen(family, &value, &prefixlen); + return log_debug_errno(r, "Failed to parse gateway address in ip=%s: %m", value); + r = extract_netmask_or_prefixlen(family, &p, &prefixlen); if (r < 0) - return r; + return log_debug_errno(r, "Failed to parse netmask in ip=%s: %m", value); /* hostname */ - p = strchr(value, ':'); - if (!p) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IP address '%s'", value); - - if (p != value) { - hostname = strndupa_safe(value, p - value); - if (!hostname_is_valid(hostname, 0)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid hostname '%s'", hostname); - } - - value = p + 1; + _cleanup_free_ char *hostname = NULL; + r = extract_first_word(&p, &hostname, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return log_debug_errno(r, "Failed to parse hostname in ip=%s: %m", value); /* ifname */ - p = strchr(value, ':'); - if (!p) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IP address '%s'", value); - - ifname = strndupa_safe(value, p - value); - - value = p + 1; + _cleanup_free_ char *ifname = NULL; + r = extract_first_word(&p, &ifname, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r <= 0) + return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse interface name in ip=%s: %m", value); /* dhcp_type */ - p = strchr(value, ':'); - if (!p) - dhcp_type = value; - else - dhcp_type = strndupa_safe(value, p - value); - - r = network_set_dhcp_type(context, ifname, dhcp_type); - if (r < 0) - return r; + _cleanup_free_ char *dhcp_type = NULL; + r = extract_first_word(&p, &dhcp_type, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r <= 0) + return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse DHCP type name in ip=%s: %m", value); /* 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; @@ -800,34 +815,50 @@ static int parse_cmdline_ip_address(Context *context, int family, const char *va if (r < 0) return r; - if (!p) - return 0; - - /* First, try [][:] */ - r = parse_cmdline_ip_mtu_mac(context, ifname, p + 1); - if (r >= 0) - return 0; - - /* Next, try [][:] */ - value = p + 1; - r = parse_ip_dns_address_one(context, ifname, &value); + r = network_set_hostname(context, ifname, hostname); if (r < 0) return r; - value += *value == ':'; - r = parse_ip_dns_address_one(context, ifname, &value); + r = network_set_dhcp_type(context, ifname, dhcp_type); + if (r < 0) + return r; + + /* First, try [][:] if an interface name is specified. */ + if (!isempty(ifname) && parse_cmdline_ip_mtu_mac(context, ifname, p) >= 0) + return 0; + + /* Next, try [][:] */ + _cleanup_free_ char *dns = NULL; + r = extract_ip_address_str(AF_UNSPEC, &p, &dns); + if (r < 0) + return log_debug_errno(r, "Failed to parse DNS address in ip=%s: %m", value); + if (r == 0) + return 0; + + r = network_set_dns(context, ifname, dns); + if (r < 0) + return r; + + dns = mfree(dns); + r = extract_ip_address_str(AF_UNSPEC, &p, &dns); + if (r < 0) + return log_debug_errno(r, "Failed to parse DNS address in ip=%s: %m", value); + if (r == 0) + return 0; + + r = network_set_dns(context, ifname, dns); if (r < 0) return r; /* refuse unexpected trailing strings */ - if (!isempty(value)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IP address '%s'", value); + if (!isempty(p)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unexpected trailing string in 'ip=%s'.", value); return 0; } static int parse_cmdline_ip_interface(Context *context, const char *value) { - const char *ifname, *dhcp_type, *p; + _cleanup_free_ char *ifname = NULL, *dhcp_type = NULL; int r; assert(context); @@ -835,27 +866,23 @@ static int parse_cmdline_ip_interface(Context *context, const char *value) { /* ip=:{dhcp|on|any|dhcp6|auto6|link6}[:[][:]] */ - p = strchr(value, ':'); - if (!p) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IP address '%s'", value); + const char *p = value; + r = extract_first_word(&p, &ifname, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r <= 0) + return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse interface name in ip=%s: %m", value); - ifname = strndupa_safe(value, p - value); + if (isempty(ifname)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing interface name in ip=%s: %m", value); - value = p + 1; - p = strchr(value, ':'); - if (!p) - dhcp_type = value; - else - dhcp_type = strndupa_safe(value, p - value); + r = extract_first_word(&p, &dhcp_type, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r <= 0) + return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse DHCP type in ip=%s: %m", 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, p + 1); + return parse_cmdline_ip_mtu_mac(context, ifname, p); } static int parse_cmdline_ip(Context *context, const char *key, const char *value) { @@ -866,13 +893,17 @@ static int parse_cmdline_ip(Context *context, const char *key, const char *value assert(key); if (proc_cmdline_value_missing(key, value)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for '%s'", key); + return 0; p = strchr(value, ':'); if (!p) /* ip={dhcp|on|any|dhcp6|auto6|either6|link6} */ return network_set_dhcp_type(context, "", value); + /* extract_first_word() eats the trailing separator, so check this earlier. */ + if (endswith(value, ":")) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unexpected trailing colon in 'ip=%s'.", value); + if (value[0] == '[') return parse_cmdline_ip_address(context, AF_INET6, value); @@ -884,9 +915,9 @@ static int parse_cmdline_ip(Context *context, const char *key, const char *value } static int parse_cmdline_rd_route(Context *context, const char *key, const char *value) { - union in_addr_union addr = {}, gateway = {}; + _cleanup_free_ char *buf = NULL; + union in_addr_union dest, gateway; unsigned char prefixlen; - const char *buf, *p; int family, r; assert(context); @@ -895,42 +926,23 @@ static int parse_cmdline_rd_route(Context *context, const char *key, const char /* rd.route=/:[:] */ if (proc_cmdline_value_missing(key, value)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for '%s'", key); + return 0; - if (value[0] == '[') { - p = strchr(value, ']'); - if (!p) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IPv6 address '%s'", value); + const char *p = value; + r = extract_ip_address_str(AF_UNSPEC, &p, &buf); + if (r <= 0) + return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse destination address in %s=%s: %m", key, value); - if (p[1] != ':') - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IPv6 address '%s'", value); - - buf = strndupa_safe(value + 1, p - value - 1); - value = p + 2; - family = AF_INET6; - } else { - p = strchr(value, ':'); - if (!p) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IPv4 address '%s'", value); - - buf = strndupa_safe(value, p - value); - value = p + 1; - family = AF_INET; - } - - r = in_addr_prefix_from_string(buf, family, &addr, &prefixlen); + r = in_addr_prefix_from_string_auto(buf, &family, &dest, &prefixlen); if (r < 0) - return log_debug_errno(r, "Invalid IP address '%s': %m", buf); + return log_debug_errno(r, "Failed to parse route destination '%s': %m", buf); - p = strchr(value, ':'); - if (!p) - value = strjoina(value, ":"); + buf = mfree(buf); + r = extract_ip_address(family, &p, &gateway); + if (r <= 0) + return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse gateway address in %s=%s: %m", key, value); - r = parse_ip_address_one(family, &value, &gateway); - if (r < 0) - return r; - - return network_set_route(context, value, family, prefixlen, &addr, &gateway); + return network_set_route(context, strempty(p), family, prefixlen, &dest, &gateway); } static int parse_cmdline_nameserver(Context *context, const char *key, const char *value) { @@ -938,9 +950,9 @@ static int parse_cmdline_nameserver(Context *context, const char *key, const cha assert(key); if (proc_cmdline_value_missing(key, value)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for '%s'", key); + return 0; - return network_set_dns(context, "", AF_UNSPEC, value); + return network_set_dns(context, "", value); } static int parse_cmdline_rd_peerdns(Context *context, const char *key, const char *value) { @@ -949,70 +961,81 @@ static int parse_cmdline_rd_peerdns(Context *context, const char *key, const cha assert(context); assert(key); - if (proc_cmdline_value_missing(key, value)) - return network_set_dhcp_use_dns(context, "", true); - - r = parse_boolean(value); + r = value ? parse_boolean(value) : true; if (r < 0) return log_debug_errno(r, "Invalid boolean value '%s'", value); return network_set_dhcp_use_dns(context, "", r); } +static int extract_vlan_id(const char *vlan_name, uint16_t *ret) { + assert(!isempty(vlan_name)); + assert(ret); + + /* From dracut.cmdline(7): + * We support the four styles of vlan names: + * VLAN_PLUS_VID (vlan0005), + * VLAN_PLUS_VID_NO_PAD (vlan5), + * DEV_PLUS_VID (eth0.0005), and + * DEV_PLUS_VID_NO_PAD (eth0.5). */ + + for (const char *p = vlan_name + strlen(vlan_name) - 1; p > vlan_name; p--) + if (!ascii_isdigit(*p)) + return parse_vlanid(p+1, ret); + + return -EINVAL; +} + static int parse_cmdline_vlan(Context *context, const char *key, const char *value) { - const char *name, *p; + _cleanup_free_ char *name = NULL; NetDev *netdev; int r; assert(context); assert(key); + /* vlan=: */ + if (proc_cmdline_value_missing(key, value)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for '%s'", key); + return 0; - p = strchr(value, ':'); - if (!p) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid VLAN value '%s'", value); + const char *p = value; + r = extract_first_word(&p, &name, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r <= 0) + return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s=%s: %m", key, value); - name = strndupa_safe(value, p - value); + r = netdev_acquire(context, "vlan", name, &netdev); + if (r < 0) + return log_debug_errno(r, "Failed to acquire VLAN device for '%s': %m", name); - netdev = netdev_get(context, name); - if (!netdev) { - r = netdev_new(context, "vlan", name, &netdev); - if (r < 0) - return log_debug_errno(r, "Failed to create VLAN device for '%s': %m", name); - } + r = extract_vlan_id(name, &netdev->vlan_id); + if (r < 0) + return log_debug_errno(r, "Failed to parse VLAN ID from VLAN device name '%s': %m", name); - return network_set_vlan(context, p + 1, name); + return network_set_vlan(context, p, name); } static int parse_cmdline_bridge(Context *context, const char *key, const char *value) { - const char *name, *p; + _cleanup_free_ char *name = NULL; NetDev *netdev; int r; assert(context); assert(key); + /* bridge=: */ + if (proc_cmdline_value_missing(key, value)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for '%s'", key); + return 0; - p = strchr(value, ':'); - if (!p) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid bridge value '%s'", value); + const char *p = value; + r = extract_first_word(&p, &name, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r <= 0) + return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s=%s: %m", key, value); - name = strndupa_safe(value, p - value); - - netdev = netdev_get(context, name); - if (!netdev) { - r = netdev_new(context, "bridge", name, &netdev); - if (r < 0) - return log_debug_errno(r, "Failed to create bridge device for '%s': %m", name); - } - - p++; - if (isempty(p)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing slave interfaces for bridge '%s'", name); + r = netdev_acquire(context, "bridge", name, &netdev); + if (r < 0) + return log_debug_errno(r, "Failed to acquire bridge device for '%s': %m", name); for (;;) { _cleanup_free_ char *word = NULL; @@ -1030,38 +1053,30 @@ static int parse_cmdline_bridge(Context *context, const char *key, const char *v } static int parse_cmdline_bond(Context *context, const char *key, const char *value) { - const char *name, *slaves, *p; + _cleanup_free_ char *name = NULL, *slaves = NULL, *options = NULL; NetDev *netdev; int r; assert(context); assert(key); + /* bond=[::[:[:]]] */ + if (proc_cmdline_value_missing(key, value)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for '%s'", key); + return 0; - p = strchr(value, ':'); - if (!p) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid bond value '%s'", value); + const char *p = value; + r = extract_first_word(&p, &name, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r <= 0) + return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s=%s: %m", key, value); - name = strndupa_safe(value, p - value); + r = netdev_acquire(context, "bond", name, &netdev); + if (r < 0) + return log_debug_errno(r, "Failed to acquire bond device for '%s': %m", name); - netdev = netdev_get(context, name); - if (!netdev) { - r = netdev_new(context, "bond", name, &netdev); - if (r < 0) - return log_debug_errno(r, "Failed to create bond device for '%s': %m", name); - } - - value = p + 1; - p = strchr(value, ':'); - if (!p) - slaves = value; - else - slaves = strndupa_safe(value, p - value); - - if (isempty(slaves)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing slave interfaces for bond '%s'", name); + r = extract_first_word(&p, &slaves, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return log_debug_errno(r, "Failed to parse %s=%s: %m", key, value); for (const char *q = slaves; ; ) { _cleanup_free_ char *word = NULL; @@ -1077,21 +1092,24 @@ static int parse_cmdline_bond(Context *context, const char *key, const char *val return r; } - if (!p) - return 0; + r = extract_first_word(&p, &options, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return log_debug_errno(r, "Failed to parse %s=%s: %m", key, value); - value = p + 1; - p = strchr(value, ':'); - if (!p) - /* TODO: set bonding options */ - return 0; + /* TODO: set bonding options */ - return parse_mtu(AF_UNSPEC, p + 1, &netdev->mtu); + if (!isempty(p)) { + r = parse_mtu(AF_UNSPEC, p, &netdev->mtu); + if (r < 0) + return log_debug_errno(r, "Invalid MTU '%s' for '%s': %m", p, name); + } + + return 0; } static int parse_cmdline_ifname(Context *context, const char *key, const char *value) { + _cleanup_free_ char *name = NULL; struct hw_addr_data mac; - const char *name, *p; int r; assert(context); @@ -1100,17 +1118,19 @@ static int parse_cmdline_ifname(Context *context, const char *key, const char *v /* ifname=: */ if (proc_cmdline_value_missing(key, value)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for '%s'", key); + return 0; - p = strchr(value, ':'); - if (!p) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid ifname value '%s'", value); + const char *p = value; + r = extract_first_word(&p, &name, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r <= 0) + return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s=%s: %m", key, value); - name = strndupa_safe(value, p - value); + if (isempty(p)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing MAC address for '%s'", name); - r = parse_hw_addr(p + 1, &mac); + r = parse_hw_addr(p, &mac); if (r < 0) - return log_debug_errno(r, "Invalid MAC address '%s' for '%s'", p + 1, name); + return log_debug_errno(r, "Invalid MAC address '%s' for '%s'", p, name); r = link_new(context, name, &mac, NULL); if (r < 0) @@ -1131,7 +1151,7 @@ static int parse_cmdline_ifname_policy(Context *context, const char *key, const /* net.ifname_policy=policy1[,policy2,...][,] */ if (proc_cmdline_value_missing(key, value)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for '%s'", key); + return 0; for (const char *q = value; ; ) { _cleanup_free_ char *word = NULL; @@ -1329,8 +1349,8 @@ void network_dump(Network *network, FILE *f) { STRV_FOREACH(dns, network->dns) fprintf(f, "DNS=%s\n", *dns); - if (network->vlan) - fprintf(f, "VLAN=%s\n", network->vlan); + STRV_FOREACH(v, network->vlan) + fprintf(f, "VLAN=%s\n", *v); if (network->bridge) fprintf(f, "Bridge=%s\n", network->bridge); @@ -1366,6 +1386,13 @@ void netdev_dump(NetDev *netdev, FILE *f) { if (netdev->mtu > 0) fprintf(f, "MTUBytes=%" PRIu32 "\n", netdev->mtu); + + if (streq(netdev->kind, "vlan")) { + fprintf(f, + "\n[VLAN]\n" + "Id=%u\n", + netdev->vlan_id); + } } void link_dump(Link *link, FILE *f) { diff --git a/src/network/generator/network-generator.h b/src/network/generator/network-generator.h index aa5ca9d695..f2fdd02808 100644 --- a/src/network/generator/network-generator.h +++ b/src/network/generator/network-generator.h @@ -62,7 +62,7 @@ struct Network { /* [Network] */ DHCPType dhcp_type; char **dns; - char *vlan; + char **vlan; char *bridge; char *bond; @@ -79,6 +79,9 @@ struct NetDev { char *ifname; char *kind; uint32_t mtu; + + /* [VLAN] */ + uint16_t vlan_id; }; struct Link { diff --git a/src/network/generator/test-network-generator.c b/src/network/generator/test-network-generator.c index 7850da9904..92118907b7 100644 --- a/src/network/generator/test-network-generator.c +++ b/src/network/generator/test-network-generator.c @@ -10,12 +10,12 @@ static void test_network_one(const char *ifname, const char *key, const char *va _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)); + log_debug("/* %s(%s=%s) */", __func__, key, value); + + ASSERT_OK(parse_cmdline_item(key, value, &context)); + ASSERT_NOT_NULL(network = network_get(&context, ifname)); + ASSERT_OK(network_format(network, &output)); + ASSERT_STREQ(output, expected); } static void test_network_two(const char *ifname, @@ -26,15 +26,14 @@ static void test_network_two(const char *ifname, _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)); + log_debug("/* %s(%s=%s, %s=%s) */", __func__, key1, value1, key2, value2); + + ASSERT_OK(parse_cmdline_item(key1, value1, &context)); + ASSERT_OK(parse_cmdline_item(key2, value2, &context)); + ASSERT_OK(context_merge_networks(&context)); + ASSERT_NOT_NULL(network = network_get(&context, ifname)); + ASSERT_OK(network_format(network, &output)); + ASSERT_STREQ(output, expected); } static void test_netdev_one(const char *ifname, const char *key, const char *value, const char *expected) { @@ -42,12 +41,12 @@ static void test_netdev_one(const char *ifname, const char *key, const char *val _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)); + log_debug("/* %s(%s=%s) */", __func__, key, value); + + ASSERT_OK(parse_cmdline_item(key, value, &context)); + ASSERT_NOT_NULL(netdev = netdev_get(&context, ifname)); + ASSERT_OK(netdev_format(netdev, &output)); + ASSERT_STREQ(output, expected); } static void test_link_one(const char *filename, const char *key, const char *value, const char *expected) { @@ -55,12 +54,12 @@ static void test_link_one(const char *filename, const char *key, const char *val _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, filename)); - assert_se(link_format(link, &output) >= 0); - puts(output); - assert_se(streq(output, expected)); + log_debug("/* %s(%s=%s) */", __func__, key, value); + + ASSERT_OK(parse_cmdline_item(key, value, &context)); + ASSERT_NOT_NULL(link = link_get(&context, filename)); + ASSERT_OK(link_format(link, &output)); + ASSERT_STREQ(output, expected); } int main(int argc, char *argv[]) { @@ -106,6 +105,21 @@ int main(int argc, char *argv[]) { "\n[DHCP]\n" ); + test_network_one("eth0", "ip", "10.99.37.44::10.99.10.1:255.255.0.0::eth0:off", + "[Match]\n" + "Name=eth0\n" + "\n[Link]\n" + "\n[Network]\n" + "DHCP=no\n" + "LinkLocalAddressing=no\n" + "IPv6AcceptRA=no\n" + "\n[DHCP]\n" + "\n[Address]\n" + "Address=10.99.37.44/16\n" + "\n[Route]\n" + "Gateway=10.99.10.1\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" @@ -283,12 +297,13 @@ int main(int argc, char *argv[]) { "UseDNS=yes\n" ); - test_network_one("eth0", "vlan", "vlan99:eth0", + test_network_two("eth0", "vlan", "vlan99:eth0", "vlan", "vlan98:eth0", "[Match]\n" "Name=eth0\n" "\n[Link]\n" "\n[Network]\n" "VLAN=vlan99\n" + "VLAN=vlan98\n" "\n[DHCP]\n" ); @@ -328,6 +343,31 @@ int main(int argc, char *argv[]) { "\n[DHCP]\n" ); + test_netdev_one("bridge99", "bridge", "bridge99:", + "[NetDev]\n" + "Kind=bridge\n" + "Name=bridge99\n" + ); + + test_netdev_one("bridge99", "bridge", "bridge99:,,,", + "[NetDev]\n" + "Kind=bridge\n" + "Name=bridge99\n" + ); + + test_netdev_one("bond99", "bond", "bond99:", + "[NetDev]\n" + "Kind=bond\n" + "Name=bond99\n" + ); + + test_netdev_one("bond99", "bond", "bond99::hogehoge:1530", + "[NetDev]\n" + "Kind=bond\n" + "Name=bond99\n" + "MTUBytes=1530\n" + ); + test_netdev_one("bond99", "bond", "bond99:eth0,eth1::1530", "[NetDev]\n" "Kind=bond\n" @@ -335,6 +375,38 @@ int main(int argc, char *argv[]) { "MTUBytes=1530\n" ); + test_netdev_one("vlan123", "vlan", "vlan123:eth0", + "[NetDev]\n" + "Kind=vlan\n" + "Name=vlan123\n" + "\n[VLAN]\n" + "Id=123\n" + ); + + test_netdev_one("vlan0013", "vlan", "vlan0013:eth0", + "[NetDev]\n" + "Kind=vlan\n" + "Name=vlan0013\n" + "\n[VLAN]\n" + "Id=11\n" /* 0013 (octal) -> 11 */ + ); + + test_netdev_one("eth0.123", "vlan", "eth0.123:eth0", + "[NetDev]\n" + "Kind=vlan\n" + "Name=eth0.123\n" + "\n[VLAN]\n" + "Id=123\n" + ); + + test_netdev_one("eth0.0013", "vlan", "eth0.0013:eth0", + "[NetDev]\n" + "Kind=vlan\n" + "Name=eth0.0013\n" + "\n[VLAN]\n" + "Id=11\n" /* 0013 (octal) -> 11 */ + ); + test_link_one("hogehoge", "ifname", "hogehoge:00:11:22:33:44:55", "[Match]\n" "MACAddress=00:11:22:33:44:55\n" diff --git a/test/test-network-generator-conversion.sh b/test/test-network-generator-conversion.sh index 12254acdab..f8d97e5de3 100755 --- a/test/test-network-generator-conversion.sh +++ b/test/test-network-generator-conversion.sh @@ -261,7 +261,6 @@ COMMAND_LINES=( "ip=1.2.3.4:2.3.4.5:1.2.3.1:255.255.255.0:hello-world.local:dummy99:off:123" "ip=1.2.3.4:2.3.4.5:1.2.3.1:255.255.255.0:hello-world.local:dummy99:off:123:52:54:00:a7:8f:ac" "ip=1.2.3.4:2.3.4.5:1.2.3.1:255.255.255.0:hello-world.local:dummy99:off::52:54:00:a7:8f:ac" - "ip=1.2.3.4:2.3.4.5:1.2.3.1:255.255.255.0:hello-world.local:dummy99:off::" "ip=1.2.3.4:2.3.4.5:1.2.3.1:255.255.255.0:hello-world.local:dummy99:off:1.2.3.2" "ip=1.2.3.4:2.3.4.5:1.2.3.1:255.255.255.0:hello-world.local:dummy99:off:1.2.3.2:1.2.3.3" "ip=192.168.0.2::192.168.0.1:255.255.128.0::foo1:off" @@ -272,7 +271,6 @@ COMMAND_LINES=( "ip=[fdef:c400:bd01:1096::2]::[fdef:c400:bd01:1096::1]:64::ipv6:off:666" "ip=[fdef:c400:bd01:1096::2]::[fdef:c400:bd01:1096::1]:64::ipv6:off:666:52:54:00:a7:8f:ac" "ip=[fdef:c400:bd01:1096::2]::[fdef:c400:bd01:1096::1]:64::ipv6:off::52:54:00:a7:8f:ac" - "ip=[fdef:c400:bd01:1096::2]::[fdef:c400:bd01:1096::1]:64::ipv6:off::" "ip=[fdef:c400:bd01:1096::2]::[fdef:c400:bd01:1096::1]:64::ipv6:off:[fdef:c400:bd01:1096::aaaa]" "ip=[fdef:c400:bd01:1096::2]::[fdef:c400:bd01:1096::1]:64::ipv6:off:[fdef:c400:bd01:1096::aaaa]:[fdef:c400:bd01:1096::bbbb]" "ip=:::::dhcp99:any" @@ -299,7 +297,9 @@ INVALID_COMMAND_LINES=( "ip=:::::dhcp99:dhcp6:4294967296" "ip=:::::dhcp99:dhcp6:-1" "ip=:::::dhcp99:dhcp6:666:52:54:00" + "ip=1.2.3.4:2.3.4.5:1.2.3.1:255.255.255.0:hello-world.local:dummy99:off::" "ip=fdef:c400:bd01:1096::2::[fdef:c400:bd01:1096::1]:64::ipv6:off:[fdef:c400:bd01:1096::aaaa]" + "ip=[fdef:c400:bd01:1096::2]::[fdef:c400:bd01:1096::1]:64::ipv6:off::" "ip=[fdef:c400:bd01:1096::2]::[fdef:c400:bd01:1096::1]:64::ipv6:off:foo" "ip=[fdef:c400:bd01:1096::2]::[fdef:c400:bd01:1096::1]:64::ipv6:off:[fdef:c400:bd01:1096::aaaa]:foo" "ip=[fdef:c400:bd01:1096::2]::[fdef:c400:bd01:1096::1]:64::ipv6:off:[fdef:c400:bd01:1096::aaaa]:[fdef:c400:bd01:1096::bbbb]:"