]> 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

1  2 
src/network/networkd-link.c
src/network/networkd-link.h
test/test-network/systemd-networkd-tests.py

index ceb960a032f0770d32fc44b394f6cda2a7875d1e,acb4943b843b510c697d7a7fc24852d3a566bd22..0cff3cf62729c090690ca4e55e86a3d5c539f95d
@@@ -14,7 -14,6 +14,7 @@@
  #include "fd-util.h"
  #include "fileio.h"
  #include "missing_network.h"
 +#include "netdev/vrf.h"
  #include "netlink-util.h"
  #include "network-internal.h"
  #include "networkd-ipv6-proxy-ndp.h"
  #include "util.h"
  #include "virt.h"
  
 +uint32_t link_get_vrf_table(Link *link) {
 +        return link->network->vrf ? VRF(link->network->vrf)->table : RT_TABLE_MAIN;
 +}
 +
 +uint32_t link_get_dhcp_route_table(Link *link) {
 +        /* When the interface is part of an VRF use the VRFs routing table, unless
 +         * another table is explicitly specified. */
 +        if (link->network->dhcp_route_table_set)
 +                return link->network->dhcp_route_table;
 +        return link_get_vrf_table(link);
 +}
 +
 +uint32_t link_get_ipv6_accept_ra_route_table(Link *link) {
 +        if (link->network->ipv6_accept_ra_route_table_set)
 +                return link->network->ipv6_accept_ra_route_table;
 +        return link_get_vrf_table(link);
 +}
 +
  DUID* link_get_duid(Link *link) {
          if (link->network->duid.type != _DUID_TYPE_INVALID)
                  return &link->network->duid;
@@@ -70,6 -51,9 +70,9 @@@ static bool link_dhcp6_enabled(Link *li
          if (!link->network)
                  return false;
  
+         if (link->network->bond)
+                 return false;
          return link->network->dhcp & ADDRESS_FAMILY_IPV6;
  }
  
@@@ -82,6 -66,9 +85,9 @@@ static bool link_dhcp4_enabled(Link *li
          if (!link->network)
                  return false;
  
+         if (link->network->bond)
+                 return false;
          return link->network->dhcp & ADDRESS_FAMILY_IPV4;
  }
  
@@@ -94,6 -81,9 +100,9 @@@ static bool link_dhcp4_server_enabled(L
          if (!link->network)
                  return false;
  
+         if (link->network->bond)
+                 return false;
          return link->network->dhcp_server;
  }
  
@@@ -106,9 -96,12 +115,12 @@@ static bool link_ipv4ll_enabled(Link *l
          if (!link->network)
                  return false;
  
 -        if (streq_ptr(link->kind, "wireguard"))
 +        if (STRPTR_IN_SET(link->kind, "vrf", "wireguard"))
                  return false;
  
+         if (link->network->bond)
+                 return false;
          return link->network->link_local & ADDRESS_FAMILY_IPV4;
  }
  
@@@ -124,9 -117,12 +136,12 @@@ static bool link_ipv6ll_enabled(Link *l
          if (!link->network)
                  return false;
  
 -        if (streq_ptr(link->kind, "wireguard"))
 +        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 -132,7 +151,7 @@@ static bool link_ipv6_enabled(Link *lin
          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 -299,9 +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)
          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 -449,7 +468,7 @@@ static int link_update_flags(Link *link
          link->flags = flags;
          link->kernel_operstate = operstate;
  
-         link_update_operstate(link);
+         link_update_operstate(link, true);
  
          return 0;
  }
@@@ -606,6 -630,8 +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 -1565,26 +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;
  
          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");
          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 -2461,13 +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 &&
@@@ -3584,7 -3656,7 +3675,7 @@@ static int link_carrier_lost(Link *link
  
          assert(link);
  
 -        if (link->network->ignore_carrier_loss)
 +        if (link->network && link->network->ignore_carrier_loss)
                  return 0;
  
          /* Some devices reset itself while setting the MTU. This causes the DHCP client fall into a loop.
@@@ -4200,6 -4272,7 +4291,7 @@@ static const char* const link_operstate
          [LINK_OPERSTATE_DORMANT] = "dormant",
          [LINK_OPERSTATE_CARRIER] = "carrier",
          [LINK_OPERSTATE_DEGRADED] = "degraded",
+         [LINK_OPERSTATE_ENSLAVED] = "enslaved",
          [LINK_OPERSTATE_ROUTABLE] = "routable",
  };
  
index 344873f303ff58a2acd88d3ff376b4a47890a47b,9a43986a323d66eff485b2b284a29bc8acf22eed..37be3e3bb3818713dce189a069ffdffa4ceba679
@@@ -34,6 -34,7 +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 +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 +152,7 @@@ int link_initialized(Link *link, sd_dev
  
  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);
@@@ -185,10 -187,6 +187,10 @@@ int link_node_enumerator(sd_bus *bus, c
  int link_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error);
  int link_send_changed(Link *link, const char *property, ...) _sentinel_;
  
 +uint32_t link_get_vrf_table(Link *link);
 +uint32_t link_get_dhcp_route_table(Link *link);
 +uint32_t link_get_ipv6_accept_ra_route_table(Link *link);
 +
  /* Macros which append INTERFACE= to the message */
  
  #define log_link_full(link, level, error, ...)                          \
index f41e824a836a74c2f7014286584347a6515aa878,9d70a0afcdda001631a0cd304aae06c0bdca02d6..ca6e977d74fdad777460a78a5cf5a91092706102
@@@ -86,6 -86,8 +86,6 @@@ def tearDownModule()
      subprocess.check_call('systemctl start systemd-networkd.service', shell=True)
  
  class Utilities():
 -    dhcp_server_data = []
 -
      def read_link_attr(self, link, dev, attribute):
          with open(os.path.join(os.path.join(os.path.join('/sys/class/net/', link), dev), attribute)) as f:
              return f.readline().strip()
@@@ -955,6 -957,9 +955,6 @@@ class NetworkdNetWorkTests(unittest.Tes
  
          self.assertTrue(self.link_exits('test1'))
  
 -        output = subprocess.check_output(['ip', '-d', 'link', 'show', 'test1']).rstrip().decode('utf-8')
 -        print(output)
 -
          self.assertEqual(subprocess.call(['ip', 'link', 'add', 'dummy98', 'type', 'dummy']), 0)
          self.assertEqual(subprocess.call(['ip', 'link', 'set', 'dummy98', 'up']), 0)
          time.sleep(2)
          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',
@@@ -1210,13 -1289,10 +1284,13 @@@ class NetworkdNetworkDHCPServerTests(un
  class NetworkdNetworkDHCPClientTests(unittest.TestCase, Utilities):
      links = [
          'dummy98',
 -        'veth99']
 +        'veth99',
 +        'vrf99']
  
      units = [
          '25-veth.netdev',
 +        '25-vrf.netdev',
 +        '25-vrf.network',
          'dhcp-client-anonymize.network',
          'dhcp-client-critical-connection.network',
          'dhcp-client-ipv4-dhcp-settings.network',
          'dhcp-client-listen-port.network',
          'dhcp-client-route-metric.network',
          'dhcp-client-route-table.network',
 +        'dhcp-client-vrf.network',
          'dhcp-client.network',
          'dhcp-server-veth-peer.network',
          'dhcp-v4-server-veth-peer.network',
  
          self.start_dnsmasq()
  
 +        print('## ip address show dev veth99')
          output = subprocess.check_output(['ip', 'address', 'show', 'dev', 'veth99']).rstrip().decode('utf-8')
          print(output)
          self.assertRegex(output, '12:34:56:78:9a:bc')
          self.assertRegex(output, '192.168.5')
          self.assertRegex(output, '1492')
  
 -        output = subprocess.check_output(['ip', 'route']).rstrip().decode('utf-8')
 +        # issue #8726
 +        print('## ip route show table main dev veth99')
 +        output = subprocess.check_output(['ip', 'route', 'show', 'table', 'main', 'dev', 'veth99']).rstrip().decode('utf-8')
 +        print(output)
 +        self.assertNotRegex(output, 'proto dhcp')
 +
 +        print('## ip route show table 211 dev veth99')
 +        output = subprocess.check_output(['ip', 'route', 'show', 'table', '211', 'dev', 'veth99']).rstrip().decode('utf-8')
          print(output)
 -        self.assertRegex(output, 'default.*dev veth99 proto dhcp')
 +        self.assertRegex(output, 'default via 192.168.5.1 proto dhcp')
 +        self.assertRegex(output, '192.168.5.0/24 via 192.168.5.5 proto dhcp')
 +        self.assertRegex(output, '192.168.5.1 proto dhcp scope link')
  
 +        print('## dnsmasq log')
          self.assertTrue(self.search_words_in_dnsmasq_log('vendor class: SusantVendorTest', True))
          self.assertTrue(self.search_words_in_dnsmasq_log('DHCPDISCOVER(veth-peer) 12:34:56:78:9a:bc'))
          self.assertTrue(self.search_words_in_dnsmasq_log('client provides name: test-hostname'))
          self.assertRegex(output, '2600::')
          self.assertRegex(output, 'valid_lft forever preferred_lft forever')
  
 +    @expectedFailureIfModuleIsNotAvailable('vrf')
 +    def test_dhcp_client_vrf(self):
 +        self.copy_unit_to_networkd_unit_path('25-veth.netdev', 'dhcp-server-veth-peer.network', 'dhcp-client-vrf.network',
 +                                             '25-vrf.netdev', '25-vrf.network')
 +        self.start_networkd()
 +
 +        self.assertTrue(self.link_exits('veth99'))
 +        self.assertTrue(self.link_exits('vrf99'))
 +
 +        self.start_dnsmasq()
 +
 +        print('## ip -d link show dev vrf99')
 +        output = subprocess.check_output(['ip', '-d', 'link', 'show', 'dev', 'vrf99']).rstrip().decode('utf-8')
 +        print(output)
 +        self.assertRegex(output, 'vrf table 42')
 +
 +        print('## ip address show vrf vrf99')
 +        output_ip_vrf = subprocess.check_output(['ip', 'address', 'show', 'vrf', 'vrf99']).rstrip().decode('utf-8')
 +        print(output_ip_vrf)
 +
 +        print('## ip address show dev veth99')
 +        output = subprocess.check_output(['ip', 'address', 'show', 'dev', 'veth99']).rstrip().decode('utf-8')
 +        print(output)
 +        self.assertEqual(output, output_ip_vrf)
 +        self.assertRegex(output, 'inet 169.254.[0-9]*.[0-9]*/16 brd 169.254.255.255 scope link veth99')
 +        self.assertRegex(output, 'inet 192.168.5.[0-9]*/24 brd 192.168.5.255 scope global dynamic veth99')
 +        self.assertRegex(output, 'inet6 2600::[0-9a-f]*/128 scope global dynamic noprefixroute')
 +        self.assertRegex(output, 'inet6 .* scope link')
 +
 +        print('## ip route show vrf vrf99')
 +        output = subprocess.check_output(['ip', 'route', 'show', 'vrf', 'vrf99']).rstrip().decode('utf-8')
 +        print(output)
 +        self.assertRegex(output, 'default via 192.168.5.1 dev veth99 proto dhcp src 192.168.5.')
 +        self.assertRegex(output, 'default dev veth99 proto static scope link')
 +        self.assertRegex(output, '169.254.0.0/16 dev veth99 proto kernel scope link src 169.254')
 +        self.assertRegex(output, '192.168.5.0/24 dev veth99 proto kernel scope link src 192.168.5')
 +        self.assertRegex(output, '192.168.5.0/24 via 192.168.5.5 dev veth99 proto dhcp')
 +        self.assertRegex(output, '192.168.5.1 dev veth99 proto dhcp scope link src 192.168.5')
 +
 +        print('## ip route show table main dev veth99')
 +        output = subprocess.check_output(['ip', 'route', 'show', 'table', 'main', 'dev', 'veth99']).rstrip().decode('utf-8')
 +        print(output)
 +        self.assertEqual(output, '')
 +
 +        output = subprocess.check_output(['networkctl', 'status', 'vrf99']).rstrip().decode('utf-8')
 +        print(output)
 +        self.assertRegex(output, 'State: carrier \(configured\)')
 +
 +        output = subprocess.check_output(['networkctl', 'status', 'veth99']).rstrip().decode('utf-8')
 +        print(output)
 +        self.assertRegex(output, 'State: routable \(configured\)')
 +
  if __name__ == '__main__':
      unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout,
                                                       verbosity=3))