]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #11681 from yuwata/network-link-enslaved-operstate
authorLennart Poettering <lennart@poettering.net>
Mon, 18 Feb 2019 12:00:13 +0000 (13:00 +0100)
committerGitHub <noreply@github.com>
Mon, 18 Feb 2019 12:00:13 +0000 (13:00 +0100)
network: introduce new 'enslaved' operstate

man/networkctl.xml
src/network/networkctl.c
src/network/networkd-address.c
src/network/networkd-link.c
src/network/networkd-link.h
test/test-network/conf/bond99.network [new file with mode: 0644]
test/test-network/conf/dhcp-server.network
test/test-network/conf/veth-bond.network [new file with mode: 0644]
test/test-network/conf/vlan6.netdev [new file with mode: 0644]
test/test-network/conf/vlan6.network [new file with mode: 0644]
test/test-network/systemd-networkd-tests.py

index 7b419d6f7271c7a3908b2e82f1958678df72abb7..955d19994930a3358112306e8171ad5681555c96 100644 (file)
               <varlistentry>
                 <term>carrier</term>
                 <listitem>
-                  <para>the link has a carrier</para>
+                  <para>the link has a carrier, or for bond master, all bonding slave network interfaces are
+                  enslaved to the master.</para>
                 </listitem>
               </varlistentry>
               <varlistentry>
                 <term>degraded</term>
                 <listitem>
-                  <para>the link has carrier and addresses valid on the local link configured</para>
+                  <para>the link has carrier and addresses valid on the local link configured, or for bond
+                  master, one of the bonding slave network interfaces is in off, no-carrier, or dormant</para>
+                </listitem>
+              </varlistentry>
+              <varlistentry>
+                <term>enslaved</term>
+                <listitem>
+                  <para>the link has carrier and is enslaved to other network interfaces</para>
                 </listitem>
               </varlistentry>
               <varlistentry>
index 20edac46795c12eb1bd705815bf4b0f6e98e3bf7..2803f5210b10f98ea988e8bf94315c550a3de63f 100644 (file)
@@ -65,7 +65,7 @@ static void operational_state_to_color(const char *state, const char **on, const
         assert(on);
         assert(off);
 
-        if (streq_ptr(state, "routable")) {
+        if (STRPTR_IN_SET(state, "routable", "enslaved")) {
                 *on = ansi_highlight_green();
                 *off = ansi_normal();
         } else if (streq_ptr(state, "degraded")) {
index 06e2662cdd03fff1965642242e5bbb779fa59c83..aa827d6ba64977a314f4d202d12ae541557cb85c 100644 (file)
@@ -351,7 +351,7 @@ int address_update(
         address->scope = scope;
         address->cinfo = *cinfo;
 
-        link_update_operstate(address->link);
+        link_update_operstate(address->link, true);
         link_check_ready(address->link);
 
         if (!ready &&
@@ -380,7 +380,7 @@ int address_drop(Address *address) {
         address_release(address);
         address_free(address);
 
-        link_update_operstate(link);
+        link_update_operstate(link, true);
 
         if (link && !ready)
                 link_check_ready(link);
index ceb960a032f0770d32fc44b394f6cda2a7875d1e..0cff3cf62729c090690ca4e55e86a3d5c539f95d 100644 (file)
@@ -70,6 +70,9 @@ static bool link_dhcp6_enabled(Link *link) {
         if (!link->network)
                 return false;
 
+        if (link->network->bond)
+                return false;
+
         return link->network->dhcp & ADDRESS_FAMILY_IPV6;
 }
 
@@ -82,6 +85,9 @@ static bool link_dhcp4_enabled(Link *link) {
         if (!link->network)
                 return false;
 
+        if (link->network->bond)
+                return false;
+
         return link->network->dhcp & ADDRESS_FAMILY_IPV4;
 }
 
@@ -94,6 +100,9 @@ static bool link_dhcp4_server_enabled(Link *link) {
         if (!link->network)
                 return false;
 
+        if (link->network->bond)
+                return false;
+
         return link->network->dhcp_server;
 }
 
@@ -109,6 +118,9 @@ static bool link_ipv4ll_enabled(Link *link) {
         if (STRPTR_IN_SET(link->kind, "vrf", "wireguard"))
                 return false;
 
+        if (link->network->bond)
+                return false;
+
         return link->network->link_local & ADDRESS_FAMILY_IPV4;
 }
 
@@ -127,6 +139,9 @@ static bool link_ipv6ll_enabled(Link *link) {
         if (STRPTR_IN_SET(link->kind, "vrf", "wireguard"))
                 return false;
 
+        if (link->network->bond)
+                return false;
+
         return link->network->link_local & ADDRESS_FAMILY_IPV6;
 }
 
@@ -136,7 +151,7 @@ static bool link_ipv6_enabled(Link *link) {
         if (!socket_ipv6_is_supported())
                 return false;
 
-        if (link->network->bridge)
+        if (link->network->bridge || link->network->bond)
                 return false;
 
         /* DHCPv6 client will not be started if no IPv6 link-local address is configured. */
@@ -303,8 +318,9 @@ static int link_enable_ipv6(Link *link) {
         return 0;
 }
 
-void link_update_operstate(Link *link) {
+void link_update_operstate(Link *link, bool also_update_bond_master) {
         LinkOperationalState operstate;
+
         assert(link);
 
         if (link->kernel_operstate == IF_OPER_DORMANT)
@@ -346,11 +362,38 @@ void link_update_operstate(Link *link) {
         else
                 operstate = LINK_OPERSTATE_OFF;
 
+        if (IN_SET(operstate, LINK_OPERSTATE_DEGRADED, LINK_OPERSTATE_CARRIER) &&
+            link->flags & IFF_SLAVE)
+                operstate = LINK_OPERSTATE_ENSLAVED;
+
+        if (IN_SET(operstate, LINK_OPERSTATE_CARRIER, LINK_OPERSTATE_ENSLAVED, LINK_OPERSTATE_ROUTABLE) &&
+            !hashmap_isempty(link->bond_slaves)) {
+                Iterator i;
+                Link *slave;
+
+                HASHMAP_FOREACH(slave, link->bond_slaves, i) {
+                        link_update_operstate(slave, false);
+
+                        if (IN_SET(slave->operstate,
+                                   LINK_OPERSTATE_OFF, LINK_OPERSTATE_NO_CARRIER, LINK_OPERSTATE_DORMANT))
+                                operstate = LINK_OPERSTATE_DEGRADED;
+                }
+        }
+
         if (link->operstate != operstate) {
                 link->operstate = operstate;
                 link_send_changed(link, "OperationalState", NULL);
                 link_dirty(link);
         }
+
+        if (also_update_bond_master && link->network && link->network->bond) {
+                Link *master;
+
+                if (link_get(link->manager, link->network->bond->ifindex, &master) < 0)
+                        return;
+
+                link_update_operstate(master, true);
+        }
 }
 
 #define FLAG_STRING(string, flag, old, new) \
@@ -425,7 +468,7 @@ static int link_update_flags(Link *link, sd_netlink_message *m) {
         link->flags = flags;
         link->kernel_operstate = operstate;
 
-        link_update_operstate(link);
+        link_update_operstate(link, true);
 
         return 0;
 }
@@ -606,6 +649,8 @@ static Link *link_free(Link *link) {
                 hashmap_remove(link->bound_by_links, INT_TO_PTR(carrier->ifindex));
         hashmap_free(link->bound_by_links);
 
+        hashmap_free(link->bond_slaves);
+
         return mfree(link);
 }
 
@@ -1539,7 +1584,26 @@ static int link_set_bridge(Link *link) {
         return r;
 }
 
-static int link_bond_set(Link *link) {
+static int link_set_bond_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+        int r;
+
+        assert(m);
+        assert(link);
+        assert(link->ifname);
+
+        if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+                return 1;
+
+        r = sd_netlink_message_get_errno(m);
+        if (r < 0) {
+                log_link_warning_errno(link, r, "Could not set bonding interface: %m");
+                return 1;
+        }
+
+        return 1;
+}
+
+static int link_set_bond(Link *link) {
         _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
         int r;
 
@@ -1582,7 +1646,7 @@ static int link_bond_set(Link *link) {
         if (r < 0)
                 return log_link_error_errno(link, r, "Could not append IFLA_INFO_DATA attribute: %m");
 
-        r = netlink_call_async(link->manager->rtnl, NULL, req, set_flags_handler,
+        r = netlink_call_async(link->manager->rtnl, NULL, req, link_set_bond_handler,
                                link_netlink_destroy_callback, link);
         if (r < 0)
                 return log_link_error_errno(link, r,  "Could not send rtnetlink message: %m");
@@ -1592,6 +1656,29 @@ static int link_bond_set(Link *link) {
         return r;
 }
 
+static int link_append_bond_slave(Link *link) {
+        Link *master;
+        int r;
+
+        assert(link);
+        assert(link->network);
+        assert(link->network->bond);
+
+        r = link_get(link->manager, link->network->bond->ifindex, &master);
+        if (r < 0)
+                return r;
+
+        r = hashmap_ensure_allocated(&master->bond_slaves, NULL);
+        if (r < 0)
+                return r;
+
+        r = hashmap_put(master->bond_slaves, INT_TO_PTR(link->ifindex), link);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
 static int link_lldp_save(Link *link) {
         _cleanup_free_ char *temp_path = NULL;
         _cleanup_fclose_ FILE *f = NULL;
@@ -2393,9 +2480,13 @@ static int link_joined(Link *link) {
         }
 
         if (link->network->bond) {
-                r = link_bond_set(link);
+                r = link_set_bond(link);
                 if (r < 0)
                         log_link_error_errno(link, r, "Could not set bond message: %m");
+
+                r = link_append_bond_slave(link);
+                if (r < 0)
+                        log_link_error_errno(link, r, "Failed to add to bond master's slave list: %m");
         }
 
         if (link->network->use_br_vlan &&
@@ -4200,6 +4291,7 @@ static const char* const link_operstate_table[_LINK_OPERSTATE_MAX] = {
         [LINK_OPERSTATE_DORMANT] = "dormant",
         [LINK_OPERSTATE_CARRIER] = "carrier",
         [LINK_OPERSTATE_DEGRADED] = "degraded",
+        [LINK_OPERSTATE_ENSLAVED] = "enslaved",
         [LINK_OPERSTATE_ROUTABLE] = "routable",
 };
 
index 344873f303ff58a2acd88d3ff376b4a47890a47b..37be3e3bb3818713dce189a069ffdffa4ceba679 100644 (file)
@@ -34,6 +34,7 @@ typedef enum LinkOperationalState {
         LINK_OPERSTATE_DORMANT,
         LINK_OPERSTATE_CARRIER,
         LINK_OPERSTATE_DEGRADED,
+        LINK_OPERSTATE_ENSLAVED,
         LINK_OPERSTATE_ROUTABLE,
         _LINK_OPERSTATE_MAX,
         _LINK_OPERSTATE_INVALID = -1
@@ -128,6 +129,7 @@ typedef struct Link {
 
         Hashmap *bound_by_links;
         Hashmap *bound_to_links;
+        Hashmap *bond_slaves;
 } Link;
 
 typedef int (*link_netlink_message_handler_t)(sd_netlink*, sd_netlink_message*, Link*);
@@ -150,7 +152,7 @@ int link_initialized(Link *link, sd_device *device);
 
 void link_check_ready(Link *link);
 
-void link_update_operstate(Link *link);
+void link_update_operstate(Link *link, bool also_update_bond_master);
 int link_update(Link *link, sd_netlink_message *message);
 
 void link_dirty(Link *link);
diff --git a/test/test-network/conf/bond99.network b/test/test-network/conf/bond99.network
new file mode 100644 (file)
index 0000000..9c18eeb
--- /dev/null
@@ -0,0 +1,6 @@
+[Match]
+Name=bond99
+
+[Network]
+IPv6AcceptRA=no
+DHCP=yes
index 9e49691a9b61317094cc7501654f8976c265bc90..439258a53994c8a887257b941bc2817fea0107d3 100644 (file)
@@ -3,6 +3,7 @@ Name=veth-peer
 
 [Network]
 Address=192.168.5.1/24
+IPv6AcceptRA=false
 DHCPServer=yes
 
 [DHCPServer]
diff --git a/test/test-network/conf/veth-bond.network b/test/test-network/conf/veth-bond.network
new file mode 100644 (file)
index 0000000..2095b7a
--- /dev/null
@@ -0,0 +1,6 @@
+[Match]
+Name=veth99
+
+[Network]
+Bond=bond99
+IPv6AcceptRA=false
diff --git a/test/test-network/conf/vlan6.netdev b/test/test-network/conf/vlan6.netdev
new file mode 100644 (file)
index 0000000..310be91
--- /dev/null
@@ -0,0 +1,7 @@
+[NetDev]
+Name=vlan6
+Kind=vlan
+MTUBytes=1500
+
+[VLAN]
+Id=6
diff --git a/test/test-network/conf/vlan6.network b/test/test-network/conf/vlan6.network
new file mode 100644 (file)
index 0000000..64e9db5
--- /dev/null
@@ -0,0 +1,6 @@
+[Match]
+Name=vlan6
+
+[Network]
+IPv6AcceptRA=false
+Address=100.100.100.2/24
index f41e824a836a74c2f7014286584347a6515aa878..ca6e977d74fdad777460a78a5cf5a91092706102 100755 (executable)
@@ -1004,6 +1004,80 @@ class NetworkdNetWorkTests(unittest.TestCase, Utilities):
         output = subprocess.check_output(['networkctl', 'status', 'test1']).rstrip().decode('utf-8')
         self.assertRegex(output, 'State: routable \(configured\)')
 
+class NetworkdNetWorkBondTests(unittest.TestCase, Utilities):
+    links = [
+        'bond99',
+        'veth99']
+
+    units = [
+        '25-bond.netdev',
+        '25-veth.netdev',
+        'bond99.network',
+        'dhcp-server.network',
+        'veth-bond.network']
+
+    def setUp(self):
+        self.link_remove(self.links)
+
+    def tearDown(self):
+        self.link_remove(self.links)
+        self.remove_unit_from_networkd_path(self.units)
+
+    def test_bridge_property(self):
+        self.copy_unit_to_networkd_unit_path('25-bond.netdev', '25-veth.netdev', 'bond99.network',
+                                             'dhcp-server.network', 'veth-bond.network')
+        self.start_networkd()
+
+        self.assertTrue(self.link_exits('bond99'))
+        self.assertTrue(self.link_exits('veth99'))
+        self.assertTrue(self.link_exits('veth-peer'))
+
+        output = subprocess.check_output(['ip', '-d', 'link', 'show', 'veth-peer']).rstrip().decode('utf-8')
+        print(output)
+        self.assertRegex(output, 'UP,LOWER_UP')
+
+        output = subprocess.check_output(['ip', '-d', 'link', 'show', 'veth99']).rstrip().decode('utf-8')
+        print(output)
+        self.assertRegex(output, 'SLAVE,UP,LOWER_UP')
+
+        output = subprocess.check_output(['ip', '-d', 'link', 'show', 'bond99']).rstrip().decode('utf-8')
+        print(output)
+        self.assertRegex(output, 'MASTER,UP,LOWER_UP')
+
+        output = subprocess.check_output(['networkctl', 'status', 'veth-peer']).rstrip().decode('utf-8')
+        print(output)
+        self.assertRegex(output, 'State: routable \(configured\)')
+
+        output = subprocess.check_output(['networkctl', 'status', 'veth99']).rstrip().decode('utf-8')
+        print(output)
+        self.assertRegex(output, 'State: enslaved \(configured\)')
+
+        output = subprocess.check_output(['networkctl', 'status', 'bond99']).rstrip().decode('utf-8')
+        print(output)
+        self.assertRegex(output, 'State: routable \(configured\)')
+
+        self.assertEqual(subprocess.call(['ip', 'link', 'set', 'veth99', 'down']), 0)
+        time.sleep(2)
+
+        output = subprocess.check_output(['networkctl', 'status', 'veth99']).rstrip().decode('utf-8')
+        print(output)
+        self.assertRegex(output, 'State: off \(configured\)')
+
+        output = subprocess.check_output(['networkctl', 'status', 'bond99']).rstrip().decode('utf-8')
+        print(output)
+        self.assertRegex(output, 'State: degraded \(configured\)')
+
+        self.assertEqual(subprocess.call(['ip', 'link', 'set', 'veth99', 'up']), 0)
+        time.sleep(2)
+
+        output = subprocess.check_output(['networkctl', 'status', 'veth99']).rstrip().decode('utf-8')
+        print(output)
+        self.assertRegex(output, 'State: enslaved \(configured\)')
+
+        output = subprocess.check_output(['networkctl', 'status', 'bond99']).rstrip().decode('utf-8')
+        print(output)
+        self.assertRegex(output, 'State: routable \(configured\)')
+
 class NetworkdNetWorkBridgeTests(unittest.TestCase, Utilities):
     links = [
         'bridge99',