]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #20626 from yuwata/network-keep-master
authorLuca Boccassi <luca.boccassi@microsoft.com>
Sat, 4 Sep 2021 14:08:56 +0000 (15:08 +0100)
committerGitHub <noreply@github.com>
Sat, 4 Sep 2021 14:08:56 +0000 (15:08 +0100)
network: introduce KeepMaster= setting

man/systemd.network.xml
network/80-container-vb.network [new file with mode: 0644]
src/network/networkd-bridge-mdb.c
src/network/networkd-link.c
src/network/networkd-network-gperf.gperf
src/network/networkd-network.c
src/network/networkd-network.h
src/network/networkd-setlink.c
test/fuzz/fuzz-network-parser/directives.network
test/test-network/conf/23-keep-master.network [new file with mode: 0644]
test/test-network/systemd-networkd-tests.py

index 5b19a4b48a21ac8fabc47807450e35f9c5946eb7..48326a25c123ae8321dc2417c0ff8212975eeb72 100644 (file)
   <refsect1>
     <title>[Link] Section Options</title>
 
-    <para> The [Link] section accepts the following keys:</para>
+    <para>The [Link] section accepts the following keys:</para>
 
     <variablelist class='network-directives'>
       <varlistentry>
             has been unsuccessful for some time. (IPv4 link-local address autoconfiguration will usually
             happen in parallel with repeated attempts to acquire a DHCPv4 lease).</para>
 
-            <para>Defaults to <option>no</option> when <varname>Bridge=</varname> is set or when the specified
+            <para>Defaults to <option>no</option> when <varname>KeepMaster=</varname> or
+            <varname>Bridge=</varname> is set or when the specified
             <varname>MACVLAN=</varname>/<varname>MACVTAP=</varname> has <varname>Mode=passthru</varname>, or
             <option>ipv6</option> otherwise.</para>
           </listitem>
@@ -923,6 +924,20 @@ IPv6Token=prefixstable:2002:da8:1::</programlisting></para>
           An integer greater than or equal to 1280 bytes. When unset, the kernel's default will be used.
           </para></listitem>
         </varlistentry>
+        <varlistentry>
+          <term><varname>KeepMaster=</varname></term>
+          <term><varname>Bond=</varname></term>
+          <term><varname>Bridge=</varname></term>
+          <term><varname>VRF=</varname></term>
+          <listitem>
+            <para>Takes a boolean value. When enabled, the current master interface index will not be
+            changed, and <varname>BatmanAdvanced=</varname>, <varname>Bond=</varname>,
+            <varname>Bridge=</varname>, and <varname>VRF=</varname> settings are ignored. This may be
+            useful when a netdev with a master interface is created by another program, e.g.
+            <citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
+            Defaults to false.</para>
+          </listitem>
+        </varlistentry>
         <varlistentry>
           <term><varname>BatmanAdvanced=</varname></term>
           <term><varname>Bond=</varname></term>
diff --git a/network/80-container-vb.network b/network/80-container-vb.network
new file mode 100644 (file)
index 0000000..37f43c6
--- /dev/null
@@ -0,0 +1,22 @@
+#  SPDX-License-Identifier: LGPL-2.1-or-later
+#
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU Lesser General Public License as published by
+#  the Free Software Foundation; either version 2.1 of the License, or
+#  (at your option) any later version.
+
+# This network file matches the host-side of the virtual Ethernet link
+# created by systemd-nspawn's --network-veth switch with --network-bridge or
+# --network-zone switch. See systemd-nspawn(1) for details.
+
+[Match]
+Name=vb-*
+Driver=veth
+
+[Network]
+KeepMaster=yes
+LinkLocalAddressing=no
+LLDP=yes
+EmitLLDP=nearest-bridge
index 6eca4708265c980850f427594d155c2d60d8919b..ed8a10cad809f468d84b29f43a1364acc0439cbc 100644 (file)
@@ -92,7 +92,7 @@ static int bridge_mdb_configure_handler(sd_netlink *rtnl, sd_netlink_message *m,
                 return 1;
 
         r = sd_netlink_message_get_errno(m);
-        if (r == -EINVAL && streq_ptr(link->kind, "bridge") && (!link->network || !link->network->bridge)) {
+        if (r == -EINVAL && streq_ptr(link->kind, "bridge") && link->master_ifindex <= 0) {
                 /* To configure bridge MDB entries on bridge master, 1bc844ee0faa1b92e3ede00bdd948021c78d7088 (v5.4) is required. */
                 if (!link->manager->bridge_mdb_on_master_not_supported) {
                         log_link_warning_errno(link, r, "Kernel seems not to support bridge MDB entries on bridge master, ignoring: %m");
@@ -112,23 +112,11 @@ static int bridge_mdb_configure_handler(sd_netlink *rtnl, sd_netlink_message *m,
         return 1;
 }
 
-static int link_get_bridge_master_ifindex(Link *link) {
-        assert(link);
-
-        if (link->network && link->network->bridge)
-                return link->network->bridge->ifindex;
-
-        if (streq_ptr(link->kind, "bridge"))
-                return link->ifindex;
-
-        return 0;
-}
-
 /* send a request to the kernel to add an MDB entry */
 static int bridge_mdb_configure(BridgeMDB *mdb, Link *link, link_netlink_message_handler_t callback) {
         _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
         struct br_mdb_entry entry;
-        int master, r;
+        int r;
 
         assert(mdb);
         assert(link);
@@ -144,14 +132,10 @@ static int bridge_mdb_configure(BridgeMDB *mdb, Link *link, link_netlink_message
                                strna(a), mdb->vlan_id);
         }
 
-        master = link_get_bridge_master_ifindex(link);
-        if (master <= 0)
-                return log_link_error_errno(link, SYNTHETIC_ERRNO(EINVAL), "Invalid bridge master ifindex %i", master);
-
         entry = (struct br_mdb_entry) {
                 /* If MDB entry is added on bridge master, then the state must be MDB_TEMPORARY.
                  * See br_mdb_add_group() in net/bridge/br_mdb.c of kernel. */
-                .state = master == link->ifindex ? MDB_TEMPORARY : MDB_PERMANENT,
+                .state = link->master_ifindex <= 0 ? MDB_TEMPORARY : MDB_PERMANENT,
                 .ifindex = link->ifindex,
                 .vid = mdb->vlan_id,
         };
@@ -172,7 +156,8 @@ static int bridge_mdb_configure(BridgeMDB *mdb, Link *link, link_netlink_message
         }
 
         /* create new RTM message */
-        r = sd_rtnl_message_new_mdb(link->manager->rtnl, &req, RTM_NEWMDB, master);
+        r = sd_rtnl_message_new_mdb(link->manager->rtnl, &req, RTM_NEWMDB,
+                                    link->master_ifindex > 0 ? link->master_ifindex : link->ifindex);
         if (r < 0)
                 return log_link_error_errno(link, r, "Could not create RTM_NEWMDB message: %m");
 
@@ -205,16 +190,6 @@ int link_request_static_bridge_mdb(Link *link) {
         if (hashmap_isempty(link->network->bridge_mdb_entries_by_section))
                 goto finish;
 
-        if (!link->network->bridge) {
-                if (!streq_ptr(link->kind, "bridge")) {
-                        log_link_warning(link, "Link is neither a bridge master nor a bridge port, ignoring [BridgeMDB] sections.");
-                        goto finish;
-                } else if (link->manager->bridge_mdb_on_master_not_supported) {
-                        log_link_debug(link, "Kernel seems not to support bridge MDB entries on bridge master, ignoring [BridgeMDB] sections.");
-                        goto finish;
-                }
-        }
-
         HASHMAP_FOREACH(mdb, link->network->bridge_mdb_entries_by_section) {
                 r = link_queue_request(link, REQUEST_TYPE_BRIDGE_MDB, mdb, false,
                                        &link->static_bridge_mdb_messages, bridge_mdb_configure_handler, NULL);
@@ -240,16 +215,13 @@ static bool bridge_mdb_is_ready_to_configure(Link *link) {
         if (!link_is_ready_to_configure(link, false))
                 return false;
 
-        if (!link->network->bridge)
-                return true; /* The interface is bridge master. */
-
-        if (link->master_ifindex <= 0)
+        if (!link->master_set)
                 return false;
 
-        if (link->master_ifindex != link->network->bridge->ifindex)
-                return false;
+        if (link->master_ifindex <= 0 && streq_ptr(link->kind, "bridge"))
+                return true; /* The interface is bridge master. */
 
-        if (link_get_by_index(link->manager, link->master_ifindex, &master) < 0)
+        if (link_get_master(link, &master) < 0)
                 return false;
 
         if (!streq_ptr(master->kind, "bridge"))
index 59313bcdfd6c8949f1da752d2fab8d82ba313338..4afd540d201512f97c196341543bef40abc18f18 100644 (file)
@@ -1689,17 +1689,11 @@ bool link_has_carrier(Link *link) {
 
 static bool link_is_enslaved(Link *link) {
         if (link->flags & IFF_SLAVE)
-                /* Even if the link is not managed by networkd, honor IFF_SLAVE flag. */
                 return true;
 
-        if (!link->network)
-                return false;
-
-        if (link->master_ifindex > 0 && link->network->bridge)
+        if (link->master_ifindex > 0)
                 return true;
 
-        /* TODO: add conditions for other netdevs. */
-
         return false;
 }
 
@@ -2015,6 +2009,9 @@ static int link_update_master(Link *link, sd_netlink_message *message) {
         if (r < 0)
                 return log_link_debug_errno(link, r, "rtnl: failed to read master ifindex: %m");
 
+        if (master_ifindex == link->ifindex)
+                master_ifindex = 0;
+
         if (master_ifindex == link->master_ifindex)
                 return 0;
 
index 405ddccc8b53863818474acfbb93d853cf85a4d1..ef5248fa38209c0a644d88081f78ec4c841951d8 100644 (file)
@@ -79,6 +79,7 @@ SR-IOV.Trust,                                config_parse_sr_iov_boolean,
 SR-IOV.LinkState,                            config_parse_sr_iov_link_state,                           0,                             0
 SR-IOV.MACAddress,                           config_parse_sr_iov_mac,                                  0,                             0
 Network.Description,                         config_parse_string,                                      0,                             offsetof(Network, description)
+Network.KeepMaster,                          config_parse_bool,                                        0,                             offsetof(Network, keep_master)
 Network.BatmanAdvanced,                      config_parse_ifname,                                      0,                             offsetof(Network, batadv_name)
 Network.Bond,                                config_parse_ifname,                                      0,                             offsetof(Network, bond_name)
 Network.Bridge,                              config_parse_ifname,                                      0,                             offsetof(Network, bridge_name)
index cdca91c7c2e78838c33f7bcdc41694729a142948..4aee6fa43cac19a668880fcc01cf5e9131dc8fc0 100644 (file)
@@ -135,6 +135,26 @@ int network_verify(Network *network) {
                                        "%s: Conditions in the file do not match the system environment, skipping.",
                                        network->filename);
 
+        if (network->keep_master) {
+                if (network->batadv_name)
+                        log_warning("%s: BatmanAdvanced= set with KeepMaster= enabled, ignoring BatmanAdvanced=.",
+                                    network->filename);
+                if (network->bond_name)
+                        log_warning("%s: Bond= set with KeepMaster= enabled, ignoring Bond=.",
+                                    network->filename);
+                if (network->bridge_name)
+                        log_warning("%s: Bridge= set with KeepMaster= enabled, ignoring Bridge=.",
+                                    network->filename);
+                if (network->vrf_name)
+                        log_warning("%s: VRF= set with KeepMaster= enabled, ignoring VRF=.",
+                                    network->filename);
+
+                network->batadv_name = mfree(network->batadv_name);
+                network->bond_name = mfree(network->bond_name);
+                network->bridge_name = mfree(network->bridge_name);
+                network->vrf_name = mfree(network->vrf_name);
+        }
+
         (void) network_resolve_netdev_one(network, network->batadv_name, NETDEV_KIND_BATADV, &network->batadv);
         (void) network_resolve_netdev_one(network, network->bond_name, NETDEV_KIND_BOND, &network->bond);
         (void) network_resolve_netdev_one(network, network->bridge_name, NETDEV_KIND_BRIDGE, &network->bridge);
@@ -174,7 +194,7 @@ int network_verify(Network *network) {
         if (network->link_local < 0) {
                 network->link_local = ADDRESS_FAMILY_IPV6;
 
-                if (network->bridge)
+                if (network->keep_master || network->bridge)
                         network->link_local = ADDRESS_FAMILY_NO;
                 else {
                         NetDev *netdev;
index 95c86e72304066423dd4593be59f11655458615c..81d484ce535b04cddf378d58f4bb970ce796ff9c 100644 (file)
@@ -84,6 +84,7 @@ struct Network {
         LIST_HEAD(Condition, conditions);
 
         /* Master or stacked netdevs */
+        bool keep_master;
         NetDev *batadv;
         NetDev *bridge;
         NetDev *bond;
index f09c3185170ba6b7a8349d51d16c7a1891661a62..21e20f10f5958a9bc6a47744d509588c06b22294 100644 (file)
@@ -405,7 +405,7 @@ static int link_configure(
                 if (r < 0)
                         return log_link_debug_errno(link, r, "Could not open IFLA_AF_SPEC container: %m");
 
-                if (!link->network->bridge) {
+                if (link->master_ifindex <= 0) {
                         /* master needs BRIDGE_FLAGS_SELF flag */
                         r = sd_netlink_message_append_u16(req, IFLA_BRIDGE_FLAGS, BRIDGE_FLAGS_SELF);
                         if (r < 0)
@@ -515,9 +515,16 @@ static bool link_is_ready_to_call_set_link(Request *req) {
         switch (op) {
         case SET_LINK_BOND:
         case SET_LINK_BRIDGE:
+                if (!link->master_set)
+                        return false;
+                if (link->network->keep_master && link->master_ifindex <= 0)
+                        return false;
+                break;
         case SET_LINK_BRIDGE_VLAN:
                 if (!link->master_set)
                         return false;
+                if (link->network->keep_master && link->master_ifindex <= 0 && !streq_ptr(link->kind, "bridge"))
+                        return false;
                 break;
         case SET_LINK_CAN:
                 /* Do not check link->set_flgas_messages here, as it is ok even if link->flags
@@ -672,8 +679,18 @@ int link_request_to_set_bond(Link *link) {
         assert(link);
         assert(link->network);
 
-        if (!link->network->bond)
-                return 0;
+        if (!link->network->bond) {
+                Link *master;
+
+                if (!link->network->keep_master)
+                        return 0;
+
+                if (link_get_master(link, &master) < 0)
+                        return 0;
+
+                if (!streq_ptr(master->kind, "bond"))
+                        return 0;
+        }
 
         return link_request_set_link(link, SET_LINK_BOND, link_set_bond_handler, NULL);
 }
@@ -682,8 +699,18 @@ int link_request_to_set_bridge(Link *link) {
         assert(link);
         assert(link->network);
 
-        if (!link->network->bridge)
-                return 0;
+        if (!link->network->bridge) {
+                Link *master;
+
+                if (!link->network->keep_master)
+                        return 0;
+
+                if (link_get_master(link, &master) < 0)
+                        return 0;
+
+                if (!streq_ptr(master->kind, "bridge"))
+                        return 0;
+        }
 
         return link_request_set_link(link, SET_LINK_BRIDGE, link_set_bridge_handler, NULL);
 }
@@ -695,8 +722,18 @@ int link_request_to_set_bridge_vlan(Link *link) {
         if (!link->network->use_br_vlan)
                 return 0;
 
-        if (!link->network->bridge && !streq_ptr(link->kind, "bridge"))
-                return 0;
+        if (!link->network->bridge && !streq_ptr(link->kind, "bridge")) {
+                Link *master;
+
+                if (!link->network->keep_master)
+                        return 0;
+
+                if (link_get_master(link, &master) < 0)
+                        return 0;
+
+                if (!streq_ptr(master->kind, "bridge"))
+                        return 0;
+        }
 
         return link_request_set_link(link, SET_LINK_BRIDGE_VLAN, link_set_bridge_vlan_handler, NULL);
 }
@@ -763,6 +800,11 @@ int link_request_to_set_master(Link *link) {
         assert(link);
         assert(link->network);
 
+        if (link->network->keep_master) {
+                link->master_set = true;
+                return 0;
+        }
+
         link->master_set = false;
 
         if (link->network->batadv || link->network->bond || link->network->bridge || link->network->vrf)
index a3711cb77d9c487ee24b6c8de89657260f246882..71ead0df9c7b6a767ba981f0efece8c67e3d075a 100644 (file)
@@ -178,6 +178,7 @@ MultiPathRoute=
 TCPAdvertisedMaximumSegmentSize=
 NextHop=
 [Network]
+KeepMaster=
 IPv6DuplicateAddressDetection=
 IPMasquerade=
 ProxyARP=
diff --git a/test/test-network/conf/23-keep-master.network b/test/test-network/conf/23-keep-master.network
new file mode 100644 (file)
index 0000000..2d8371c
--- /dev/null
@@ -0,0 +1,21 @@
+[Match]
+Name=dummy98
+
+[Network]
+LinkLocalAddressing=no
+IPv6AcceptRA=no
+KeepMaster=true
+ActiveSlave=true
+
+[Bridge]
+Cost=400
+HairPin = true
+FastLeave = true
+UnicastFlood = true
+MulticastFlood = false
+MulticastToUnicast = true
+NeighborSuppression = true
+Learning = false
+Priority = 23
+UseBPDU = true
+AllowPortToBeRoot=true
index 02f889ea5ef6cbd8583861f54006bdabebe03709..1754ebd3d6e6773e24064440f5c0401fba5d07c6 100755 (executable)
@@ -3388,6 +3388,7 @@ class NetworkdBondTests(unittest.TestCase, Utilities):
         '12-dummy.netdev',
         '23-active-slave.network',
         '23-bond199.network',
+        '23-keep-master.network',
         '23-primary-slave.network',
         '25-bond-active-backup-slave.netdev',
         '25-bond.netdev',
@@ -3403,6 +3404,23 @@ class NetworkdBondTests(unittest.TestCase, Utilities):
         remove_unit_from_networkd_path(self.units)
         stop_networkd(show_logs=True)
 
+    def test_bond_keep_master(self):
+        check_output('ip link add bond199 type bond mode active-backup')
+        check_output('ip link add dummy98 type dummy')
+        check_output('ip link set dummy98 master bond199')
+
+        copy_unit_to_networkd_unit_path('23-keep-master.network')
+        start_networkd()
+        self.wait_online(['dummy98:enslaved'])
+
+        output = check_output('ip -d link show bond199')
+        print(output)
+        self.assertRegex(output, 'active_slave dummy98')
+
+        output = check_output('ip -d link show dummy98')
+        print(output)
+        self.assertRegex(output, 'master bond199')
+
     def test_bond_active_slave(self):
         copy_unit_to_networkd_unit_path('23-active-slave.network', '23-bond199.network', '25-bond-active-backup-slave.netdev', '12-dummy.netdev')
         start_networkd()
@@ -3479,6 +3497,7 @@ class NetworkdBridgeTests(unittest.TestCase, Utilities):
         '12-dummy.netdev',
         '21-vlan.netdev',
         '21-vlan.network',
+        '23-keep-master.network',
         '26-bridge.netdev',
         '26-bridge-configure-without-carrier.network',
         '26-bridge-issue-20373.netdev',
@@ -3562,6 +3581,38 @@ 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')
 
+    def test_bridge_keep_master(self):
+        check_output('ip link add bridge99 type bridge')
+        check_output('ip link set bridge99 up')
+        check_output('ip link add dummy98 type dummy')
+        check_output('ip link set dummy98 master bridge99')
+
+        copy_unit_to_networkd_unit_path('23-keep-master.network')
+        start_networkd()
+        self.wait_online(['dummy98:enslaved'])
+
+        output = check_output('ip -d link show dummy98')
+        print(output)
+        self.assertRegex(output, 'master bridge99')
+        self.assertRegex(output, 'bridge')
+
+        output = check_output('bridge -d link show dummy98')
+        print(output)
+        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'path_cost'), '400')
+        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'hairpin_mode'), '1')
+        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'multicast_fast_leave'), '1')
+        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'unicast_flood'), '1')
+        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'multicast_flood'), '0')
+        # CONFIG_BRIDGE_IGMP_SNOOPING=y
+        if (os.path.exists('/sys/devices/virtual/net/bridge00/lower_dummy98/brport/multicast_to_unicast')):
+            self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'multicast_to_unicast'), '1')
+        if (os.path.exists('/sys/devices/virtual/net/bridge99/lower_dummy98/brport/neigh_suppress')):
+            self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'neigh_suppress'), '1')
+        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'learning'), '0')
+        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'priority'), '23')
+        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'bpdu_guard'), '1')
+        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'root_block'), '1')
+
     def test_bridge_property(self):
         copy_unit_to_networkd_unit_path('11-dummy.netdev', '12-dummy.netdev', '26-bridge.netdev',
                                         '26-bridge-slave-interface-1.network', '26-bridge-slave-interface-2.network',