]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
networkd: Static neighbor support 10999/head
authorWilliam A. Kennington III <william@wkennington.com>
Thu, 29 Nov 2018 03:00:58 +0000 (19:00 -0800)
committerWilliam A. Kennington III <william@wkennington.com>
Mon, 10 Dec 2018 00:56:37 +0000 (16:56 -0800)
When using networkd we currently have no way of ensuring that static
neighbor entries are set when our link comes up. This change adds a new
section to the network definition that allows multiple static neighbors
to be set on a link.

13 files changed:
man/systemd.network.xml
src/network/meson.build
src/network/networkd-link.c
src/network/networkd-link.h
src/network/networkd-neighbor.c [new file with mode: 0644]
src/network/networkd-neighbor.h [new file with mode: 0644]
src/network/networkd-network-gperf.gperf
src/network/networkd-network.c
src/network/networkd-network.h
test/fuzz/fuzz-network-parser/25-neighbor-section.network [new file with mode: 0644]
test/fuzz/fuzz-network-parser/directives.network
test/test-network/conf/25-neighbor-section.network [new file with mode: 0644]
test/test-network/systemd-networkd-tests.py

index f7234537d86e954d4f5275c744997420df3e1da1..865b46f403e88ced9d9c19cfd89f5dd3c53a42e0 100644 (file)
       </variablelist>
   </refsect1>
 
+  <refsect1>
+    <title>[Neighbor] Section Options</title>
+      <para>A <literal>[Neighbor]</literal> section accepts the
+      following keys. The neighbor section adds a permanent, static
+      entry to the neighbor table (IPv6) or ARP table (IPv4) for
+      the given hardware address on the links matched for the network.
+      Specify several <literal>[Neighbor]</literal> sections to configure
+      several static neighbors.</para>
+
+      <variablelist class='network-directives'>
+        <varlistentry>
+          <term><varname>Address=</varname></term>
+          <listitem>
+            <para>The IP address of the neighbor.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><varname>MACAddress=</varname></term>
+          <listitem>
+            <para>The hardware address of the neighbor.</para>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+  </refsect1>
+
     <refsect1>
     <title>[IPv6AddressLabel] Section Options</title>
 
index d4fa27a288ff5a8919b24521b0a666eece26e8a2..8fe4854424b8cd08ff77b4acccf6ba43b185fffb 100644 (file)
@@ -64,6 +64,8 @@ sources = files('''
         networkd-manager.h
         networkd-ndisc.c
         networkd-ndisc.h
+        networkd-neighbor.c
+        networkd-neighbor.h
         networkd-radv.c
         networkd-radv.h
         networkd-network-bus.c
index 637bd3c554d80f39e1bb8e9367144e05759ab94e..d575740e83fe5d10fd17c4275482c1619748bf84 100644 (file)
@@ -20,6 +20,7 @@
 #include "networkd-lldp-tx.h"
 #include "networkd-manager.h"
 #include "networkd-ndisc.h"
+#include "networkd-neighbor.h"
 #include "networkd-radv.h"
 #include "networkd-routing-policy-rule.h"
 #include "set.h"
@@ -744,6 +745,9 @@ void link_check_ready(Link *link) {
         if (!link->addresses_configured)
                 return;
 
+        if (!link->neighbors_configured)
+                return;
+
         if (!link->static_routes_configured)
                 return;
 
@@ -890,6 +894,34 @@ static int link_request_set_routes(Link *link) {
         return 0;
 }
 
+static int link_request_set_neighbors(Link *link) {
+        Neighbor *neighbor;
+        int r;
+
+        assert(link);
+        assert(link->network);
+        assert(link->state != _LINK_STATE_INVALID);
+
+        link_set_state(link, LINK_STATE_CONFIGURING);
+
+        LIST_FOREACH(neighbors, neighbor, link->network->neighbors) {
+                r = neighbor_configure(neighbor, link, NULL);
+                if (r < 0) {
+                        log_link_warning_errno(link, r, "Could not set neighbor: %m");
+                        link_enter_failed(link);
+                        return r;
+                }
+        }
+
+        if (link->neighbor_messages == 0) {
+                link->neighbors_configured = true;
+                link_check_ready(link);
+        } else
+                log_link_debug(link, "Setting neighbors");
+
+        return 0;
+}
+
 static int address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
         int r;
 
@@ -1047,6 +1079,8 @@ static int link_request_set_addresses(Link *link) {
 
         link_set_state(link, LINK_STATE_CONFIGURING);
 
+        link_request_set_neighbors(link);
+
         LIST_FOREACH(addresses, ad, link->network->static_addresses) {
                 r = address_configure(ad, link, address_handler, false);
                 if (r < 0) {
index 44f3c0bc2f533e905335971933f691b0e7724ef8..00e68fdfaab6975d753acc4eee9b52ec0f3368bb 100644 (file)
@@ -70,6 +70,7 @@ typedef struct Link {
 
         unsigned address_messages;
         unsigned address_label_messages;
+        unsigned neighbor_messages;
         unsigned route_messages;
         unsigned routing_policy_rule_messages;
         unsigned routing_policy_rule_remove_messages;
@@ -97,6 +98,8 @@ typedef struct Link {
         bool ipv4ll_address:1;
         bool ipv4ll_route:1;
 
+        bool neighbors_configured;
+
         bool static_routes_configured;
         bool routing_policy_rules_configured;
         bool setting_mtu;
diff --git a/src/network/networkd-neighbor.c b/src/network/networkd-neighbor.c
new file mode 100644 (file)
index 0000000..254a60b
--- /dev/null
@@ -0,0 +1,237 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "sd-netlink.h"
+
+#include "alloc-util.h"
+#include "conf-parser.h"
+#include "ether-addr-util.h"
+#include "hashmap.h"
+#include "in-addr-util.h"
+#include "netlink-util.h"
+#include "networkd-link.h"
+#include "networkd-manager.h"
+#include "networkd-neighbor.h"
+
+void neighbor_free(Neighbor *neighbor) {
+        if (!neighbor)
+                return;
+
+        if (neighbor->network) {
+                LIST_REMOVE(neighbors, neighbor->network->neighbors, neighbor);
+                assert(neighbor->network->n_neighbors > 0);
+                neighbor->network->n_neighbors--;
+
+                if (neighbor->section) {
+                        hashmap_remove(neighbor->network->neighbors_by_section, neighbor->section);
+                        network_config_section_free(neighbor->section);
+                }
+        }
+
+        free(neighbor);
+}
+
+static int neighbor_new_static(Network *network, const char *filename, unsigned section_line, Neighbor **ret) {
+        _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL;
+        _cleanup_(neighbor_freep) Neighbor *neighbor = NULL;
+        int r;
+
+        assert(network);
+        assert(ret);
+        assert(!!filename == (section_line > 0));
+
+        if (filename) {
+                r = network_config_section_new(filename, section_line, &n);
+                if (r < 0)
+                        return r;
+
+                neighbor = hashmap_get(network->neighbors_by_section, n);
+                if (neighbor) {
+                        *ret = TAKE_PTR(neighbor);
+
+                        return 0;
+                }
+        }
+
+        neighbor = new(Neighbor, 1);
+        if (!neighbor)
+                return -ENOMEM;
+
+        *neighbor = (Neighbor) {
+                .network = network,
+                .family = AF_UNSPEC,
+        };
+
+        LIST_APPEND(neighbors, network->neighbors, neighbor);
+        network->n_neighbors++;
+
+        if (filename) {
+                neighbor->section = TAKE_PTR(n);
+
+                r = hashmap_ensure_allocated(&network->neighbors_by_section, &network_config_hash_ops);
+                if (r < 0)
+                        return r;
+
+                r = hashmap_put(network->neighbors_by_section, neighbor->section, neighbor);
+                if (r < 0)
+                        return r;
+        }
+
+        *ret = TAKE_PTR(neighbor);
+
+        return 0;
+}
+
+static int neighbor_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+        int r;
+
+        assert(link);
+        assert(link->neighbor_messages > 0);
+
+        link->neighbor_messages--;
+
+        if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+                return 1;
+
+        r = sd_netlink_message_get_errno(m);
+        if (r < 0 && r != -EEXIST)
+                log_link_warning_errno(link, r, "Could not set neighbor: %m");
+
+        if (link->neighbor_messages == 0) {
+                log_link_debug(link, "Neighbors set");
+                link->neighbors_configured = true;
+                link_check_ready(link);
+        }
+
+        return 1;
+}
+
+int neighbor_configure(Neighbor *neighbor, Link *link, link_netlink_message_handler_t callback) {
+        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+        int r;
+
+        assert(neighbor);
+        assert(link);
+        assert(link->ifindex > 0);
+        assert(link->manager);
+        assert(link->manager->rtnl);
+
+        if (neighbor->family == AF_UNSPEC)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Neighbor without Address= configured");
+        if (!neighbor->mac_configured)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Neighbor without MACAddress= configured");
+
+        r = sd_rtnl_message_new_neigh(link->manager->rtnl, &req, RTM_NEWNEIGH,
+                                          link->ifindex, neighbor->family);
+        if (r < 0)
+                return log_error_errno(r, "Could not allocate RTM_NEWNEIGH message: %m");
+
+        r = sd_rtnl_message_neigh_set_state(req, NUD_PERMANENT);
+        if (r < 0)
+                return log_error_errno(r, "Could not set state: %m");
+
+        r = sd_netlink_message_set_flags(req, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE);
+        if (r < 0)
+                return log_error_errno(r, "Could not set flags: %m");
+
+        r = sd_netlink_message_append_ether_addr(req, NDA_LLADDR, &neighbor->mac);
+        if (r < 0)
+                return log_error_errno(r, "Could not append NDA_LLADDR attribute: %m");
+
+        switch (neighbor->family) {
+        case AF_INET6:
+                r = sd_netlink_message_append_in6_addr(req, NDA_DST, &neighbor->in_addr.in6);
+                if (r < 0)
+                        return log_error_errno(r, "Could not append NDA_DST attribute: %m");
+                break;
+        case AF_INET:
+                r = sd_netlink_message_append_in_addr(req, NDA_DST, &neighbor->in_addr.in);
+                if (r < 0)
+                        return log_error_errno(r, "Could not append NDA_DST attribute: %m");
+                break;
+        default:
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Neighbor with invalid address family");
+        }
+
+        r = netlink_call_async(link->manager->rtnl, NULL, req, callback ?: neighbor_handler,
+                               link_netlink_destroy_callback, link);
+        if (r < 0)
+                return log_error_errno(r, "Could not send rtnetlink message: %m");
+
+        link->neighbor_messages++;
+        link_ref(link);
+
+        return 0;
+}
+
+int config_parse_neighbor_address(const char *unit,
+                                  const char *filename,
+                                  unsigned line,
+                                  const char *section,
+                                  unsigned section_line,
+                                  const char *lvalue,
+                                  int ltype,
+                                  const char *rvalue,
+                                  void *data,
+                                  void *userdata) {
+
+        Network *network = userdata;
+        _cleanup_(neighbor_freep) Neighbor *n = NULL;
+        int r;
+
+        assert(filename);
+        assert(section);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        r = neighbor_new_static(network, filename, section_line, &n);
+        if (r < 0)
+                return r;
+
+        r = in_addr_from_string_auto(rvalue, &n->family, &n->in_addr);
+        if (r < 0) {
+                log_syntax(unit, LOG_ERR, filename, line, r, "Neighbor Address is invalid, ignoring assignment: %s", rvalue);
+                return 0;
+        }
+
+        TAKE_PTR(n);
+
+        return 0;
+}
+
+int config_parse_neighbor_hwaddr(const char *unit,
+                                 const char *filename,
+                                 unsigned line,
+                                 const char *section,
+                                 unsigned section_line,
+                                 const char *lvalue,
+                                 int ltype,
+                                 const char *rvalue,
+                                 void *data,
+                                 void *userdata) {
+
+        Network *network = userdata;
+        _cleanup_(neighbor_freep) Neighbor *n = NULL;
+        int r;
+
+        assert(filename);
+        assert(section);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        r = neighbor_new_static(network, filename, section_line, &n);
+        if (r < 0)
+                return r;
+
+        r = ether_addr_from_string(rvalue, &n->mac);
+        if (r < 0) {
+                log_syntax(unit, LOG_ERR, filename, line, r, "Neighbor MACAddress is invalid, ignoring assignment: %s", rvalue);
+                return 0;
+        }
+
+        n->mac_configured = true;
+        TAKE_PTR(n);
+
+        return 0;
+}
diff --git a/src/network/networkd-neighbor.h b/src/network/networkd-neighbor.h
new file mode 100644 (file)
index 0000000..094bf79
--- /dev/null
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "sd-netlink.h"
+
+#include "conf-parser.h"
+#include "ether-addr-util.h"
+#include "in-addr-util.h"
+#include "list.h"
+#include "macro.h"
+
+typedef struct Neighbor Neighbor;
+
+#include "networkd-link.h"
+#include "networkd-network.h"
+
+struct Neighbor {
+        Network *network;
+        Link *link;
+        NetworkConfigSection *section;
+
+        int family;
+        union in_addr_union in_addr;
+        bool mac_configured;
+        struct ether_addr mac;
+
+        LIST_FIELDS(Neighbor, neighbors);
+};
+
+void neighbor_free(Neighbor *neighbor);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Neighbor*, neighbor_free);
+
+int neighbor_configure(Neighbor *neighbor, Link *link, link_netlink_message_handler_t callback);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_neighbor_address);
+CONFIG_PARSER_PROTOTYPE(config_parse_neighbor_hwaddr);
index 945a3c5705c08a474b0c4bf35ef3790308dc3e2b..00605c0b92683e6f846ba24a21bc1b1be78c1c4c 100644 (file)
@@ -93,6 +93,8 @@ Address.AutoJoin,                       config_parse_address_flags,
 Address.Scope,                          config_parse_address_scope,                     0,                             0
 IPv6AddressLabel.Prefix,                config_parse_address_label_prefix,              0,                             0
 IPv6AddressLabel.Label,                 config_parse_address_label,                     0,                             0
+Neighbor.Address,                       config_parse_neighbor_address,                  0,                             0
+Neighbor.MACAddress,                    config_parse_neighbor_hwaddr,                   0,                             0
 RoutingPolicyRule.TypeOfService,        config_parse_routing_policy_rule_tos,           0,                             0
 RoutingPolicyRule.Priority,             config_parse_routing_policy_rule_priority,      0,                             0
 RoutingPolicyRule.Table,                config_parse_routing_policy_rule_table,         0,                             0
index 178cdff82b73f0f4d5d8401e9b8a86ce70f202ac..85340e7527f605aed1dc9c06b12bda0685611b15 100644 (file)
@@ -210,6 +210,7 @@ int network_load_one(Manager *manager, const char *filename) {
                               "Link\0"
                               "Network\0"
                               "Address\0"
+                              "Neighbor\0"
                               "IPv6AddressLabel\0"
                               "RoutingPolicyRule\0"
                               "Route\0"
@@ -300,6 +301,7 @@ void network_free(Network *network) {
         IPv6ProxyNDPAddress *ipv6_proxy_ndp_address;
         RoutingPolicyRule *rule;
         FdbEntry *fdb_entry;
+        Neighbor *neighbor;
         AddressLabel *label;
         Prefix *prefix;
         Address *address;
@@ -350,6 +352,9 @@ 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 ((neighbor = network->neighbors))
+                neighbor_free(neighbor);
+
         while ((label = network->address_labels))
                 address_label_free(label);
 
@@ -362,6 +367,7 @@ void network_free(Network *network) {
         hashmap_free(network->addresses_by_section);
         hashmap_free(network->routes_by_section);
         hashmap_free(network->fdb_entries_by_section);
+        hashmap_free(network->neighbors_by_section);
         hashmap_free(network->address_labels_by_section);
         hashmap_free(network->prefixes_by_section);
         hashmap_free(network->rules_by_section);
index 3592b563c09b390548f371fb50be8f90cb84723c..7f64c3883983bba9a125f38482cffcef67320890 100644 (file)
@@ -15,6 +15,7 @@
 #include "networkd-fdb.h"
 #include "networkd-ipv6-proxy-ndp.h"
 #include "networkd-lldp-tx.h"
+#include "networkd-neighbor.h"
 #include "networkd-radv.h"
 #include "networkd-route.h"
 #include "networkd-routing-policy-rule.h"
@@ -235,6 +236,7 @@ struct Network {
         LIST_HEAD(Route, static_routes);
         LIST_HEAD(FdbEntry, static_fdb_entries);
         LIST_HEAD(IPv6ProxyNDPAddress, ipv6_proxy_ndp_addresses);
+        LIST_HEAD(Neighbor, neighbors);
         LIST_HEAD(AddressLabel, address_labels);
         LIST_HEAD(Prefix, static_prefixes);
         LIST_HEAD(RoutingPolicyRule, rules);
@@ -243,6 +245,7 @@ struct Network {
         unsigned n_static_routes;
         unsigned n_static_fdb_entries;
         unsigned n_ipv6_proxy_ndp_addresses;
+        unsigned n_neighbors;
         unsigned n_address_labels;
         unsigned n_static_prefixes;
         unsigned n_rules;
@@ -250,6 +253,7 @@ struct Network {
         Hashmap *addresses_by_section;
         Hashmap *routes_by_section;
         Hashmap *fdb_entries_by_section;
+        Hashmap *neighbors_by_section;
         Hashmap *address_labels_by_section;
         Hashmap *prefixes_by_section;
         Hashmap *rules_by_section;
diff --git a/test/fuzz/fuzz-network-parser/25-neighbor-section.network b/test/fuzz/fuzz-network-parser/25-neighbor-section.network
new file mode 100644 (file)
index 0000000..dd750dd
--- /dev/null
@@ -0,0 +1,6 @@
+[Match]
+Name=dummy98
+
+[Neighbor]
+Address=2004:da8:1:0::
+MACAddress=00:00:5e:00:02:00
index 6afdd05e877f7f671a437af94fb74da5d2f13fc6..cab87bf98f2e632ab7dcc49cd3d4b746dde35428 100644 (file)
@@ -165,6 +165,9 @@ EmitDNS=
 EmitDomains=
 Managed=
 OtherInformation=
+[Neighbor]
+Address=
+MacAddress=
 [IPv6AddressLabel]
 Label=
 Prefix=
diff --git a/test/test-network/conf/25-neighbor-section.network b/test/test-network/conf/25-neighbor-section.network
new file mode 100644 (file)
index 0000000..d90802f
--- /dev/null
@@ -0,0 +1,10 @@
+[Match]
+Name=dummy98
+
+[Neighbor]
+Address=192.168.10.1
+MACAddress=00:00:5e:00:02:65
+
+[Neighbor]
+Address=2004:da8:1:0::1
+MACAddress=00:00:5e:00:02:66
index 8fb1c7f38d1e5596fa1123205e8dc535467c874c..65cc43e8b6b2b3bbed5454b9b878d9090b63da35 100755 (executable)
@@ -525,6 +525,7 @@ class NetworkdNetWorkTests(unittest.TestCase, Utilities):
         '25-fibrule-invert.network',
         '25-fibrule-port-range.network',
         '25-ipv6-address-label-section.network',
+        '25-neighbor-section.network',
         '25-link-section-unmanaged.network',
         '25-route-gateway.network',
         '25-route-gateway-on-link.network',
@@ -775,6 +776,17 @@ class NetworkdNetWorkTests(unittest.TestCase, Utilities):
         print(output)
         self.assertRegex(output, '2004:da8:1::/64')
 
+    def test_ipv6_neighbor(self):
+        self.copy_unit_to_networkd_unit_path('25-neighbor-section.network', '12-dummy.netdev')
+        self.start_networkd()
+
+        self.assertTrue(self.link_exits('dummy98'))
+
+        output = subprocess.check_output(['ip', 'neigh', 'list']).rstrip().decode('utf-8')
+        print(output)
+        self.assertRegex(output, '192.168.10.1.*00:00:5e:00:02:65.*PERMANENT')
+        self.assertRegex(output, '2004:da8:1:0::1.*00:00:5e:00:02:66.*PERMANENT')
+
     def test_sysctl(self):
         self.copy_unit_to_networkd_unit_path('25-sysctl.network', '12-dummy.netdev')
         self.start_networkd()