networkd: add support for address label
authorSusant Sahani <susant@redhat.com>
Tue, 25 Apr 2017 10:36:50 +0000 (16:06 +0530)
committerSusant Sahani <susant@redhat.com>
Wed, 26 Apr 2017 10:30:44 +0000 (16:00 +0530)
IPv6 address labels are used for address selection; they are described in RFC 3484.
Precedence is managed by userspace, and only the label itself is stored in the kernel.

enp0s25.network

[Match]
Name=enp0s25

[Network]
DHCP=yes
Address = 2001:db8:f00:baa::b

[AddressLabel]
Label=199
Prefix=2001:db8:41::/64

[AddressLabel]
Label=11
Prefix=2001:db8:31::/64

[AddressLabel]
Label=123
Prefix=2001:db8:21::/64

[AddressLabel]
Label=124
Prefix=2001:db8:11::/64
[sus@maximus label]$ ip addrlabel list

prefix ::1/128 label 0
prefix ::/96 label 3
prefix ::ffff:0.0.0.0/96 label 4
prefix 2001:db8:41::/64 dev enp0s25 label 199
prefix 2001:db8:31::/64 dev enp0s25 label 11
prefix 2001:db8:21::/64 dev enp0s25 label 123
prefix 2001:db8:11::/64 dev enp0s25 label 124
prefix 2001::/32 label 6
prefix 2001:10::/28 label 7
prefix 3ffe::/16 label 12
prefix 2002::/16 label 2
prefix fec0::/10 label 11
prefix fc00::/7 label 5
prefix ::/0 label 1

Makefile.am
man/systemd.network.xml
src/network/networkd-address-label.c [new file with mode: 0644]
src/network/networkd-address-label.h [new file with mode: 0644]
src/network/networkd-link.c
src/network/networkd-network-gperf.gperf
src/network/networkd-network.c
src/network/networkd-network.h

index 8339625..eb9f53c 100644 (file)
@@ -5812,6 +5812,8 @@ libnetworkd_core_la_SOURCES = \
        src/network/networkd-network-bus.c \
        src/network/networkd-address.h \
        src/network/networkd-address.c \
+       src/network/networkd-address-label.h \
+       src/network/networkd-address-label.c \
        src/network/networkd-route.h \
        src/network/networkd-route.c \
        src/network/networkd-fdb.h \
index 4b80578..ad0e0cf 100644 (file)
       </variablelist>
   </refsect1>
 
+    <refsect1>
+    <title>[IPv6AddressLabel] Section Options</title>
+
+      <para>An <literal>[IPv6AddressLabel]</literal> section accepts the
+      following keys. Specify several <literal>[IPv6AddressLabel]</literal>
+      sections to configure several addresse labels. IPv6 address labels are
+      used for address selection. See <ulink url="https://tools.ietf.org/html/rfc3484">RFC 3484</ulink>.
+      Precedence is managed by userspace, and only the label itself is stored in the kernel</para>
+
+      <variablelist class='network-directives'>
+        <varlistentry>
+          <term><varname>Label=</varname></term>
+          <listitem>
+            <para> The label for the prefix (an unsigned integer) ranges 0 to 4294967294.
+            0xffffffff is reserved. This key is mandatory.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><varname>Prefix=</varname></term>
+          <listitem>
+            <para>IPv6 prefix is an address with a prefix length, separated by a slash <literal>/</literal> character.
+            This key is mandatory. </para>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+  </refsect1>
+
   <refsect1>
     <title>[Route] Section Options</title>
       <para>The <literal>[Route]</literal> section accepts the
diff --git a/src/network/networkd-address-label.c b/src/network/networkd-address-label.c
new file mode 100644 (file)
index 0000000..1248719
--- /dev/null
@@ -0,0 +1,257 @@
+/***
+  This file is part of systemd.
+
+  Copyright 2017 Susant Sahani
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <net/if.h>
+#include <linux/if_addrlabel.h>
+
+#include "alloc-util.h"
+#include "conf-parser.h"
+#include "networkd-address-label.h"
+#include "netlink-util.h"
+#include "networkd-manager.h"
+#include "parse-util.h"
+#include "socket-util.h"
+
+int address_label_new(AddressLabel **ret) {
+        _cleanup_address_label_free_ AddressLabel *addrlabel = NULL;
+
+        addrlabel = new0(AddressLabel, 1);
+        if (!addrlabel)
+                return -ENOMEM;
+
+        *ret = addrlabel;
+        addrlabel = NULL;
+
+        return 0;
+}
+
+void address_label_free(AddressLabel *label) {
+        if (!label)
+                return;
+
+        if (label->network) {
+                LIST_REMOVE(labels, label->network->address_labels, label);
+                assert(label->network->n_address_labels > 0);
+                label->network->n_address_labels--;
+
+                if (label->section) {
+                        hashmap_remove(label->network->address_labels_by_section, label->section);
+                        network_config_section_free(label->section);
+                }
+        }
+
+        free(label);
+}
+
+static int address_label_new_static(Network *network, const char *filename, unsigned section_line, AddressLabel **ret) {
+        _cleanup_network_config_section_free_ NetworkConfigSection *n = NULL;
+        _cleanup_address_label_free_ AddressLabel *label = NULL;
+        int r;
+
+        assert(network);
+        assert(ret);
+        assert(!!filename == (section_line > 0));
+
+        r = network_config_section_new(filename, section_line, &n);
+        if (r < 0)
+                return r;
+
+        label = hashmap_get(network->address_labels_by_section, n);
+        if (label) {
+                *ret = label;
+                label = NULL;
+
+                return 0;
+        }
+
+        r = address_label_new(&label);
+        if (r < 0)
+                return r;
+
+        label->section = n;
+        n = NULL;
+
+        r = hashmap_put(network->address_labels_by_section, label->section, label);
+        if (r < 0)
+                return r;
+
+        label->network = network;
+        LIST_APPEND(labels, network->address_labels, label);
+        network->n_address_labels++;
+
+        *ret = label;
+        label = NULL;
+
+        return 0;
+}
+
+int address_label_configure(
+                AddressLabel *label,
+                Link *link,
+                sd_netlink_message_handler_t callback,
+                bool update) {
+
+        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+        int r;
+
+        assert(label);
+        assert(link);
+        assert(link->ifindex > 0);
+        assert(link->manager);
+        assert(link->manager->rtnl);
+
+        r = sd_rtnl_message_new_addrlabel(link->manager->rtnl, &req, RTM_NEWADDRLABEL,
+                                          link->ifindex, label->family);
+        if (r < 0)
+                return log_error_errno(r, "Could not allocate RTM_NEWADDR message: %m");
+
+        r = sd_rtnl_message_addrlabel_set_prefixlen(req, label->prefixlen);
+        if (r < 0)
+                return log_error_errno(r, "Could not set prefixlen: %m");
+
+        r = sd_netlink_message_append_u32(req, IFAL_LABEL, label->label);
+        if (r < 0)
+                return log_error_errno(r, "Could not append IFAL_LABEL attribute: %m");
+
+        r = sd_netlink_message_append_in6_addr(req, IFA_ADDRESS, &label->in_addr.in6);
+        if (r < 0)
+                return log_error_errno(r, "Could not append IFA_ADDRESS attribute: %m");
+
+        r = sd_netlink_call_async(link->manager->rtnl, req, callback, link, 0, NULL);
+        if (r < 0)
+                return log_error_errno(r, "Could not send rtnetlink message: %m");
+
+        link_ref(link);
+
+        return 0;
+}
+
+int config_parse_address_label_prefix(const char *unit,
+                                      const char *filename,
+                                      unsigned line,
+                                      const char *section,
+                                      unsigned section_line,
+                                      const char *lvalue,
+                                      int ltype,
+                                      const char *rvalue,
+                                      void *data,
+                                      void *userdata) {
+
+        _cleanup_address_label_free_ AddressLabel *n = NULL;
+        Network *network = userdata;
+        const char *prefix, *e;
+        union in_addr_union buffer;
+        int r, f;
+
+        assert(filename);
+        assert(section);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        r = address_label_new_static(network, filename, section_line, &n);
+        if (r < 0)
+                return r;
+
+        /* AddressLabel=prefix/prefixlen */
+
+        /* prefixlen */
+        e = strchr(rvalue, '/');
+        if (e) {
+                unsigned i;
+
+                r = safe_atou(e + 1, &i);
+                if (r < 0) {
+                        log_syntax(unit, LOG_ERR, filename, line, r, "Prefix length is invalid, ignoring assignment: %s", e + 1);
+                        return 0;
+                }
+
+                if (i > 128) {
+                        log_syntax(unit, LOG_ERR, filename, line, r, "Prefix length is out of range, ignoring assignment: %s", e + 1);
+                        return 0;
+                }
+
+                n->prefixlen = (unsigned char) i;
+
+                prefix = strndupa(rvalue, e - rvalue);
+        } else
+                prefix = rvalue;
+
+        r = in_addr_from_string_auto(prefix, &f, &buffer);
+        if (r < 0) {
+                log_syntax(unit, LOG_ERR, filename, line, r, "Address label is invalid, ignoring assignment: %s", prefix);
+                return 0;
+        }
+
+        if (f != AF_INET6) {
+                log_syntax(unit, LOG_ERR, filename, line, 0, "Address label family is not IPv6, ignoring assignment: %s", prefix);
+                return 0;
+        }
+
+        n->family = f;
+        n->in_addr = buffer;
+
+        n = NULL;
+
+        return 0;
+}
+
+int config_parse_address_label(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        _cleanup_address_label_free_ AddressLabel *n = NULL;
+        Network *network = userdata;
+        uint32_t k;
+        int r;
+
+        assert(filename);
+        assert(section);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        r = address_label_new_static(network, filename, section_line, &n);
+        if (r < 0)
+                return r;
+
+        r = safe_atou32(rvalue, &k);
+        if (r < 0) {
+                log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse address label, ignoring: %s", rvalue);
+                return 0;
+        }
+
+        if (k == 0xffffffffUL) {
+                log_syntax(unit, LOG_ERR, filename, line, r, "Adress label is invalid, ignoring: %s", rvalue);
+                return 0;
+        }
+
+        n->label = k;
+        n = NULL;
+
+        return 0;
+}
diff --git a/src/network/networkd-address-label.h b/src/network/networkd-address-label.h
new file mode 100644 (file)
index 0000000..05bb249
--- /dev/null
@@ -0,0 +1,59 @@
+#pragma once
+
+/***
+  This file is part of systemd.
+
+  Copyright 2017 Susant Sahani
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include "in-addr-util.h"
+
+typedef struct AddressLabel AddressLabel;
+
+#include "networkd-link.h"
+#include "networkd-network.h"
+
+typedef struct Network Network;
+typedef struct Link Link;
+typedef struct NetworkConfigSection NetworkConfigSection;
+
+struct AddressLabel {
+        Network *network;
+        Link *link;
+        NetworkConfigSection *section;
+
+        int family;
+        unsigned char prefixlen;
+        uint32_t label;
+
+        union in_addr_union in_addr;
+
+        LIST_FIELDS(AddressLabel, labels);
+};
+
+int address_label_new(AddressLabel **ret);
+void address_label_free(AddressLabel *label);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(AddressLabel*, address_label_free);
+#define _cleanup_address_label_free_ _cleanup_(address_label_freep)
+
+int address_label_configure(AddressLabel *address, Link *link, sd_netlink_message_handler_t callback, bool update);
+
+int config_parse_address_label(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_address_label_prefix(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
index 1797f14..48ee12a 100644 (file)
@@ -853,6 +853,35 @@ static int address_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userda
         return 1;
 }
 
+static int address_label_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) {
+        _cleanup_link_unref_ Link *link = userdata;
+        int r;
+
+        assert(rtnl);
+        assert(m);
+        assert(link);
+        assert(link->ifname);
+        assert(link->link_messages > 0);
+
+        link->link_messages--;
+
+        if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+                return 1;
+
+        r = sd_netlink_message_get_errno(m);
+        if (r < 0 && r != -EEXIST)
+                log_link_warning_errno(link, r, "could not set address label: %m");
+        else if (r >= 0)
+                manager_rtnl_process_address(rtnl, m, link->manager);
+
+        if (link->link_messages == 0) {
+                log_link_debug(link, "Addresses label set");
+                link_enter_set_routes(link);
+        }
+
+        return 1;
+}
+
 static int link_push_uplink_dns_to_dhcp_server(Link *link, sd_dhcp_server *s) {
         _cleanup_free_ struct in_addr *addresses = NULL;
         size_t n_addresses = 0, n_allocated = 0;
@@ -965,6 +994,7 @@ static int link_set_bridge_fdb(Link *link) {
 }
 
 static int link_enter_set_addresses(Link *link) {
+        AddressLabel *label;
         Address *ad;
         int r;
 
@@ -989,6 +1019,17 @@ static int link_enter_set_addresses(Link *link) {
                 link->link_messages++;
         }
 
+        LIST_FOREACH(labels, label, link->network->address_labels) {
+                r = address_label_configure(label, link, address_label_handler, false);
+                if (r < 0) {
+                        log_link_warning_errno(link, r, "Could not set address label: %m");
+                        link_enter_failed(link);
+                        return r;
+                }
+
+                link->link_messages++;
+        }
+
         /* now that we can figure out a default address for the dhcp server,
            start it */
         if (link_dhcp4_server_enabled(link)) {
index 3743113..6c4530f 100644 (file)
@@ -79,6 +79,8 @@ Address.DuplicateAddressDetection,      config_parse_address_flags,
 Address.ManageTemporaryAddress,         config_parse_address_flags,                     0,                             0
 Address.PrefixRoute,                    config_parse_address_flags,                     0,                             0
 Address.AutoJoin,                       config_parse_address_flags,                     0,                             0
+IPv6AddressLabel.Prefix,                config_parse_address_label_prefix,              0,                             0
+IPv6AddressLabel.Label,                 config_parse_address_label,                     0,                             0
 Route.Gateway,                          config_parse_gateway,                           0,                             0
 Route.Destination,                      config_parse_destination,                       0,                             0
 Route.Source,                           config_parse_destination,                       0,                             0
index dd29b4c..0c0e661 100644 (file)
@@ -114,6 +114,7 @@ static int network_load_one(Manager *manager, const char *filename) {
         LIST_HEAD_INIT(network->static_routes);
         LIST_HEAD_INIT(network->static_fdb_entries);
         LIST_HEAD_INIT(network->ipv6_proxy_ndp_addresses);
+        LIST_HEAD_INIT(network->address_labels);
 
         network->stacked_netdevs = hashmap_new(&string_hash_ops);
         if (!network->stacked_netdevs)
@@ -131,6 +132,10 @@ static int network_load_one(Manager *manager, const char *filename) {
         if (!network->fdb_entries_by_section)
                 return log_oom();
 
+        network->address_labels_by_section = hashmap_new(&network_config_hash_ops);
+        if (!network->address_labels_by_section)
+                return log_oom();
+
         network->filename = strdup(filename);
         if (!network->filename)
                 return log_oom();
@@ -192,6 +197,7 @@ static int network_load_one(Manager *manager, const char *filename) {
                               "Link\0"
                               "Network\0"
                               "Address\0"
+                              "IPv6AddressLabel\0"
                               "Route\0"
                               "DHCP\0"
                               "DHCPv4\0" /* compat */
@@ -271,6 +277,7 @@ void network_free(Network *network) {
         Address *address;
         FdbEntry *fdb_entry;
         IPv6ProxyNDPAddress *ipv6_proxy_ndp_address;
+        AddressLabel *label;
         Iterator i;
 
         if (!network)
@@ -318,9 +325,13 @@ void network_free(Network *network) {
         while ((ipv6_proxy_ndp_address = network->ipv6_proxy_ndp_addresses))
                 ipv6_proxy_ndp_address_free(ipv6_proxy_ndp_address);
 
+        while ((label = network->address_labels))
+                address_label_free(label);
+
         hashmap_free(network->addresses_by_section);
         hashmap_free(network->routes_by_section);
         hashmap_free(network->fdb_entries_by_section);
+        hashmap_free(network->address_labels_by_section);
 
         if (network->manager) {
                 if (network->manager->networks)
index d6f418d..28ef285 100644 (file)
@@ -28,6 +28,7 @@
 #include "resolve-util.h"
 
 #include "networkd-address.h"
+#include "networkd-address-label.h"
 #include "networkd-brvlan.h"
 #include "networkd-fdb.h"
 #include "networkd-lldp-tx.h"
@@ -202,15 +203,18 @@ struct Network {
         LIST_HEAD(Route, static_routes);
         LIST_HEAD(FdbEntry, static_fdb_entries);
         LIST_HEAD(IPv6ProxyNDPAddress, ipv6_proxy_ndp_addresses);
+        LIST_HEAD(AddressLabel, address_labels);
 
         unsigned n_static_addresses;
         unsigned n_static_routes;
         unsigned n_static_fdb_entries;
         unsigned n_ipv6_proxy_ndp_addresses;
+        unsigned n_address_labels;
 
         Hashmap *addresses_by_section;
         Hashmap *routes_by_section;
         Hashmap *fdb_entries_by_section;
+        Hashmap *address_labels_by_section;
 
         struct in_addr_data *dns;
         unsigned n_dns;