#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;
if (!link->network)
return false;
+ if (link->network->bond)
+ return false;
+
return link->network->dhcp & ADDRESS_FAMILY_IPV6;
}
if (!link->network)
return false;
+ if (link->network->bond)
+ return false;
+
return link->network->dhcp & ADDRESS_FAMILY_IPV4;
}
if (!link->network)
return false;
+ if (link->network->bond)
+ return false;
+
return link->network->dhcp_server;
}
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;
}
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;
}
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. */
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) \
link->flags = flags;
link->kernel_operstate = operstate;
- link_update_operstate(link);
+ link_update_operstate(link, true);
return 0;
}
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);
}
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;
}
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 &&
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.
[LINK_OPERSTATE_DORMANT] = "dormant",
[LINK_OPERSTATE_CARRIER] = "carrier",
[LINK_OPERSTATE_DEGRADED] = "degraded",
+ [LINK_OPERSTATE_ENSLAVED] = "enslaved",
[LINK_OPERSTATE_ROUTABLE] = "routable",
};
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()
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',
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))