diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index d8ac4552f4c..ace8b254d14 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -6,7 +6,6 @@ #include "alloc-util.h" #include "hostname-util.h" #include "parse-util.h" -#include "netdev/vrf.h" #include "network-internal.h" #include "networkd-link.h" #include "networkd-manager.h" @@ -67,11 +66,7 @@ static int link_set_dhcp_routes(Link *link) { if (!link->network->dhcp_use_routes) return 0; - /* When the interface is part of an VRF use the VRFs routing table, unless - * there is a another table specified. */ - table = link->network->dhcp_route_table; - if (!link->network->dhcp_route_table_set && link->network->vrf != NULL) - table = VRF(link->network->vrf)->table; + table = link_get_dhcp_route_table(link); r = sd_dhcp_lease_get_address(link->dhcp_lease, &address); if (r < 0) @@ -135,14 +130,7 @@ static int link_set_dhcp_routes(Link *link) { log_link_warning(link, "Classless static routes received from DHCP server: ignoring static-route option and router option"); if (r >= 0 && !classless_route) { - _cleanup_(route_freep) Route *route = NULL; - _cleanup_(route_freep) Route *route_gw = NULL; - - r = route_new(&route); - if (r < 0) - return log_link_error_errno(link, r, "Could not allocate route: %m"); - - route->protocol = RTPROT_DHCP; + _cleanup_(route_freep) Route *route = NULL, *route_gw = NULL; r = route_new(&route_gw); if (r < 0) @@ -166,9 +154,14 @@ static int link_set_dhcp_routes(Link *link) { link->dhcp4_messages++; + r = route_new(&route); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate route: %m"); + route->family = AF_INET; route->gw.in = gateway; route->prefsrc.in = address; + route->protocol = RTPROT_DHCP; route->priority = link->network->dhcp_route_metric; route->table = table; diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index c1fba03f9fe..90361c9f4a3 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -142,10 +142,15 @@ int dhcp6_lease_pd_prefix_lost(sd_dhcp6_client *client, Link* link) { continue; } - route_add(link, AF_INET6, &pd_prefix, pd_prefix_len, - 0, 0, 0, &route); - route_update(route, NULL, 0, NULL, NULL, 0, 0, - RTN_UNREACHABLE); + r = route_add(link, AF_INET6, &pd_prefix, pd_prefix_len, 0, 0, 0, &route); + if (r < 0) { + log_link_warning_errno(link, r, "Failed to add unreachable route to delete for DHCPv6 delegated subnet %s/%u: %m", + strnull(buf), + pd_prefix_len); + continue; + } + + route_update(route, NULL, 0, NULL, NULL, 0, 0, RTN_UNREACHABLE); r = route_remove(route, link, dhcp6_route_remove_handler); if (r < 0) { @@ -288,7 +293,8 @@ static int dhcp6_lease_pd_prefix_acquired(sd_dhcp6_client *client, Link *link) { } if (pd_prefix_len < 64) { - Route *route = NULL; + _cleanup_(route_freep) Route *route = NULL; + uint32_t table; (void) in_addr_to_string(AF_INET6, &pd_prefix, &buf); @@ -300,22 +306,26 @@ static int dhcp6_lease_pd_prefix_acquired(sd_dhcp6_client *client, Link *link) { continue; } - route_add(link, AF_INET6, &pd_prefix, pd_prefix_len, - 0, 0, 0, &route); - route_update(route, NULL, 0, NULL, NULL, 0, 0, - RTN_UNREACHABLE); + table = link_get_dhcp_route_table(link); + + r = route_add(link, AF_INET6, &pd_prefix, pd_prefix_len, 0, 0, table, &route); + if (r < 0) { + log_link_warning_errno(link, r, "Failed to add unreachable route for DHCPv6 delegated subnet %s/%u: %m", + strnull(buf), + pd_prefix_len); + continue; + } + + route_update(route, NULL, 0, NULL, NULL, 0, 0, RTN_UNREACHABLE); r = route_configure(route, link, dhcp6_route_handler); if (r < 0) { log_link_warning_errno(link, r, "Cannot configure unreachable route for delegated subnet %s/%u: %m", strnull(buf), pd_prefix_len); - route_free(route); continue; } - route_free(route); - log_link_debug(link, "Configuring unreachable route for %s/%u", strnull(buf), pd_prefix_len); diff --git a/src/network/networkd-ipv4ll.c b/src/network/networkd-ipv4ll.c index 3562e90535d..829dc48a0a1 100644 --- a/src/network/networkd-ipv4ll.c +++ b/src/network/networkd-ipv4ll.c @@ -65,12 +65,28 @@ static int ipv4ll_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *l link->ipv4ll_route = true; - if (link->ipv4ll_address == true) - link_check_ready(link); + link_check_ready(link); return 1; } +static int ipv4ll_route_configure(Link *link) { + _cleanup_(route_freep) Route *route = NULL; + int r; + + r = route_new(&route); + if (r < 0) + return r; + + route->family = AF_INET; + route->scope = RT_SCOPE_LINK; + route->protocol = RTPROT_STATIC; + route->priority = IPV4LL_ROUTE_METRIC; + route->table = link_get_vrf_table(link); + + return route_configure(route, link, ipv4ll_route_handler); +} + static int ipv4ll_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { int r; @@ -86,21 +102,26 @@ static int ipv4ll_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link link->ipv4ll_address = true; - if (link->ipv4ll_route) - link_check_ready(link); + r = ipv4ll_route_configure(link); + if (r < 0) { + log_link_error_errno(link, r, "Failed to configure ipv4ll route: %m"); + link_enter_failed(link); + } return 1; } static int ipv4ll_address_claimed(sd_ipv4ll *ll, Link *link) { _cleanup_(address_freep) Address *ll_addr = NULL; - _cleanup_(route_freep) Route *route = NULL; struct in_addr address; int r; assert(ll); assert(link); + link->ipv4ll_address = false; + link->ipv4ll_route = false; + r = sd_ipv4ll_get_address(ll, &address); if (r == -ENOENT) return 0; @@ -124,23 +145,6 @@ static int ipv4ll_address_claimed(sd_ipv4ll *ll, Link *link) { if (r < 0) return r; - link->ipv4ll_address = false; - - r = route_new(&route); - if (r < 0) - return r; - - route->family = AF_INET; - route->scope = RT_SCOPE_LINK; - route->protocol = RTPROT_STATIC; - route->priority = IPV4LL_ROUTE_METRIC; - - r = route_configure(route, link, ipv4ll_route_handler); - if (r < 0) - return r; - - link->ipv4ll_route = false; - return 0; } @@ -176,6 +180,7 @@ static void ipv4ll_handler(sd_ipv4ll *ll, int event, void *userdata) { case SD_IPV4LL_EVENT_BIND: r = ipv4ll_address_claimed(ll, link); if (r < 0) { + log_link_error(link, "Failed to configure ipv4ll address: %m"); link_enter_failed(link); return; } diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 64ebcf41e39..ceb960a032f 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -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" @@ -32,6 +33,24 @@ #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; @@ -87,7 +106,7 @@ static bool link_ipv4ll_enabled(Link *link) { if (!link->network) return false; - if (streq_ptr(link->kind, "wireguard")) + if (STRPTR_IN_SET(link->kind, "vrf", "wireguard")) return false; return link->network->link_local & ADDRESS_FAMILY_IPV4; @@ -105,7 +124,7 @@ static bool link_ipv6ll_enabled(Link *link) { if (!link->network) return false; - if (streq_ptr(link->kind, "wireguard")) + if (STRPTR_IN_SET(link->kind, "vrf", "wireguard")) return false; return link->network->link_local & ADDRESS_FAMILY_IPV6; diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index dcb1ea68dd9..344873f303f 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -185,6 +185,10 @@ int link_node_enumerator(sd_bus *bus, const char *path, void *userdata, char *** 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, ...) \ diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index c8d369e2a0f..4b69e6afaeb 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -422,6 +422,28 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, vo (void) route_get(link, family, &dst, dst_prefixlen, tos, priority, table, &route); + if (DEBUG_LOGGING) { + _cleanup_free_ char *buf_dst = NULL, *buf_dst_prefixlen = NULL, + *buf_src = NULL, *buf_gw = NULL, *buf_prefsrc = NULL; + + if (!in_addr_is_null(family, &dst)) { + (void) in_addr_to_string(family, &dst, &buf_dst); + (void) asprintf(&buf_dst_prefixlen, "/%u", dst_prefixlen); + } + if (!in_addr_is_null(family, &src)) + (void) in_addr_to_string(family, &src, &buf_src); + if (!in_addr_is_null(family, &gw)) + (void) in_addr_to_string(family, &gw, &buf_gw); + if (!in_addr_is_null(family, &prefsrc)) + (void) in_addr_to_string(family, &prefsrc, &buf_prefsrc); + + log_link_debug(link, + "%s route: dst: %s%s, src: %s, gw: %s, prefsrc: %s", + type == RTM_DELROUTE ? "Removing" : route ? "Updating" : "Adding", + strna(buf_dst), strempty(buf_dst_prefixlen), + strna(buf_src), strna(buf_gw), strna(buf_prefsrc)); + } + switch (type) { case RTM_NEWROUTE: if (!route) { diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index b8384dfe537..c3053c007b8 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -103,7 +103,7 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) { return log_link_error_errno(link, r, "Could not allocate route: %m"); route->family = AF_INET6; - route->table = link->network->ipv6_accept_ra_route_table; + route->table = link_get_ipv6_accept_ra_route_table(link); route->priority = link->network->dhcp_route_metric; route->protocol = RTPROT_RA; route->pref = preference; @@ -238,7 +238,7 @@ static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) { return log_link_error_errno(link, r, "Could not allocate route: %m"); route->family = AF_INET6; - route->table = link->network->ipv6_accept_ra_route_table; + route->table = link_get_ipv6_accept_ra_route_table(link); route->priority = link->network->dhcp_route_metric; route->protocol = RTPROT_RA; route->flags = RTM_F_PREFIX; @@ -299,7 +299,7 @@ static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) { return log_link_error_errno(link, r, "Could not allocate route: %m"); route->family = AF_INET6; - route->table = link->network->ipv6_accept_ra_route_table; + route->table = link_get_ipv6_accept_ra_route_table(link); route->protocol = RTPROT_RA; route->pref = preference; route->gw.in6 = gateway; diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index bd79c5438d4..36b09f86819 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -141,7 +141,7 @@ DHCP.UserClass, config_parse_dhcp_user_class, DHCP.DUIDType, config_parse_duid_type, 0, offsetof(Network, duid) DHCP.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Network, duid) DHCP.RouteMetric, config_parse_unsigned, 0, offsetof(Network, dhcp_route_metric) -DHCP.RouteTable, config_parse_dhcp_route_table, 0, 0 +DHCP.RouteTable, config_parse_section_route_table, 0, 0 DHCP.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_use_timezone) DHCP.IAID, config_parse_iaid, 0, 0 DHCP.ListenPort, config_parse_uint16, 0, offsetof(Network, dhcp_client_port) @@ -151,7 +151,7 @@ IPv6AcceptRA.UseAutonomousPrefix, config_parse_bool, IPv6AcceptRA.UseOnLinkPrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_onlink_prefix) IPv6AcceptRA.UseDNS, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_dns) IPv6AcceptRA.UseDomains, config_parse_dhcp_use_domains, 0, offsetof(Network, ipv6_accept_ra_use_domains) -IPv6AcceptRA.RouteTable, config_parse_uint32, 0, offsetof(Network, ipv6_accept_ra_route_table) +IPv6AcceptRA.RouteTable, config_parse_section_route_table, 0, 0 DHCPServer.MaxLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_max_lease_time_usec) DHCPServer.DefaultLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_default_lease_time_usec) DHCPServer.EmitDNS, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_dns) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 2d233223786..45bf44cd363 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -209,6 +209,7 @@ int network_load_one(Manager *manager, const char *filename) { .ipv6_accept_ra_use_autonomous_prefix = true, .ipv6_accept_ra_use_onlink_prefix = true, .ipv6_accept_ra_route_table = RT_TABLE_MAIN, + .ipv6_accept_ra_route_table_set = false, }; r = config_parse_many(filename, network_dirs, dropin_dirname, @@ -1423,16 +1424,18 @@ int config_parse_dhcp_user_class( return 0; } -int config_parse_dhcp_route_table(const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { +int config_parse_section_route_table( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + Network *network = data; uint32_t rt; int r; @@ -1445,12 +1448,17 @@ int config_parse_dhcp_route_table(const char *unit, r = safe_atou32(rvalue, &rt); if (r < 0) { log_syntax(unit, LOG_ERR, filename, line, r, - "Unable to read RouteTable, ignoring assignment: %s", rvalue); + "Failed to parse RouteTable=%s, ignoring assignment: %m", rvalue); return 0; } - network->dhcp_route_table = rt; - network->dhcp_route_table_set = true; + if (streq_ptr(section, "DHCP")) { + network->dhcp_route_table = rt; + network->dhcp_route_table_set = true; + } else { /* section is IPv6AcceptRA */ + network->ipv6_accept_ra_route_table = rt; + network->ipv6_accept_ra_route_table_set = true; + } return 0; } diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 1b2e51337ef..e61c391c766 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -215,6 +215,7 @@ struct Network { bool primary_slave; DHCPUseDomains ipv6_accept_ra_use_domains; uint32_t ipv6_accept_ra_route_table; + bool ipv6_accept_ra_route_table_set; union in_addr_union ipv6_token; IPv6PrivacyExtensions ipv6_privacy_extensions; @@ -311,7 +312,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_ntp); CONFIG_PARSER_PROTOTYPE(config_parse_dnssec_negative_trust_anchors); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_use_domains); CONFIG_PARSER_PROTOTYPE(config_parse_lldp_mode); -CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_route_table); +CONFIG_PARSER_PROTOTYPE(config_parse_section_route_table); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_user_class); CONFIG_PARSER_PROTOTYPE(config_parse_ntp); CONFIG_PARSER_PROTOTYPE(config_parse_iaid); diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index 2d174fafde2..6f67f18e645 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -501,6 +501,24 @@ int route_configure( set_size(link->routes) >= routes_max()) return -E2BIG; + if (DEBUG_LOGGING) { + _cleanup_free_ char *dst = NULL, *dst_prefixlen = NULL, *src = NULL, *gw = NULL, *prefsrc = NULL; + + if (!in_addr_is_null(route->family, &route->dst)) { + (void) in_addr_to_string(route->family, &route->dst, &dst); + (void) asprintf(&dst_prefixlen, "/%u", route->dst_prefixlen); + } + if (!in_addr_is_null(route->family, &route->src)) + (void) in_addr_to_string(route->family, &route->src, &src); + if (!in_addr_is_null(route->family, &route->gw)) + (void) in_addr_to_string(route->family, &route->gw, &gw); + if (!in_addr_is_null(route->family, &route->prefsrc)) + (void) in_addr_to_string(route->family, &route->prefsrc, &prefsrc); + + log_link_debug(link, "Configuring route: dst: %s%s, src: %s, gw: %s, prefsrc: %s", + strna(dst), strempty(dst_prefixlen), strna(src), strna(gw), strna(prefsrc)); + } + r = sd_rtnl_message_new_route(link->manager->rtnl, &req, RTM_NEWROUTE, route->family, route->protocol); diff --git a/test/test-network/conf/25-vrf.network b/test/test-network/conf/25-vrf.network new file mode 100644 index 00000000000..42ce5b19252 --- /dev/null +++ b/test/test-network/conf/25-vrf.network @@ -0,0 +1,2 @@ +[Match] +Name=vrf99 diff --git a/test/test-network/conf/dhcp-client-ipv4-dhcp-settings.network b/test/test-network/conf/dhcp-client-ipv4-dhcp-settings.network index 5c4ca22ab7b..ebe3b7c0dd1 100644 --- a/test/test-network/conf/dhcp-client-ipv4-dhcp-settings.network +++ b/test/test-network/conf/dhcp-client-ipv4-dhcp-settings.network @@ -13,3 +13,4 @@ UseHostname=true Hostname=test-hostname ClientIdentifier=mac VendorClassIdentifier=SusantVendorTest +RouteTable=211 diff --git a/test/test-network/conf/dhcp-client-vrf.network b/test/test-network/conf/dhcp-client-vrf.network new file mode 100644 index 00000000000..bb1d2e09c5e --- /dev/null +++ b/test/test-network/conf/dhcp-client-vrf.network @@ -0,0 +1,8 @@ +[Match] +Name=veth99 + +[Network] +DHCP=yes +IPv6AcceptRA=yes +LinkLocalAddressing=yes +VRF=vrf99 diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 494606d53fb..f41e824a836 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -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() @@ -1212,10 +1210,13 @@ class NetworkdNetworkDHCPServerTests(unittest.TestCase, Utilities): 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', @@ -1226,6 +1227,7 @@ class NetworkdNetworkDHCPClientTests(unittest.TestCase, Utilities): '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', @@ -1290,16 +1292,27 @@ class NetworkdNetworkDHCPClientTests(unittest.TestCase, Utilities): 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.assertRegex(output, 'default.*dev veth99 proto dhcp') + 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 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')) @@ -1439,6 +1452,58 @@ class NetworkdNetworkDHCPClientTests(unittest.TestCase, Utilities): 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))