]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
network: add support for setting MDB entries
authorDaniel Mack <daniel@zonque.org>
Wed, 9 Sep 2020 12:09:29 +0000 (14:09 +0200)
committerDaniel Mack <daniel@zonque.org>
Thu, 10 Sep 2020 16:55:15 +0000 (18:55 +0200)
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.

src/network/meson.build
src/network/networkd-link.c
src/network/networkd-mdb.c [new file with mode: 0644]
src/network/networkd-mdb.h [new file with mode: 0644]
src/network/networkd-network-gperf.gperf
src/network/networkd-network.c
src/network/networkd-network.h

index b3a88d991035677fbfc9d364b566f10e2edf908d..cb8f80103185755329dd7419d85b5d9f1fe3ec3f 100644 (file)
@@ -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
index 47af15b6b3cdbe35a02743f8fbc2c282a78542b5..297b26cb6913cd9717a63707cd7612beddc7472c 100644 (file)
@@ -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 (file)
index 0000000..3dec19b
--- /dev/null
@@ -0,0 +1,252 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <linux/if_bridge.h>
+#include <net/ethernet.h>
+#include <net/if.h>
+
+#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 (file)
index 0000000..9ac8a18
--- /dev/null
@@ -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);
index 3f1652b1904ffa622973dd91f29a97701d347880..7a2d2d5f3c66eecc648ed12f594977e49ba181b5 100644 (file)
@@ -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
index a61e774e362915142ae2fbf6cf222cdd70a2c2cc..81876902f56b052fd18621bc8fc75fa15f5d897a 100644 (file)
@@ -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;
index 5dcb3c548bcc74c8ec59e82f0433c01c84bbc8c0..5ba0bc705d2f326437ddb1294ad97929846cd16f 100644 (file)
@@ -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;