]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Added support for L2 BridgeMDB entries (#32894)
authorjauge-technica <102538870+jauge-technica@users.noreply.github.com>
Fri, 2 Aug 2024 15:31:20 +0000 (17:31 +0200)
committerGitHub <noreply@github.com>
Fri, 2 Aug 2024 15:31:20 +0000 (16:31 +0100)
* Added support for L2 BridgeMDB entries

man/systemd.network.xml
src/network/networkd-bridge-mdb.c
src/network/networkd-bridge-mdb.h
test/test-network/conf/26-bridge-mdb-master.network
test/test-network/systemd-networkd-tests.py

index 68d0c9be59be6c6a4da15e07623b284296a7919c..8755a06eb0f369fdf3645370cf5f887c12e6def7 100644 (file)
@@ -4570,7 +4570,7 @@ ServerAddress=192.168.0.1/24</programlisting>
       <varlistentry>
         <term><varname>MulticastGroupAddress=</varname></term>
         <listitem>
-          <para>Specifies the IPv4 or IPv6 multicast group address to add. This setting is mandatory.</para>
+          <para>Specifies the IPv4, IPv6, or L2 MAC multicast group address to add. This setting is mandatory.</para>
 
           <xi:include href="version-info.xml" xpointer="v247"/>
         </listitem>
index 7ff4a188467185c13147d2aa3edcfc073b3feb24..358ca4d2947cdf8cf6168d03f3b114b9c8e5ce02 100644 (file)
@@ -71,6 +71,7 @@ static int bridge_mdb_new_static(
         *mdb = (BridgeMDB) {
                 .network = network,
                 .section = TAKE_PTR(n),
+                .type = _BRIDGE_MDB_ENTRY_TYPE_INVALID,
         };
 
         r = hashmap_ensure_put(&network->bridge_mdb_entries_by_section, &config_section_hash_ops, mdb->section, mdb);
@@ -125,24 +126,35 @@ static int bridge_mdb_configure(BridgeMDB *mdb, Link *link, Request *req) {
                                IN_ADDR_TO_STRING(mdb->family, &mdb->group_addr), mdb->vlan_id);
 
         entry = (struct br_mdb_entry) {
-                /* If MDB entry is added on bridge master, then the state must be MDB_TEMPORARY.
+                /* If MDB entry is added on bridge master, then the state must be MDB_TEMPORARY,
+                 * except on L2 routes, where they must always be permanent.
                  * See br_mdb_add_group() in net/bridge/br_mdb.c of kernel. */
-                .state = link->master_ifindex <= 0 ? MDB_TEMPORARY : MDB_PERMANENT,
+                .state = link->master_ifindex <= 0 && mdb->type == BRIDGE_MDB_ENTRY_TYPE_L3 ? MDB_TEMPORARY : MDB_PERMANENT,
                 .ifindex = link->ifindex,
                 .vid = mdb->vlan_id,
         };
 
-        switch (mdb->family) {
-        case AF_INET:
-                entry.addr.u.ip4 = mdb->group_addr.in.s_addr;
-                entry.addr.proto = htobe16(ETH_P_IP);
+        switch (mdb->type) {
+        case BRIDGE_MDB_ENTRY_TYPE_L2:
+                memcpy(entry.addr.u.mac_addr, &mdb->l2_addr.ether_addr_octet, ETH_ALEN);
+                entry.addr.proto = 0;
                 break;
-
-        case AF_INET6:
-                entry.addr.u.ip6 = mdb->group_addr.in6;
-                entry.addr.proto = htobe16(ETH_P_IPV6);
+        case BRIDGE_MDB_ENTRY_TYPE_L3:
+                switch (mdb->family) {
+                case AF_INET:
+                        entry.addr.u.ip4 = mdb->group_addr.in.s_addr;
+                        entry.addr.proto = htobe16(ETH_P_IP);
+                        break;
+
+                case AF_INET6:
+                        entry.addr.u.ip6 = mdb->group_addr.in6;
+                        entry.addr.proto = htobe16(ETH_P_IPV6);
+                        break;
+
+                default:
+                        assert_not_reached();
+                }
                 break;
-
         default:
                 assert_not_reached();
         }
@@ -252,30 +264,49 @@ static int bridge_mdb_verify(BridgeMDB *mdb) {
         if (section_is_invalid(mdb->section))
                 return -EINVAL;
 
-        if (mdb->family == AF_UNSPEC)
-                return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
-                                         "%s: [BridgeMDB] section without MulticastGroupAddress= field configured. "
-                                         "Ignoring [BridgeMDB] section from line %u.",
-                                         mdb->section->filename, mdb->section->line);
-
-        if (!in_addr_is_multicast(mdb->family, &mdb->group_addr))
-                return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
-                                         "%s: MulticastGroupAddress= is not a multicast address. "
-                                         "Ignoring [BridgeMDB] section from line %u.",
-                                         mdb->section->filename, mdb->section->line);
-
-        if (mdb->family == AF_INET) {
-                if (in4_addr_is_local_multicast(&mdb->group_addr.in))
+        switch (mdb->type) {
+        case BRIDGE_MDB_ENTRY_TYPE_L2:
+                if (!ether_addr_is_multicast(&mdb->l2_addr))
                         return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
-                                                 "%s: MulticastGroupAddress= is a local multicast address. "
+                                                 "%s: MulticastGroupAddress= is not an L2 multicast address. "
                                                  "Ignoring [BridgeMDB] section from line %u.",
                                                  mdb->section->filename, mdb->section->line);
-        } else {
-                if (in6_addr_is_link_local_all_nodes(&mdb->group_addr.in6))
+                break;
+        case BRIDGE_MDB_ENTRY_TYPE_L3:
+                if (mdb->family == AF_UNSPEC)
                         return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
-                                                 "%s: MulticastGroupAddress= is the multicast all nodes address. "
+                                                 "%s: [BridgeMDB] section without MulticastGroupAddress= field configured. "
                                                  "Ignoring [BridgeMDB] section from line %u.",
                                                  mdb->section->filename, mdb->section->line);
+
+                if (!in_addr_is_multicast(mdb->family, &mdb->group_addr))
+                        return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                 "%s: MulticastGroupAddress= is not a multicast address. "
+                                                 "Ignoring [BridgeMDB] section from line %u.",
+                                                 mdb->section->filename, mdb->section->line);
+
+                switch (mdb->family) {
+                case AF_INET:
+                        if (in4_addr_is_local_multicast(&mdb->group_addr.in))
+                                return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                         "%s: MulticastGroupAddress= is a local multicast address. "
+                                                         "Ignoring [BridgeMDB] section from line %u.",
+                                                         mdb->section->filename, mdb->section->line);
+                        break;
+                default:
+                        if (in6_addr_is_link_local_all_nodes(&mdb->group_addr.in6))
+                                return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                         "%s: MulticastGroupAddress= is the multicast all nodes address. "
+                                                         "Ignoring [BridgeMDB] section from line %u.",
+                                                         mdb->section->filename, mdb->section->line);
+                        break;
+                }
+                break;
+        default:
+                return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+                                         "%s: [BridgeMDB] section without MulticastGroupAddress= field configured. "
+                                         "Ignoring [BridgeMDB] section from line %u.",
+                                         mdb->section->filename, mdb->section->line);
         }
 
         return 0;
@@ -355,10 +386,17 @@ int config_parse_mdb_group_address(
         if (r < 0)
                 return log_oom();
 
-        r = in_addr_from_string_auto(rvalue, &mdb->family, &mdb->group_addr);
-        if (r < 0) {
-                log_syntax(unit, LOG_WARNING, filename, line, r, "Cannot parse multicast group address: %m");
-                return 0;
+        r = parse_ether_addr(rvalue, &mdb->l2_addr);
+        if (r >= 0)
+                mdb->type = BRIDGE_MDB_ENTRY_TYPE_L2;
+        else {
+                r = in_addr_from_string_auto(rvalue, &mdb->family, &mdb->group_addr);
+                if (r < 0) {
+                        log_syntax(unit, LOG_WARNING, filename, line, r,
+                        "Cannot parse multicast group address as either L2 MAC, IPv4 or IPv6, ignoring: %m");
+                        return 0;
+                }
+                mdb->type = BRIDGE_MDB_ENTRY_TYPE_L3;
         }
 
         TAKE_PTR(mdb);
index edea255769de8b7dbcf7d8cfefd5eb54d36d69e8..fe6d60187903a08cf57ceddf69cb808246b1283a 100644 (file)
 typedef struct Link Link;
 typedef struct Network Network;
 
+typedef enum BridgeMDBEntryType {
+        BRIDGE_MDB_ENTRY_TYPE_L2,
+        BRIDGE_MDB_ENTRY_TYPE_L3,
+        _BRIDGE_MDB_ENTRY_TYPE_MAX,
+        _BRIDGE_MDB_ENTRY_TYPE_INVALID = -EINVAL,
+} BridgeMDBEntryType;
+
 typedef struct BridgeMDB {
         Network *network;
         ConfigSection *section;
 
+        BridgeMDBEntryType type;
+
+        struct ether_addr l2_addr;
+
         int family;
         union in_addr_union group_addr;
         uint16_t vlan_id;
index d92762d89975b62ff7ebaf39cb90cfc7758a4311..116e95b1450845d201b92575e96808e402a1879f 100644 (file)
@@ -12,3 +12,7 @@ MulticastGroupAddress=ff02:aaaa:fee5:0000:0000:0000:0001:0004
 [BridgeMDB]
 VLANId=4067
 MulticastGroupAddress=224.0.1.2
+
+[BridgeMDB]
+VLANId=4069
+MulticastGroupAddress=01:80:c2:00:00:0e
index b4d5f80e1178ba9324470a4aec49a2f77950ca9d..01d871047aafd8c7ee246d7ba803cb9cf7760a8a 100755 (executable)
@@ -5077,6 +5077,10 @@ class NetworkdBridgeTests(unittest.TestCase, Utilities):
             self.assertRegex(output, 'dev bridge99 port bridge99 grp ff02:aaaa:fee5::1:4 temp *vid 4066')
             self.assertRegex(output, 'dev bridge99 port bridge99 grp 224.0.1.2 temp *vid 4067')
 
+        # Old kernel may not support L2 bridge MDB entries
+        if call_quiet('bridge mdb add dev bridge99 port bridge99 grp 01:80:c2:00:00:0f permanent vid 4070') == 0:
+            self.assertRegex(output, 'dev bridge99 port bridge99 grp 01:80:c2:00:00:0e permanent *vid 4069')
+
     def test_bridge_keep_master(self):
         check_output('ip link add bridge99 type bridge')
         check_output('ip link set bridge99 up')