From 2402e69c2ccf795d67bb55d7cd073e7384d57a3d Mon Sep 17 00:00:00 2001 From: "William A. Kennington III" Date: Wed, 28 Nov 2018 19:00:58 -0800 Subject: [PATCH 3/3] 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. (cherry picked from commit e4a71bf36f422c3728b902aaa5846add7bbc0eb9) Upstream-Status: Backport --- 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 | 234 +++++++++++++++++++++++ src/network/networkd-neighbor.h | 37 ++++ src/network/networkd-network-gperf.gperf | 2 + src/network/networkd-network.c | 10 + src/network/networkd-network.h | 4 + 9 files changed, 351 insertions(+) create mode 100644 src/network/networkd-neighbor.c create mode 100644 src/network/networkd-neighbor.h diff --git a/man/systemd.network.xml b/man/systemd.network.xml index fc8e0aea68..804419c31a 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -899,6 +899,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 8f5544ea04..900130ed6a 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -62,6 +62,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 71b92185cd..51f95ac55e 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -17,6 +17,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" @@ -731,6 +732,9 @@ void link_check_ready(Link *link) { if (!link->addresses_configured) return; + if (!link->neighbors_configured) + return; + if (!link->static_routes_configured) return; @@ -883,6 +887,34 @@ int link_route_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, void *use return 1; } +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, void *userdata) { _cleanup_(link_unrefp) Link *link = userdata; int r; @@ -1068,6 +1100,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 5fd81be5a5..464e93d982 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -68,6 +68,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; @@ -95,6 +96,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 0000000000..db69d310d3 --- /dev/null +++ b/src/network/networkd-neighbor.c @@ -0,0 +1,234 @@ +/* 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_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, void *userdata) { + _cleanup_(link_unrefp) Link *link = userdata; + 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, sd_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(EINVAL, "Neighbor without Address= configured"); + if (!neighbor->mac_configured) + return log_error_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(EINVAL, "Neighbor with invalid address family"); + } + + r = sd_netlink_call_async(link->manager->rtnl, req, callback ?: neighbor_handler, + link, 0, NULL); + 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 0000000000..30a1f8fe27 --- /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, sd_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 6ad5257f79..18405149a4 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 429aac5e6c..a3b8f0cec4 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -158,6 +158,10 @@ static int network_load_one(Manager *manager, const char *filename) { if (!network->fdb_entries_by_section) return log_oom(); + network->neighbors_by_section = hashmap_new(&network_config_hash_ops); + if (!network->neighbors_by_section) + log_oom(); + network->address_labels_by_section = hashmap_new(&network_config_hash_ops); if (!network->address_labels_by_section) log_oom(); @@ -256,6 +260,7 @@ static int network_load_one(Manager *manager, const char *filename) { "Link\0" "Network\0" "Address\0" + "Neighbor\0" "IPv6AddressLabel\0" "RoutingPolicyRule\0" "Route\0" @@ -340,6 +345,7 @@ void network_free(Network *network) { IPv6ProxyNDPAddress *ipv6_proxy_ndp_address; RoutingPolicyRule *rule; FdbEntry *fdb_entry; + Neighbor *neighbor; AddressLabel *label; Prefix *prefix; Address *address; @@ -393,6 +399,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); @@ -405,6 +414,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 2d46d393ac..8df18326bb 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -16,6 +16,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" @@ -229,6 +230,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); @@ -237,6 +239,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; @@ -244,6 +247,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; -- 2.20.1.97.g81188d93c3-goog