| From 2402e69c2ccf795d67bb55d7cd073e7384d57a3d Mon Sep 17 00:00:00 2001 |
| From: "William A. Kennington III" <william@wkennington.com> |
| 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 @@ |
| </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> |
| |
| 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 |
| |