From e4a71bf36f422c3728b902aaa5846add7bbc0eb9 Mon Sep 17 00:00:00 2001 From: "William A. Kennington III" Date: Wed, 28 Nov 2018 19:00:58 -0800 Subject: [PATCH] networkd: Static neighbor support 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. --- man/systemd.network.xml | 25 ++ src/network/meson.build | 2 + src/network/networkd-link.c | 34 +++ src/network/networkd-link.h | 3 + src/network/networkd-neighbor.c | 237 ++++++++++++++++++ src/network/networkd-neighbor.h | 37 +++ src/network/networkd-network-gperf.gperf | 2 + src/network/networkd-network.c | 6 + src/network/networkd-network.h | 4 + .../25-neighbor-section.network | 6 + .../fuzz-network-parser/directives.network | 3 + .../conf/25-neighbor-section.network | 10 + test/test-network/systemd-networkd-tests.py | 12 + 13 files changed, 381 insertions(+) create mode 100644 src/network/networkd-neighbor.c create mode 100644 src/network/networkd-neighbor.h create mode 100644 test/fuzz/fuzz-network-parser/25-neighbor-section.network create mode 100644 test/test-network/conf/25-neighbor-section.network diff --git a/man/systemd.network.xml b/man/systemd.network.xml index f7234537d86..865b46f403e 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -907,6 +907,31 @@ + + [Neighbor] Section Options + A [Neighbor] 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 [Neighbor] sections to configure + several static neighbors. + + + + Address= + + The IP address of the neighbor. + + + + MACAddress= + + The hardware address of the neighbor. + + + + + [IPv6AddressLabel] Section Options diff --git a/src/network/meson.build b/src/network/meson.build index d4fa27a288f..8fe4854424b 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -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 diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 637bd3c554d..d575740e83f 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -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) { diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 44f3c0bc2f5..00e68fdfaab 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -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 index 00000000000..254a60bdc3f --- /dev/null +++ b/src/network/networkd-neighbor.c @@ -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 index 00000000000..094bf7977ed --- /dev/null +++ b/src/network/networkd-neighbor.h @@ -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); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 945a3c5705c..00605c0b926 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -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 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 178cdff82b7..85340e7527f 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -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); diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 3592b563c09..7f64c388398 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -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 index 00000000000..dd750dd5666 --- /dev/null +++ b/test/fuzz/fuzz-network-parser/25-neighbor-section.network @@ -0,0 +1,6 @@ +[Match] +Name=dummy98 + +[Neighbor] +Address=2004:da8:1:0:: +MACAddress=00:00:5e:00:02:00 diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index 6afdd05e877..cab87bf98f2 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -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 index 00000000000..d90802f44f2 --- /dev/null +++ b/test/test-network/conf/25-neighbor-section.network @@ -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 diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 8fb1c7f38d1..65cc43e8b6b 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -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() -- 2.47.3