From: Daniel Mack Date: Wed, 9 Sep 2020 12:09:29 +0000 (+0200) Subject: network: add support for setting MDB entries X-Git-Tag: v247-rc1~233^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3db468ea874c0aad062b53c786451a2b4e2af405;p=thirdparty%2Fsystemd.git network: add support for setting MDB entries Multicast snooping enabled bridges maintain a database for multicast port memberships to decide which mulicast packet is supposed to egress on which port. This patch teaches networkd to add entries to this database manually by adding `[BridgeMDB]` sections to `.network` configuration files. --- diff --git a/src/network/meson.build b/src/network/meson.build index b3a88d99103..cb8f8010318 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -87,6 +87,8 @@ sources = files(''' networkd-manager-bus.h networkd-manager.c networkd-manager.h + networkd-mdb.c + networkd-mdb.h networkd-ndisc.c networkd-ndisc.h networkd-neighbor.c diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 47af15b6b3c..297b26cb691 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -1254,6 +1254,30 @@ static int link_set_bridge_fdb(Link *link) { return 0; } +static int link_set_bridge_mdb(Link *link) { + MdbEntry *mdb_entry; + int r; + + if (!link->network) + return 0; + + if (LIST_IS_EMPTY(link->network->static_mdb_entries)) + return 0; + + if (!link->network->bridge) { + log_link_error(link, "Cannot configure MDB entries on non-bridge port"); + return 0; + } + + LIST_FOREACH(static_mdb_entries, mdb_entry, link->network->static_mdb_entries) { + r = mdb_entry_configure(link, mdb_entry); + if (r < 0) + return log_link_error_errno(link, r, "Failed to add entry to multicast group database: %m"); + } + + return 0; +} + static int static_address_ready_callback(Address *address) { Address *a; Link *link; @@ -3907,6 +3931,10 @@ static int link_carrier_gained(Link *link) { if (r < 0) return r; + r = link_set_bridge_mdb(link); + if (r < 0) + return r; + return 0; } diff --git a/src/network/networkd-mdb.c b/src/network/networkd-mdb.c new file mode 100644 index 00000000000..3dec19b1c2f --- /dev/null +++ b/src/network/networkd-mdb.c @@ -0,0 +1,252 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include +#include +#include + +#include "alloc-util.h" +#include "netlink-util.h" +#include "networkd-manager.h" +#include "networkd-mdb.h" +#include "util.h" +#include "vlan-util.h" + +#define STATIC_MDB_ENTRIES_PER_NETWORK_MAX 1024U + +/* create a new MDB entry or get an existing one. */ +static int mdb_entry_new_static( + Network *network, + const char *filename, + unsigned section_line, + MdbEntry **ret) { + + _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; + _cleanup_(mdb_entry_freep) MdbEntry *mdb_entry = NULL; + int r; + + assert(network); + assert(ret); + assert(!!filename == (section_line > 0)); + + /* search entry in hashmap first. */ + if (filename) { + r = network_config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + mdb_entry = hashmap_get(network->mdb_entries_by_section, n); + if (mdb_entry) { + *ret = TAKE_PTR(mdb_entry); + return 0; + } + } + + if (network->n_static_mdb_entries >= STATIC_MDB_ENTRIES_PER_NETWORK_MAX) + return -E2BIG; + + /* allocate space for an MDB entry. */ + mdb_entry = new(MdbEntry, 1); + if (!mdb_entry) + return -ENOMEM; + + /* init MDB structure. */ + *mdb_entry = (MdbEntry) { + .network = network, + }; + + LIST_PREPEND(static_mdb_entries, network->static_mdb_entries, mdb_entry); + network->n_static_mdb_entries++; + + if (filename) { + mdb_entry->section = TAKE_PTR(n); + + r = hashmap_ensure_allocated(&network->mdb_entries_by_section, &network_config_hash_ops); + if (r < 0) + return r; + + r = hashmap_put(network->mdb_entries_by_section, mdb_entry->section, mdb_entry); + if (r < 0) + return r; + } + + /* return allocated MDB structure. */ + *ret = TAKE_PTR(mdb_entry); + + return 0; +} + +/* parse the VLAN Id from config files. */ +int config_parse_mdb_vlan_id( + 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_(mdb_entry_free_or_set_invalidp) MdbEntry *mdb_entry = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = mdb_entry_new_static(network, filename, section_line, &mdb_entry); + if (r < 0) + return log_oom(); + + r = config_parse_vlanid(unit, filename, line, section, + section_line, lvalue, ltype, + rvalue, &mdb_entry->vlan_id, userdata); + if (r < 0) + return r; + + mdb_entry = NULL; + + return 0; +} + +/* parse the multicast group from config files. */ +int config_parse_mdb_group_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_(mdb_entry_free_or_set_invalidp) MdbEntry *mdb_entry = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = mdb_entry_new_static(network, filename, section_line, &mdb_entry); + if (r < 0) + return log_oom(); + + r = in_addr_from_string_auto(rvalue, &mdb_entry->family, &mdb_entry->group_addr); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Cannot parse multicast group address: %m"); + return 0; + } + + mdb_entry = NULL; + + return 0; +} + +/* remove and MDB entry. */ +MdbEntry *mdb_entry_free(MdbEntry *mdb_entry) { + if (!mdb_entry) + return NULL; + + if (mdb_entry->network) { + LIST_REMOVE(static_mdb_entries, mdb_entry->network->static_mdb_entries, mdb_entry); + assert(mdb_entry->network->n_static_mdb_entries > 0); + mdb_entry->network->n_static_mdb_entries--; + + if (mdb_entry->section) + hashmap_remove(mdb_entry->network->mdb_entries_by_section, mdb_entry->section); + } + + network_config_section_free(mdb_entry->section); + + return mfree(mdb_entry); +} + +static int set_mdb_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + + 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_message_warning_errno(link, m, r, "Could not add MDB entry"); + link_enter_failed(link); + return 1; + } + + return 1; +} + +int mdb_entry_verify(MdbEntry *mdb_entry) { + if (section_is_invalid(mdb_entry->section)) + return -EINVAL; + + if (in_addr_is_multicast(mdb_entry->family, &mdb_entry->group_addr) <= 0) { + log_error("No valid MulticastGroupAddress= assignment in this section"); + return -EINVAL; + } + + return 0; +} + +/* send a request to the kernel to add an MDB entry */ +int mdb_entry_configure(Link *link, MdbEntry *mdb_entry) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + struct br_mdb_entry entry; + int r; + + assert(link); + assert(link->network); + assert(link->manager); + assert(mdb_entry); + + entry = (struct br_mdb_entry) { + .state = MDB_PERMANENT, + .ifindex = link->ifindex, + .vid = mdb_entry->vlan_id, + }; + + /* create new RTM message */ + r = sd_rtnl_message_new_mdb(link->manager->rtnl, &req, RTM_NEWMDB, link->network->bridge->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not create RTM_NEWMDB message: %m"); + + switch (mdb_entry->family) { + case AF_INET: + entry.addr.u.ip4 = mdb_entry->group_addr.in.s_addr; + entry.addr.proto = htobe16(ETH_P_IP); + break; + + case AF_INET6: + entry.addr.u.ip6 = mdb_entry->group_addr.in6; + entry.addr.proto = htobe16(ETH_P_IPV6); + break; + + default: + assert_not_reached("Invalid address family"); + } + + r = sd_netlink_message_append_data(req, MDBA_SET_ENTRY, &entry, sizeof(entry)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append MDBA_SET_ENTRY attribute: %m"); + + r = netlink_call_async(link->manager->rtnl, NULL, req, set_mdb_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + return 1; +} diff --git a/src/network/networkd-mdb.h b/src/network/networkd-mdb.h new file mode 100644 index 00000000000..9ac8a18188a --- /dev/null +++ b/src/network/networkd-mdb.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include "conf-parser.h" +#include "list.h" +#include "macro.h" +#include "networkd-util.h" + +typedef struct Network Network; +typedef struct MdbEntry MdbEntry; +typedef struct Link Link; +typedef struct NetworkConfigSection NetworkConfigSection; + +struct MdbEntry { + Network *network; + NetworkConfigSection *section; + + int family; + union in_addr_union group_addr; + uint16_t vlan_id; + + LIST_FIELDS(MdbEntry, static_mdb_entries); +}; + +int mdb_entry_verify(MdbEntry *mdb_entry); +MdbEntry *mdb_entry_free(MdbEntry *mdb_entry); +int mdb_entry_configure(Link *link, MdbEntry *mdb_entry); + +DEFINE_NETWORK_SECTION_FUNCTIONS(MdbEntry, mdb_entry_free); + +CONFIG_PARSER_PROTOTYPE(config_parse_mdb_group_address); +CONFIG_PARSER_PROTOTYPE(config_parse_mdb_vlan_id); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 3f1652b1904..7a2d2d5f3c6 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -265,6 +265,8 @@ BridgeFDB.VLANId, config_parse_fdb_vlan_id, BridgeFDB.Destination, config_parse_fdb_destination, 0, 0 BridgeFDB.VNI, config_parse_fdb_vxlan_vni, 0, 0 BridgeFDB.AssociatedWith, config_parse_fdb_ntf_flags, 0, 0 +BridgeMDB.MulticastGroupAddress, config_parse_mdb_group_address, 0, 0 +BridgeMDB.VLANId, config_parse_mdb_vlan_id, 0, 0 BridgeVLAN.PVID, config_parse_brvlan_pvid, 0, 0 BridgeVLAN.VLAN, config_parse_brvlan_vlan, 0, 0 BridgeVLAN.EgressUntagged, config_parse_brvlan_untagged, 0, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index a61e774e362..81876902f56 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -157,6 +157,7 @@ int network_verify(Network *network) { Prefix *prefix, *prefix_next; Route *route, *route_next; FdbEntry *fdb, *fdb_next; + MdbEntry *mdb, *mdb_next; TrafficControl *tc; SRIOV *sr_iov; @@ -305,6 +306,10 @@ int network_verify(Network *network) { if (section_is_invalid(fdb->section)) fdb_entry_free(fdb); + LIST_FOREACH_SAFE(static_mdb_entries, mdb, mdb_next, network->static_mdb_entries) + if (mdb_entry_verify(mdb) < 0) + mdb_entry_free(mdb); + LIST_FOREACH_SAFE(neighbors, neighbor, neighbor_next, network->neighbors) if (neighbor_section_verify(neighbor) < 0) neighbor_free(neighbor); @@ -508,6 +513,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi "IPv6NDPProxyAddress\0" "Bridge\0" "BridgeFDB\0" + "BridgeMDB\0" "BridgeVLAN\0" "IPv6PrefixDelegation\0" "IPv6Prefix\0" @@ -642,6 +648,7 @@ static Network *network_free(Network *network) { RoutingPolicyRule *rule; AddressLabel *label; FdbEntry *fdb_entry; + MdbEntry *mdb_entry; Neighbor *neighbor; Address *address; NextHop *nexthop; @@ -715,6 +722,9 @@ static Network *network_free(Network *network) { while ((fdb_entry = network->static_fdb_entries)) fdb_entry_free(fdb_entry); + while ((mdb_entry = network->static_mdb_entries)) + mdb_entry_free(mdb_entry); + while ((ipv6_proxy_ndp_address = network->ipv6_proxy_ndp_addresses)) ipv6_proxy_ndp_address_free(ipv6_proxy_ndp_address); @@ -737,6 +747,7 @@ static Network *network_free(Network *network) { hashmap_free(network->routes_by_section); hashmap_free(network->nexthops_by_section); hashmap_free(network->fdb_entries_by_section); + hashmap_free(network->mdb_entries_by_section); hashmap_free(network->neighbors_by_section); hashmap_free(network->address_labels_by_section); hashmap_free(network->prefixes_by_section); @@ -849,6 +860,7 @@ bool network_has_static_ipv6_configurations(Network *network) { Address *address; Route *route; FdbEntry *fdb; + MdbEntry *mdb; Neighbor *neighbor; assert(network); @@ -865,6 +877,10 @@ bool network_has_static_ipv6_configurations(Network *network) { if (fdb->family == AF_INET6) return true; + LIST_FOREACH(static_mdb_entries, mdb, network->static_mdb_entries) + if (mdb->family == AF_INET6) + return true; + LIST_FOREACH(neighbors, neighbor, network->neighbors) if (neighbor->family == AF_INET6) return true; diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 5dcb3c548bc..5ba0bc705d2 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -23,6 +23,7 @@ #include "networkd-ipv6-proxy-ndp.h" #include "networkd-lldp-rx.h" #include "networkd-lldp-tx.h" +#include "networkd-mdb.h" #include "networkd-ndisc.h" #include "networkd-neighbor.h" #include "networkd-nexthop.h" @@ -288,6 +289,7 @@ struct Network { LIST_HEAD(Route, static_routes); LIST_HEAD(NextHop, static_nexthops); LIST_HEAD(FdbEntry, static_fdb_entries); + LIST_HEAD(MdbEntry, static_mdb_entries); LIST_HEAD(IPv6ProxyNDPAddress, ipv6_proxy_ndp_addresses); LIST_HEAD(Neighbor, neighbors); LIST_HEAD(AddressLabel, address_labels); @@ -299,6 +301,7 @@ struct Network { unsigned n_static_routes; unsigned n_static_nexthops; unsigned n_static_fdb_entries; + unsigned n_static_mdb_entries; unsigned n_ipv6_proxy_ndp_addresses; unsigned n_neighbors; unsigned n_address_labels; @@ -310,6 +313,7 @@ struct Network { Hashmap *routes_by_section; Hashmap *nexthops_by_section; Hashmap *fdb_entries_by_section; + Hashmap *mdb_entries_by_section; Hashmap *neighbors_by_section; Hashmap *address_labels_by_section; Hashmap *prefixes_by_section;