From 473a64e5692fedb794bfbffd10211e7cc87849cf Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 7 Oct 2020 15:20:37 +0200 Subject: [PATCH 01/19] meson: add missing files --- src/basic/meson.build | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/basic/meson.build b/src/basic/meson.build index 42d0754d6d..78fdb62465 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -91,9 +91,11 @@ basic_sources = files(''' limits-util.h linux/btrfs.h linux/btrfs_tree.h + linux/can/netlink.h linux/can/vxcan.h linux/fib_rules.h linux/fou.h + linux/hdlc/ioctl.h linux/if.h linux/if_addr.h linux/if_arp.h From b2f61e0da09829ecf33908b39601ca5d6c44ece7 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 7 Oct 2020 15:21:12 +0200 Subject: [PATCH 02/19] basic: import linux/ipv6_route.h --- src/basic/linux/ipv6_route.h | 64 ++++++++++++++++++++++++++++++++++++ src/basic/meson.build | 1 + 2 files changed, 65 insertions(+) create mode 100644 src/basic/linux/ipv6_route.h diff --git a/src/basic/linux/ipv6_route.h b/src/basic/linux/ipv6_route.h new file mode 100644 index 0000000000..593800a187 --- /dev/null +++ b/src/basic/linux/ipv6_route.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * Linux INET6 implementation + * + * Authors: + * Pedro Roque + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#ifndef _UAPI_LINUX_IPV6_ROUTE_H +#define _UAPI_LINUX_IPV6_ROUTE_H + +#include +#include /* For struct in6_addr. */ + +#define RTF_DEFAULT 0x00010000 /* default - learned via ND */ +#define RTF_ALLONLINK 0x00020000 /* (deprecated and will be removed) + fallback, no routers on link */ +#define RTF_ADDRCONF 0x00040000 /* addrconf route - RA */ +#define RTF_PREFIX_RT 0x00080000 /* A prefix only route - RA */ +#define RTF_ANYCAST 0x00100000 /* Anycast */ + +#define RTF_NONEXTHOP 0x00200000 /* route with no nexthop */ +#define RTF_EXPIRES 0x00400000 + +#define RTF_ROUTEINFO 0x00800000 /* route information - RA */ + +#define RTF_CACHE 0x01000000 /* read-only: can not be set by user */ +#define RTF_FLOW 0x02000000 /* flow significant route */ +#define RTF_POLICY 0x04000000 /* policy route */ + +#define RTF_PREF(pref) ((pref) << 27) +#define RTF_PREF_MASK 0x18000000 + +#define RTF_PCPU 0x40000000 /* read-only: can not be set by user */ +#define RTF_LOCAL 0x80000000 + + +struct in6_rtmsg { + struct in6_addr rtmsg_dst; + struct in6_addr rtmsg_src; + struct in6_addr rtmsg_gateway; + __u32 rtmsg_type; + __u16 rtmsg_dst_len; + __u16 rtmsg_src_len; + __u32 rtmsg_metric; + unsigned long rtmsg_info; + __u32 rtmsg_flags; + int rtmsg_ifindex; +}; + +#define RTMSG_NEWDEVICE 0x11 +#define RTMSG_DELDEVICE 0x12 +#define RTMSG_NEWROUTE 0x21 +#define RTMSG_DELROUTE 0x22 + +#define IP6_RT_PRIO_USER 1024 +#define IP6_RT_PRIO_ADDRCONF 256 + +#endif /* _UAPI_LINUX_IPV6_ROUTE_H */ diff --git a/src/basic/meson.build b/src/basic/meson.build index 78fdb62465..d144bc2f87 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -108,6 +108,7 @@ basic_sources = files(''' linux/if_tunnel.h linux/in.h linux/in6.h + linux/ipv6_route.h linux/l2tp.h linux/libc-compat.h linux/netdevice.h From 8973df5c42a292d36e3828c35104c9cd03711f4a Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 13 Aug 2020 18:55:06 +0900 Subject: [PATCH 03/19] network: set default priority for IPv6 routes See inet6_rtm_newroute() in kernel's net/ipv6/route.c. --- src/network/networkd-route.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index 659fecbf2e..1b725c1a2d 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1+ */ #include +#include #include "alloc-util.h" #include "netlink-util.h" @@ -2200,6 +2201,9 @@ static int route_section_verify(Route *route, Network *network) { route->scope = RT_SCOPE_LINK; } + if (route->family == AF_INET6 && route->priority == 0) + route->priority = IP6_RT_PRIO_USER; + if (ordered_hashmap_isempty(network->addresses_by_section) && in_addr_is_null(route->family, &route->gw) == 0 && route->gateway_onlink < 0) { From 0a2808a20822abf1fd43e628688a0bc134e4313b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 13 Aug 2020 19:01:23 +0900 Subject: [PATCH 04/19] test-network: drop meaningless Scope= settings in the config --- test/test-network/conf/25-route-static.network | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/test-network/conf/25-route-static.network b/test/test-network/conf/25-route-static.network index a4ba77a3e3..94583b9b01 100644 --- a/test/test-network/conf/25-route-static.network +++ b/test/test-network/conf/25-route-static.network @@ -10,11 +10,9 @@ IPv4LLRoute=yes [Route] Destination=2001:1234:5:8fff:ff:ff:ff:ff/128 -Scope=link [Route] Destination=2001:1234:5:9fff:ff:ff:ff:ff/128 -Scope=link [Route] Destination=::/0 From fd7701bf311dde85375f46d39b36a81215c062d4 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 14 Aug 2020 00:04:00 +0900 Subject: [PATCH 05/19] network: ignore Scope= for IPv6 routes as it will not be used --- src/network/networkd-route.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index 1b725c1a2d..870de18b8c 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -2201,6 +2201,11 @@ static int route_section_verify(Route *route, Network *network) { route->scope = RT_SCOPE_LINK; } + if (route->scope != RT_SCOPE_UNIVERSE && route->family == AF_INET6) { + log_warning("%s: Scope= is specified for IPv6 route. It will be ignored.", route->section->filename); + route->scope = RT_SCOPE_UNIVERSE; + } + if (route->family == AF_INET6 && route->priority == 0) route->priority = IP6_RT_PRIO_USER; From ad208fac7311ce6757add412fe3ac26cbf938049 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 7 Oct 2020 09:41:52 +0900 Subject: [PATCH 06/19] network: also manage routes without RTA_OIF attribute --- src/network/networkd-dhcp4.c | 4 +- src/network/networkd-dhcp6.c | 8 +- src/network/networkd-manager.c | 3 + src/network/networkd-manager.h | 4 + src/network/networkd-ndisc.c | 2 +- src/network/networkd-route.c | 163 ++++++++++++++++++++------------- src/network/networkd-route.h | 3 +- 7 files changed, 116 insertions(+), 71 deletions(-) diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index ed4d7c34e4..b31b68756d 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -38,7 +38,7 @@ static int dhcp4_release_old_lease(Link *link) { link_dirty(link); SET_FOREACH(route, link->dhcp_routes_old) { - k = route_remove(route, link, NULL); + k = route_remove(route, NULL, link, NULL); if (k < 0) r = k; } @@ -512,7 +512,7 @@ static int dhcp4_remove_all(Link *link) { assert(link); SET_FOREACH(route, link->dhcp_routes) { - k = route_remove(route, link, dhcp4_remove_route_handler); + k = route_remove(route, NULL, link, dhcp4_remove_route_handler); if (k < 0) r = k; else diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index ea331c95db..5217497f98 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -164,7 +164,7 @@ static int dhcp6_pd_remove_old(Link *link, bool force) { link_dirty(link); SET_FOREACH(route, link->dhcp6_pd_routes_old) { - k = route_remove(route, link, NULL); + k = route_remove(route, NULL, link, NULL); if (k < 0) r = k; @@ -208,7 +208,7 @@ int dhcp6_pd_remove(Link *link) { link_dirty(link); SET_FOREACH(route, link->dhcp6_pd_routes) { - k = route_remove(route, link, NULL); + k = route_remove(route, NULL, link, NULL); if (k < 0) r = k; @@ -704,7 +704,7 @@ static int dhcp6_remove_old(Link *link, bool force) { link_dirty(link); SET_FOREACH(route, link->dhcp6_routes_old) { - k = route_remove(route, link, NULL); + k = route_remove(route, NULL, link, NULL); if (k < 0) r = k; } @@ -740,7 +740,7 @@ static int dhcp6_remove(Link *link) { link_dirty(link); SET_FOREACH(route, link->dhcp6_routes) { - k = route_remove(route, link, NULL); + k = route_remove(route, NULL, link, NULL); if (k < 0) r = k; } diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index e8d270987d..2cd6830072 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -886,6 +886,9 @@ void manager_free(Manager *m) { m->rules_foreign = set_free(m->rules_foreign); set_free(m->rules_saved); + m->routes = set_free(m->routes); + m->routes_foreign = set_free(m->routes_foreign); + sd_netlink_unref(m->rtnl); sd_netlink_unref(m->genl); sd_resolve_unref(m->resolve); diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h index 142a6eb358..ac7de58c64 100644 --- a/src/network/networkd-manager.h +++ b/src/network/networkd-manager.h @@ -61,6 +61,10 @@ struct Manager { Set *rules_foreign; Set *rules_saved; + /* Manager stores routes without RTA_OIF attribute. */ + Set *routes; + Set *routes_foreign; + /* For link speed meter*/ bool use_speed_meter; sd_event_source *speed_meter_event_source; diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 2599ec2232..e2e049201a 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -163,7 +163,7 @@ static int ndisc_remove_old_one(Link *link, const struct in6_addr *router, bool SET_FOREACH(nr, link->ndisc_routes) if (nr->marked && IN6_ARE_ADDR_EQUAL(&nr->router, router)) { - k = route_remove(nr->route, link, NULL); + k = route_remove(nr->route, NULL, link, NULL); if (k < 0) r = k; } diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index 870de18b8c..c3de2c7c08 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -272,6 +272,11 @@ Route *route_free(Route *route) { free(set_remove(route->link->ndisc_routes, n)); } + if (route->manager) { + set_remove(route->manager->routes, route); + set_remove(route->manager->routes_foreign, route); + } + ordered_set_free_free(route->multipath_routes); sd_event_source_unref(route->expire); @@ -404,36 +409,51 @@ static bool route_equal(Route *r1, Route *r2) { return route_compare_func(r1, r2) == 0; } -static int route_get(Link *link, Route *in, Route **ret) { - +static int route_get(Manager *manager, Link *link, Route *in, Route **ret) { Route *existing; - assert(link); + assert(manager || link); assert(in); - existing = set_get(link->routes, in); - if (existing) { - if (ret) - *ret = existing; - return 1; - } + if (link) { + existing = set_get(link->routes, in); + if (existing) { + if (ret) + *ret = existing; + return 1; + } - existing = set_get(link->routes_foreign, in); - if (existing) { - if (ret) - *ret = existing; - return 0; + existing = set_get(link->routes_foreign, in); + if (existing) { + if (ret) + *ret = existing; + return 0; + } + } else { + existing = set_get(manager->routes, in); + if (existing) { + if (ret) + *ret = existing; + return 1; + } + + existing = set_get(manager->routes_foreign, in); + if (existing) { + if (ret) + *ret = existing; + return 0; + } } return -ENOENT; } -static int route_add_internal(Link *link, Set **routes, Route *in, Route **ret) { +static int route_add_internal(Manager *manager, Link *link, Set **routes, Route *in, Route **ret) { _cleanup_(route_freep) Route *route = NULL; int r; - assert(link); + assert(manager || link); assert(routes); assert(in); @@ -465,6 +485,7 @@ static int route_add_internal(Link *link, Set **routes, Route *in, Route **ret) return -EEXIST; route->link = link; + route->manager = manager; if (ret) *ret = route; @@ -474,28 +495,39 @@ static int route_add_internal(Link *link, Set **routes, Route *in, Route **ret) return 0; } -static int route_add_foreign(Link *link, Route *in, Route **ret) { - return route_add_internal(link, &link->routes_foreign, in, ret); +static int route_add_foreign(Manager *manager, Link *link, Route *in, Route **ret) { + assert(manager || link); + return route_add_internal(manager, link, link ? &link->routes_foreign : &manager->routes_foreign, in, ret); } -static int route_add(Link *link, Route *in, Route **ret) { - +static int route_add(Manager *manager, Link *link, Route *in, Route **ret) { Route *route; int r; - r = route_get(link, in, &route); + assert(manager || link); + assert(in); + + r = route_get(manager, link, in, &route); if (r == -ENOENT) { /* Route does not exist, create a new one */ - r = route_add_internal(link, &link->routes, in, &route); + r = route_add_internal(manager, link, link ? &link->routes : &manager->routes, in, &route); if (r < 0) return r; } else if (r == 0) { /* Take over a foreign route */ - r = set_ensure_put(&link->routes, &route_hash_ops, route); - if (r < 0) - return r; + if (link) { + r = set_ensure_put(&link->routes, &route_hash_ops, route); + if (r < 0) + return r; - set_remove(link->routes_foreign, route); + set_remove(link->routes_foreign, route); + } else { + r = set_ensure_put(&manager->routes, &route_hash_ops, route); + if (r < 0) + return r; + + set_remove(manager->routes_foreign, route); + } } else if (r == 1) { /* Route exists, do nothing */ ; @@ -512,10 +544,9 @@ static int route_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *l int r; assert(m); - assert(link); - assert(link->ifname); - if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + /* Note that link may be NULL. */ + if (link && IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) return 1; r = sd_netlink_message_get_errno(m); @@ -525,19 +556,22 @@ static int route_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *l return 1; } -int route_remove(Route *route, Link *link, - link_netlink_message_handler_t callback) { +int route_remove( + Route *route, + Manager *manager, + Link *link, + link_netlink_message_handler_t callback) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; int r; - assert(link); - assert(link->manager); - assert(link->manager->rtnl); - assert(link->ifindex > 0); + assert(link || manager); assert(IN_SET(route->family, AF_INET, AF_INET6)); - r = sd_rtnl_message_new_route(link->manager->rtnl, &req, + if (!manager) + manager = link->manager; + + r = sd_rtnl_message_new_route(manager->rtnl, &req, RTM_DELROUTE, route->family, route->protocol); if (r < 0) @@ -618,7 +652,8 @@ int route_remove(Route *route, Link *link, if (r < 0) return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); - link_ref(link); + if (link) + link_ref(link); return 0; } @@ -668,9 +703,9 @@ int link_drop_foreign_routes(Link *link) { continue; if (link_is_static_route_configured(link, route)) - k = route_add(link, route, NULL); + k = route_add(NULL, link, route, NULL); else - k = route_remove(route, link, NULL); + k = route_remove(route, NULL, link, NULL); if (k < 0 && r >= 0) r = k; } @@ -689,7 +724,7 @@ int link_drop_routes(Link *link) { if (route->protocol == RTPROT_KERNEL) continue; - k = route_remove(route, link, NULL); + k = route_remove(route, NULL, link, NULL); if (k < 0 && r >= 0) r = k; } @@ -703,7 +738,7 @@ static int route_expire_handler(sd_event_source *s, uint64_t usec, void *userdat assert(route); - r = route_remove(route, route->link, NULL); + r = route_remove(route, route->manager, route->link, NULL); if (r < 0) log_link_warning_errno(route->link, r, "Could not remove route: %m"); else @@ -810,7 +845,7 @@ int route_configure( assert(IN_SET(route->family, AF_INET, AF_INET6)); assert(callback); - if (route_get(link, route, NULL) <= 0 && + if (route_get(link->manager, link, route, NULL) <= 0 && set_size(link->routes) >= routes_max()) return log_link_error_errno(link, SYNTHETIC_ERRNO(E2BIG), "Too many routes are configured, refusing: %m"); @@ -988,7 +1023,10 @@ int route_configure( link_ref(link); - r = route_add(link, route, &route); + if (IN_SET(route->type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE, RTN_THROW) || !ordered_set_isempty(route->multipath_routes)) + r = route_add(link->manager, NULL, route, &route); + else + r = route_add(NULL, link, route, &route); if (r < 0) return log_link_error_errno(link, r, "Could not add route: %m"); @@ -1123,24 +1161,23 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Ma } r = sd_netlink_message_read_u32(message, RTA_OIF, &ifindex); - if (r == -ENODATA) { - log_debug("rtnl: received route message without ifindex, ignoring"); - return 0; - } else if (r < 0) { + if (r < 0 && r != -ENODATA) { log_warning_errno(r, "rtnl: could not get ifindex from route message, ignoring: %m"); return 0; - } else if (ifindex <= 0) { - log_warning("rtnl: received route message with invalid ifindex %d, ignoring.", ifindex); - return 0; - } + } else if (r >= 0) { + if (ifindex <= 0) { + log_warning("rtnl: received route message with invalid ifindex %d, ignoring.", ifindex); + return 0; + } - r = link_get(m, ifindex, &link); - if (r < 0 || !link) { - /* when enumerating we might be out of sync, but we will - * get the route again, so just ignore it */ - if (!m->enumerating) - log_warning("rtnl: received route message for link (%d) we do not know about, ignoring", ifindex); - return 0; + r = link_get(m, ifindex, &link); + if (r < 0 || !link) { + /* when enumerating we might be out of sync, but we will + * get the route again, so just ignore it */ + if (!m->enumerating) + log_warning("rtnl: received route message for link (%d) we do not know about, ignoring", ifindex); + return 0; + } } r = route_new(&tmp); @@ -1290,7 +1327,7 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Ma } } - (void) route_get(link, tmp, &route); + (void) route_get(m, link, tmp, &route); if (DEBUG_LOGGING) { _cleanup_free_ char *buf_dst = NULL, *buf_dst_prefixlen = NULL, @@ -1311,7 +1348,7 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Ma log_link_debug(link, "%s route: dst: %s%s, src: %s, gw: %s, prefsrc: %s, scope: %s, table: %s, proto: %s, type: %s", - (!route && !link->manager->manage_foreign_routes) ? "Ignoring received foreign" : + (!route && !m->manage_foreign_routes) ? "Ignoring received foreign" : type == RTM_DELROUTE ? "Forgetting" : route ? "Received remembered" : "Remembering", strna(buf_dst), strempty(buf_dst_prefixlen), @@ -1324,9 +1361,9 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Ma switch (type) { case RTM_NEWROUTE: - if (!route && link->manager->manage_foreign_routes) { + if (!route && m->manage_foreign_routes) { /* A route appeared that we did not request */ - r = route_add_foreign(link, tmp, &route); + r = route_add_foreign(m, link, tmp, &route); if (r < 0) { log_link_warning_errno(link, r, "Failed to remember foreign route, ignoring: %m"); return 0; @@ -1420,7 +1457,7 @@ int link_deserialize_routes(Link *link, const char *routes) { continue; } - r = route_add(link, tmp, &route); + r = route_add(NULL, link, tmp, &route); if (r < 0) return log_link_debug_errno(link, r, "Failed to add route: %m"); diff --git a/src/network/networkd-route.h b/src/network/networkd-route.h index 3347c7c57b..5a82c3ac58 100644 --- a/src/network/networkd-route.h +++ b/src/network/networkd-route.h @@ -31,6 +31,7 @@ typedef struct Route { NetworkConfigSection *section; Link *link; + Manager *manager; int family; int quickack; @@ -74,7 +75,7 @@ Route *route_free(Route *route); DEFINE_NETWORK_SECTION_FUNCTIONS(Route, route_free); int route_configure(Route *route, Link *link, link_netlink_message_handler_t callback, Route **ret); -int route_remove(Route *route, Link *link, link_netlink_message_handler_t callback); +int route_remove(Route *route, Manager *manager, Link *link, link_netlink_message_handler_t callback); int link_set_routes(Link *link); int link_drop_routes(Link *link); From 2c59a8a6248c4cdf4d446d707b99cf67054afb71 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 14 Aug 2020 01:50:45 +0900 Subject: [PATCH 07/19] sd-netlink: fix type of RTA_VIA --- src/libsystemd/sd-netlink/netlink-types.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c index 9c9471eadc..5f5a1558a1 100644 --- a/src/libsystemd/sd-netlink/netlink-types.c +++ b/src/libsystemd/sd-netlink/netlink-types.c @@ -716,7 +716,7 @@ static const NLType rtnl_route_types[] = { [RTA_TABLE] = { .type = NETLINK_TYPE_U32 }, [RTA_MARK] = { .type = NETLINK_TYPE_U32 }, [RTA_MFC_STATS] = { .type = NETLINK_TYPE_U64 }, - [RTA_VIA] = { .type = NETLINK_TYPE_U32 }, + [RTA_VIA] = { /* See struct rtvia */ }, [RTA_NEWDST] = { .type = NETLINK_TYPE_U32 }, [RTA_PREF] = { .type = NETLINK_TYPE_U8 }, [RTA_EXPIRES] = { .type = NETLINK_TYPE_U32 }, From 6dd53981372e7aac9df7c5173d7999507b9bdc2f Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 7 Oct 2020 10:04:13 +0900 Subject: [PATCH 08/19] network: support IPv4 route with IPv6 gateway --- src/network/networkd-dhcp4.c | 3 ++ src/network/networkd-ndisc.c | 3 ++ src/network/networkd-route.c | 98 ++++++++++++++++++++++++++---------- src/network/networkd-route.h | 8 +-- 4 files changed, 83 insertions(+), 29 deletions(-) diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index b31b68756d..81a9594512 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -312,6 +312,7 @@ static int link_set_dhcp_routes(Link *link) { route->family = AF_INET; route->protocol = RTPROT_DHCP; + route->gw_family = AF_INET; assert_se(sd_dhcp_route_get_gateway(static_routes[i], &route->gw.in) >= 0); assert_se(sd_dhcp_route_get_destination(static_routes[i], &route->dst.in) >= 0); assert_se(sd_dhcp_route_get_destination_prefix_length(static_routes[i], &route->dst_prefixlen) >= 0); @@ -374,6 +375,7 @@ static int link_set_dhcp_routes(Link *link) { return log_link_error_errno(link, r, "Could not allocate route: %m"); route->family = AF_INET; + route->gw_family = AF_INET; route->gw.in = router[0]; route->prefsrc.in = address; route->protocol = RTPROT_DHCP; @@ -392,6 +394,7 @@ static int link_set_dhcp_routes(Link *link) { if (rt->family != AF_INET) continue; + rt->gw_family = AF_INET; rt->gw.in = router[0]; r = dhcp_route_configure(rt, link); diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index e2e049201a..d0e426f231 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -513,6 +513,7 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) { route->priority = link->network->dhcp6_route_metric; route->protocol = RTPROT_RA; route->pref = preference; + route->gw_family = AF_INET6; route->gw = gateway; route->lifetime = time_now + lifetime * USEC_PER_SEC; route->mtu = mtu; @@ -530,6 +531,7 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) { continue; route_gw->gw = gateway; + route_gw->gw_family = AF_INET6; r = ndisc_route_configure(route_gw, link, rt); if (r < 0) @@ -839,6 +841,7 @@ static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) { route->protocol = RTPROT_RA; route->pref = preference; route->gw.in6 = gateway; + route->gw_family = AF_INET6; route->dst_prefixlen = prefixlen; route->lifetime = time_now + lifetime * USEC_PER_SEC; diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index c3de2c7c08..e75d93c561 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -298,7 +298,10 @@ void route_hash_func(const Route *route, struct siphash *state) { siphash24_compress(&route->src_prefixlen, sizeof(route->src_prefixlen), state); siphash24_compress(&route->src, FAMILY_ADDRESS_SIZE(route->family), state); - siphash24_compress(&route->gw, FAMILY_ADDRESS_SIZE(route->family), state); + siphash24_compress(&route->gw_family, sizeof(route->gw_family), state); + if (IN_SET(route->gw_family, AF_INET, AF_INET6)) + siphash24_compress(&route->gw, FAMILY_ADDRESS_SIZE(route->gw_family), state); + siphash24_compress(&route->prefsrc, FAMILY_ADDRESS_SIZE(route->family), state); @@ -345,10 +348,16 @@ int route_compare_func(const Route *a, const Route *b) { if (r != 0) return r; - r = memcmp(&a->gw, &b->gw, FAMILY_ADDRESS_SIZE(a->family)); + r = CMP(a->gw_family, b->gw_family); if (r != 0) return r; + if (IN_SET(a->gw_family, AF_INET, AF_INET6)) { + r = memcmp(&a->gw, &b->gw, FAMILY_ADDRESS_SIZE(a->family)); + if (r != 0) + return r; + } + r = memcmp(&a->prefsrc, &b->prefsrc, FAMILY_ADDRESS_SIZE(a->family)); if (r != 0) return r; @@ -466,6 +475,7 @@ static int route_add_internal(Manager *manager, Link *link, Set **routes, Route route->src_prefixlen = in->src_prefixlen; route->dst = in->dst; route->dst_prefixlen = in->dst_prefixlen; + route->gw_family = in->gw_family; route->gw = in->gw; route->prefsrc = in->prefsrc; route->scope = in->scope; @@ -587,8 +597,8 @@ int route_remove( } 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->gw_family, &route->gw)) + (void) in_addr_to_string(route->gw_family, &route->gw, &gw); if (!in_addr_is_null(route->family, &route->prefsrc)) (void) in_addr_to_string(route->family, &route->prefsrc, &prefsrc); @@ -600,10 +610,21 @@ int route_remove( strna(route_type_to_string(route->type))); } - if (in_addr_is_null(route->family, &route->gw) == 0) { - r = netlink_message_append_in_addr_union(req, RTA_GATEWAY, route->family, &route->gw); - if (r < 0) - return log_link_error_errno(link, r, "Could not append RTA_GATEWAY attribute: %m"); + if (in_addr_is_null(route->gw_family, &route->gw) == 0) { + if (route->gw_family == route->family) { + r = netlink_message_append_in_addr_union(req, RTA_GATEWAY, route->gw_family, &route->gw); + if (r < 0) + return log_link_error_errno(link, r, "Could not append RTA_GATEWAY attribute: %m"); + } else { + RouteVia rtvia = { + .family = route->gw_family, + .address = route->gw, + }; + + r = sd_netlink_message_append_data(req, RTA_VIA, &rtvia, sizeof(rtvia)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append RTA_VIA attribute: %m"); + } } if (route->dst_prefixlen) { @@ -860,8 +881,8 @@ int route_configure( } 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->gw_family, &route->gw)) + (void) in_addr_to_string(route->gw_family, &route->gw, &gw); if (!in_addr_is_null(route->family, &route->prefsrc)) (void) in_addr_to_string(route->family, &route->prefsrc, &prefsrc); @@ -879,14 +900,21 @@ int route_configure( if (r < 0) return log_link_error_errno(link, r, "Could not create RTM_NEWROUTE message: %m"); - if (in_addr_is_null(route->family, &route->gw) == 0) { - r = netlink_message_append_in_addr_union(req, RTA_GATEWAY, route->family, &route->gw); - if (r < 0) - return log_link_error_errno(link, r, "Could not append RTA_GATEWAY attribute: %m"); + if (in_addr_is_null(route->gw_family, &route->gw) == 0) { + if (route->gw_family == route->family) { + r = netlink_message_append_in_addr_union(req, RTA_GATEWAY, route->gw_family, &route->gw); + if (r < 0) + return log_link_error_errno(link, r, "Could not append RTA_GATEWAY attribute: %m"); + } else { + RouteVia rtvia = { + .family = route->gw_family, + .address = route->gw, + }; - r = sd_rtnl_message_route_set_family(req, route->family); - if (r < 0) - return log_link_error_errno(link, r, "Could not set route family: %m"); + r = sd_netlink_message_append_data(req, RTA_VIA, &rtvia, sizeof(rtvia)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append RTA_VIA attribute: %m"); + } } if (route->dst_prefixlen > 0) { @@ -1109,7 +1137,7 @@ int link_set_routes(Link *link) { if (rt->gateway_from_dhcp) continue; - if ((in_addr_is_null(rt->family, &rt->gw) && ordered_set_isempty(rt->multipath_routes)) != (phase == PHASE_NON_GATEWAY)) + if ((in_addr_is_null(rt->gw_family, &rt->gw) && ordered_set_isempty(rt->multipath_routes)) != (phase == PHASE_NON_GATEWAY)) continue; r = route_configure(rt, link, route_handler, NULL); @@ -1137,6 +1165,7 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Ma uint32_t ifindex; uint16_t type; unsigned char table; + RouteVia via; int r; assert(rtnl); @@ -1211,6 +1240,16 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Ma if (r < 0 && r != -ENODATA) { log_link_warning_errno(link, r, "rtnl: received route message without valid gateway, ignoring: %m"); return 0; + } else if (r >= 0) + tmp->gw_family = AF_INET; + + r = sd_netlink_message_read(message, RTA_VIA, sizeof(via), &via); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: received route message without valid gateway, ignoring: %m"); + return 0; + } else if (r >= 0) { + tmp->gw_family = via.family; + tmp->gw = via.address; } r = sd_netlink_message_read_in_addr(message, RTA_SRC, &tmp->src.in); @@ -1238,7 +1277,8 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Ma if (r < 0 && r != -ENODATA) { log_link_warning_errno(link, r, "rtnl: received route message without valid gateway, ignoring: %m"); return 0; - } + } else if (r >= 0) + tmp->gw_family = AF_INET6; r = sd_netlink_message_read_in6_addr(message, RTA_SRC, &tmp->src.in6); if (r < 0 && r != -ENODATA) { @@ -1341,8 +1381,8 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Ma } if (!in_addr_is_null(tmp->family, &tmp->src)) (void) in_addr_to_string(tmp->family, &tmp->src, &buf_src); - if (!in_addr_is_null(tmp->family, &tmp->gw)) - (void) in_addr_to_string(tmp->family, &tmp->gw, &buf_gw); + if (!in_addr_is_null(tmp->gw_family, &tmp->gw)) + (void) in_addr_to_string(tmp->gw_family, &tmp->gw, &buf_gw); if (!in_addr_is_null(tmp->family, &tmp->prefsrc)) (void) in_addr_to_string(tmp->family, &tmp->prefsrc, &buf_prefsrc); @@ -1582,10 +1622,7 @@ int config_parse_gateway( } } - if (n->family == AF_UNSPEC) - r = in_addr_from_string_auto(rvalue, &n->family, &n->gw); - else - r = in_addr_from_string(n->family, rvalue, &n->gw); + r = in_addr_from_string_auto(rvalue, &n->gw_family, &n->gw); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue); @@ -2213,6 +2250,9 @@ static int route_section_verify(Route *route, Network *network) { if (section_is_invalid(route->section)) return -EINVAL; + if (route->family == AF_UNSPEC) + route->family = route->gw_family; + if (route->family == AF_UNSPEC) { assert(route->section); @@ -2223,6 +2263,12 @@ static int route_section_verify(Route *route, Network *network) { route->section->filename, route->section->line); } + if (route->family == AF_INET6 && route->gw_family == AF_INET) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: IPv4 gateway is configured for IPv6 route. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + if (!route->table_set && network->vrf) { route->table = VRF(network->vrf)->table; route->table_set = true; @@ -2247,7 +2293,7 @@ static int route_section_verify(Route *route, Network *network) { route->priority = IP6_RT_PRIO_USER; if (ordered_hashmap_isempty(network->addresses_by_section) && - in_addr_is_null(route->family, &route->gw) == 0 && + in_addr_is_null(route->gw_family, &route->gw) == 0 && route->gateway_onlink < 0) { log_warning("%s: Gateway= without static address configured. " "Enabling GatewayOnLink= option.", diff --git a/src/network/networkd-route.h b/src/network/networkd-route.h index 5a82c3ac58..9baee22ef2 100644 --- a/src/network/networkd-route.h +++ b/src/network/networkd-route.h @@ -15,13 +15,14 @@ typedef struct Manager Manager; typedef struct Network Network; -typedef struct MultipathRouteVia { +/* See struct rtvia in rtnetlink.h */ +typedef struct RouteVia { uint16_t family; union in_addr_union address; -} _packed_ MultipathRouteVia; +} _packed_ RouteVia; typedef struct MultipathRoute { - MultipathRouteVia gateway; + RouteVia gateway; int ifindex; uint32_t weight; } MultipathRoute; @@ -34,6 +35,7 @@ typedef struct Route { Manager *manager; int family; + int gw_family; int quickack; int fast_open_no_cookie; int ttl_propagate; From 297f9d86fecdb8a3074ce6c317ea568482ebc9e8 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 14 Aug 2020 01:52:19 +0900 Subject: [PATCH 09/19] test-network: add a test case for IPv4 route with IPv6 gateway --- .../test-network/conf/25-route-static.network | 1 - .../conf/25-route-via-ipv6.network | 14 ++++++++ test/test-network/systemd-networkd-tests.py | 36 +++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 test/test-network/conf/25-route-via-ipv6.network diff --git a/test/test-network/conf/25-route-static.network b/test/test-network/conf/25-route-static.network index 94583b9b01..bb485eb6b0 100644 --- a/test/test-network/conf/25-route-static.network +++ b/test/test-network/conf/25-route-static.network @@ -15,7 +15,6 @@ Destination=2001:1234:5:8fff:ff:ff:ff:ff/128 Destination=2001:1234:5:9fff:ff:ff:ff:ff/128 [Route] -Destination=::/0 Gateway=2001:1234:5:8fff:ff:ff:ff:ff [Route] diff --git a/test/test-network/conf/25-route-via-ipv6.network b/test/test-network/conf/25-route-via-ipv6.network new file mode 100644 index 0000000000..a0ba93e631 --- /dev/null +++ b/test/test-network/conf/25-route-via-ipv6.network @@ -0,0 +1,14 @@ +[Match] +Name=dummy98 + +[Network] +IPv6AcceptRA=no +Address=2001:1234:5:8f63::1/128 +Address=149.10.124.58/28 + +[Route] +Destination=2001:1234:5:8fff:ff:ff:ff:ff/128 + +[Route] +Destination=149.10.124.66 +Gateway=2001:1234:5:8fff:ff:ff:ff:ff diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 8058122e46..3411c30cc8 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -146,6 +146,20 @@ def expectedFailureIfNexthopIsNotAvailable(): return f +def expectedFailureIfRTA_VIAIsNotSupported(): + def f(func): + call('ip link add dummy98 type dummy', stderr=subprocess.DEVNULL) + call('ip link set up dev dummy98', stderr=subprocess.DEVNULL) + call('ip route add 2001:1234:5:8fff:ff:ff:ff:fe/128 dev dummy98', stderr=subprocess.DEVNULL) + rc = call('ip route add 10.10.10.10 via inet6 2001:1234:5:8fff:ff:ff:ff:fe dev dummy98', stderr=subprocess.DEVNULL) + call('ip link del dummy98', stderr=subprocess.DEVNULL) + if rc == 0: + return func + else: + return unittest.expectedFailure(func) + + return f + def expectedFailureIfAlternativeNameIsNotAvailable(): def f(func): call('ip link add dummy98 type dummy', stderr=subprocess.DEVNULL) @@ -1760,6 +1774,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): '25-prefix-route-without-vrf.network', '25-route-ipv6-src.network', '25-route-static.network', + '25-route-via-ipv6.network', '25-route-vrf.network', '25-gateway-static.network', '25-gateway-next-static.network', @@ -2198,6 +2213,27 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): self.assertRegex(output, 'via 2001:1234:5:8fff:ff:ff:ff:ff dev dummy98') self.assertRegex(output, 'via 2001:1234:5:9fff:ff:ff:ff:ff dev dummy98') + @expectedFailureIfRTA_VIAIsNotSupported() + def test_route_via_ipv6(self): + copy_unit_to_networkd_unit_path('25-route-via-ipv6.network', '12-dummy.netdev') + start_networkd() + self.wait_online(['dummy98:routable']) + + output = check_output(*networkctl_cmd, '-n', '0', 'status', 'dummy98', env=env) + print(output) + + print('### ip -6 route show dev dummy98') + output = check_output('ip -6 route show dev dummy98') + print(output) + self.assertRegex(output, '2001:1234:5:8fff:ff:ff:ff:ff proto static') + self.assertRegex(output, '2001:1234:5:8f63::1 proto kernel') + + print('### ip -4 route show dev dummy98') + output = check_output('ip -4 route show dev dummy98') + print(output) + self.assertRegex(output, '149.10.124.48/28 proto kernel scope link src 149.10.124.58') + self.assertRegex(output, '149.10.124.66 via inet6 2001:1234:5:8fff:ff:ff:ff:ff proto static') + @expectedFailureIfModuleIsNotAvailable('vrf') def test_route_vrf(self): copy_unit_to_networkd_unit_path('25-route-vrf.network', '12-dummy.netdev', From 423c249c2e01b613e4cc6bdcdf9648ac89f01ace Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 7 Oct 2020 11:01:02 +0900 Subject: [PATCH 10/19] network: constify arguments --- src/network/networkd-route.c | 49 +++++++++++++++++++----------------- src/network/networkd-route.h | 10 ++++---- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index e75d93c561..5df5f8fb6c 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -22,11 +22,11 @@ #define ROUTES_DEFAULT_MAX_PER_FAMILY 4096U -static uint32_t link_get_vrf_table(Link *link) { +static uint32_t link_get_vrf_table(const Link *link) { return link->network->vrf ? VRF(link->network->vrf)->table : RT_TABLE_MAIN; } -uint32_t link_get_dhcp_route_table(Link *link) { +uint32_t link_get_dhcp_route_table(const 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) @@ -34,7 +34,7 @@ uint32_t link_get_dhcp_route_table(Link *link) { return link_get_vrf_table(link); } -uint32_t link_get_ipv6_accept_ra_route_table(Link *link) { +uint32_t link_get_ipv6_accept_ra_route_table(const 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); @@ -408,7 +408,7 @@ DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( route_compare_func, route_free); -static bool route_equal(Route *r1, Route *r2) { +static bool route_equal(const Route *r1, const Route *r2) { if (r1 == r2) return true; @@ -418,7 +418,7 @@ static bool route_equal(Route *r1, Route *r2) { return route_compare_func(r1, r2) == 0; } -static int route_get(Manager *manager, Link *link, Route *in, Route **ret) { +static int route_get(const Manager *manager, const Link *link, const Route *in, Route **ret) { Route *existing; assert(manager || link); @@ -457,7 +457,7 @@ static int route_get(Manager *manager, Link *link, Route *in, Route **ret) { return -ENOENT; } -static int route_add_internal(Manager *manager, Link *link, Set **routes, Route *in, Route **ret) { +static int route_add_internal(Manager *manager, Link *link, Set **routes, const Route *in, Route **ret) { _cleanup_(route_freep) Route *route = NULL; int r; @@ -505,12 +505,12 @@ static int route_add_internal(Manager *manager, Link *link, Set **routes, Route return 0; } -static int route_add_foreign(Manager *manager, Link *link, Route *in, Route **ret) { +static int route_add_foreign(Manager *manager, Link *link, const Route *in, Route **ret) { assert(manager || link); return route_add_internal(manager, link, link ? &link->routes_foreign : &manager->routes_foreign, in, ret); } -static int route_add(Manager *manager, Link *link, Route *in, Route **ret) { +static int route_add(Manager *manager, Link *link, const Route *in, Route **ret) { Route *route; int r; @@ -567,7 +567,7 @@ static int route_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *l } int route_remove( - Route *route, + const Route *route, Manager *manager, Link *link, link_netlink_message_handler_t callback) { @@ -679,7 +679,7 @@ int route_remove( return 0; } -static bool link_is_static_route_configured(Link *link, Route *route) { +static bool link_is_static_route_configured(const Link *link, const Route *route) { Route *net_route; assert(link); @@ -768,7 +768,7 @@ static int route_expire_handler(sd_event_source *s, uint64_t usec, void *userdat return 1; } -static int append_nexthop_one(Route *route, MultipathRoute *m, struct rtattr **rta, size_t offset) { +static int append_nexthop_one(const Route *route, const MultipathRoute *m, struct rtattr **rta, size_t offset) { struct rtnexthop *rtnh; struct rtattr *new_rta; int r; @@ -813,7 +813,7 @@ clear: return r; } -static int append_nexthops(Route *route, sd_netlink_message *req) { +static int append_nexthops(const Route *route, sd_netlink_message *req) { _cleanup_free_ struct rtattr *rta = NULL; struct rtnexthop *rtnh; MultipathRoute *m; @@ -850,13 +850,15 @@ static int append_nexthops(Route *route, sd_netlink_message *req) { } int route_configure( - Route *route, + const Route *route, Link *link, link_netlink_message_handler_t callback, Route **ret) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; _cleanup_(sd_event_source_unrefp) sd_event_source *expire = NULL; + unsigned flags; + Route *nr; int r; assert(link); @@ -947,10 +949,11 @@ int route_configure( if (r < 0) return log_link_error_errno(link, r, "Could not set scope: %m"); + flags = route->flags; if (route->gateway_onlink >= 0) - SET_FLAG(route->flags, RTNH_F_ONLINK, route->gateway_onlink); + SET_FLAG(flags, RTNH_F_ONLINK, route->gateway_onlink); - r = sd_rtnl_message_route_set_flags(req, route->flags); + r = sd_rtnl_message_route_set_flags(req, flags); if (r < 0) return log_link_error_errno(link, r, "Could not set flags: %m"); @@ -1052,25 +1055,25 @@ int route_configure( link_ref(link); if (IN_SET(route->type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE, RTN_THROW) || !ordered_set_isempty(route->multipath_routes)) - r = route_add(link->manager, NULL, route, &route); + r = route_add(link->manager, NULL, route, &nr); else - r = route_add(NULL, link, route, &route); + r = route_add(NULL, link, route, &nr); if (r < 0) return log_link_error_errno(link, r, "Could not add route: %m"); /* TODO: drop expiration handling once it can be pushed into the kernel */ - if (route->lifetime != USEC_INFINITY && !kernel_route_expiration_supported()) { + if (nr->lifetime != USEC_INFINITY && !kernel_route_expiration_supported()) { r = sd_event_add_time(link->manager->event, &expire, clock_boottime_or_monotonic(), - route->lifetime, 0, route_expire_handler, route); + nr->lifetime, 0, route_expire_handler, nr); if (r < 0) return log_link_error_errno(link, r, "Could not arm expiration timer: %m"); } - sd_event_source_unref(route->expire); - route->expire = TAKE_PTR(expire); + sd_event_source_unref(nr->expire); + nr->expire = TAKE_PTR(expire); if (ret) - *ret = route; + *ret = nr; return 1; } @@ -1423,7 +1426,7 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Ma return 1; } -int link_serialize_routes(Link *link, FILE *f) { +int link_serialize_routes(const Link *link, FILE *f) { bool space = false; Route *route; diff --git a/src/network/networkd-route.h b/src/network/networkd-route.h index 9baee22ef2..68d2d63574 100644 --- a/src/network/networkd-route.h +++ b/src/network/networkd-route.h @@ -76,17 +76,17 @@ int route_new(Route **ret); Route *route_free(Route *route); DEFINE_NETWORK_SECTION_FUNCTIONS(Route, route_free); -int route_configure(Route *route, Link *link, link_netlink_message_handler_t callback, Route **ret); -int route_remove(Route *route, Manager *manager, Link *link, link_netlink_message_handler_t callback); +int route_configure(const Route *route, Link *link, link_netlink_message_handler_t callback, Route **ret); +int route_remove(const Route *route, Manager *manager, Link *link, link_netlink_message_handler_t callback); int link_set_routes(Link *link); int link_drop_routes(Link *link); int link_drop_foreign_routes(Link *link); -int link_serialize_routes(Link *link, FILE *f); +int link_serialize_routes(const Link *link, FILE *f); int link_deserialize_routes(Link *link, const char *routes); -uint32_t link_get_dhcp_route_table(Link *link); -uint32_t link_get_ipv6_accept_ra_route_table(Link *link); +uint32_t link_get_dhcp_route_table(const Link *link); +uint32_t link_get_ipv6_accept_ra_route_table(const Link *link); int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Manager *m); From d6ad41e27d37518ce9068a74dcd9380de90ee4a9 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 7 Oct 2020 11:06:27 +0900 Subject: [PATCH 11/19] network: free Route object when route_remove() fails When route_remove() succeeds, the Route object will be freed later by manager_rtnl_process_route(). --- src/network/networkd-route.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index 5df5f8fb6c..feb62ee783 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -760,10 +760,10 @@ static int route_expire_handler(sd_event_source *s, uint64_t usec, void *userdat assert(route); r = route_remove(route, route->manager, route->link, NULL); - if (r < 0) + if (r < 0) { log_link_warning_errno(route->link, r, "Could not remove route: %m"); - else route_free(route); + } return 1; } From 2fe1d557e51181f2124fd08b463edb8d4fe8daaf Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 7 Oct 2020 13:00:45 +0900 Subject: [PATCH 12/19] sd-netlink: introduce rtattr_read_nexthop() --- src/libsystemd/sd-netlink/netlink-util.c | 77 ++++++++++++++++++++++++ src/libsystemd/sd-netlink/netlink-util.h | 15 +++++ src/network/networkd-route.h | 12 ---- 3 files changed, 92 insertions(+), 12 deletions(-) diff --git a/src/libsystemd/sd-netlink/netlink-util.c b/src/libsystemd/sd-netlink/netlink-util.c index f045e794b6..f893400580 100644 --- a/src/libsystemd/sd-netlink/netlink-util.c +++ b/src/libsystemd/sd-netlink/netlink-util.c @@ -368,3 +368,80 @@ int rtattr_append_attribute(struct rtattr **rta, unsigned short type, const void return 0; } + +int rtattr_read_nexthop(const struct rtnexthop *rtnh, size_t size, int family, OrderedSet **ret) { + _cleanup_ordered_set_free_free_ OrderedSet *set = NULL; + int r; + + assert(rtnh); + assert(IN_SET(family, AF_INET, AF_INET6)); + + if (size < sizeof(struct rtnexthop)) + return -EBADMSG; + + for (; size >= sizeof(struct rtnexthop); ) { + _cleanup_free_ MultipathRoute *m = NULL; + + if (NLMSG_ALIGN(rtnh->rtnh_len) > size) + return -EBADMSG; + + if (rtnh->rtnh_len < sizeof(struct rtnexthop)) + return -EBADMSG; + + m = new(MultipathRoute, 1); + if (!m) + return -ENOMEM; + + *m = (MultipathRoute) { + .ifindex = rtnh->rtnh_ifindex, + .weight = rtnh->rtnh_hops == 0 ? 0 : rtnh->rtnh_hops + 1, + }; + + if (rtnh->rtnh_len > sizeof(struct rtnexthop)) { + size_t len = rtnh->rtnh_len - sizeof(struct rtnexthop); + + for (struct rtattr *attr = RTNH_DATA(rtnh); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) { + if (attr->rta_type == RTA_GATEWAY) { + if (attr->rta_len != RTA_LENGTH(FAMILY_ADDRESS_SIZE(family))) + return -EBADMSG; + + m->gateway.family = family; + memcpy(&m->gateway.address, RTA_DATA(attr), FAMILY_ADDRESS_SIZE(family)); + break; + } else if (attr->rta_type == RTA_VIA) { + uint16_t gw_family; + + if (family != AF_INET) + return -EINVAL; + + if (attr->rta_len < RTA_LENGTH(sizeof(uint16_t))) + return -EBADMSG; + + gw_family = *(uint16_t *) RTA_DATA(attr); + + if (gw_family != AF_INET6) + return -EBADMSG; + + if (attr->rta_len != RTA_LENGTH(FAMILY_ADDRESS_SIZE(gw_family) + sizeof(gw_family))) + return -EBADMSG; + + memcpy(&m->gateway, RTA_DATA(attr), FAMILY_ADDRESS_SIZE(gw_family) + sizeof(gw_family)); + break; + } + } + } + + r = ordered_set_ensure_put(&set, NULL, m); + if (r < 0) + return r; + + TAKE_PTR(m); + + size -= NLMSG_ALIGN(rtnh->rtnh_len); + rtnh = RTNH_NEXT(rtnh); + } + + if (ret) + *ret = TAKE_PTR(set); + return 0; +} diff --git a/src/libsystemd/sd-netlink/netlink-util.h b/src/libsystemd/sd-netlink/netlink-util.h index fa17e7dae7..2768d5fdc4 100644 --- a/src/libsystemd/sd-netlink/netlink-util.h +++ b/src/libsystemd/sd-netlink/netlink-util.h @@ -6,9 +6,22 @@ #include "sd-netlink.h" #include "in-addr-util.h" +#include "ordered-set.h" #include "socket-util.h" #include "util.h" +/* See struct rtvia in rtnetlink.h */ +typedef struct RouteVia { + uint16_t family; + union in_addr_union address; +} _packed_ RouteVia; + +typedef struct MultipathRoute { + RouteVia gateway; + int ifindex; + uint32_t weight; +} MultipathRoute; + int rtnl_message_new_synthetic_error(sd_netlink *rtnl, int error, uint32_t serial, sd_netlink_message **ret); uint32_t rtnl_message_get_serial(sd_netlink_message *m); void rtnl_message_seal(sd_netlink_message *m); @@ -94,3 +107,5 @@ int netlink_message_read_in_addr_union(sd_netlink_message *m, unsigned short typ void rtattr_append_attribute_internal(struct rtattr *rta, unsigned short type, const void *data, size_t data_length); int rtattr_append_attribute(struct rtattr **rta, unsigned short type, const void *data, size_t data_length); + +int rtattr_read_nexthop(const struct rtnexthop *rtnh, size_t size, int family, OrderedSet **ret); diff --git a/src/network/networkd-route.h b/src/network/networkd-route.h index 68d2d63574..ecc0aa4224 100644 --- a/src/network/networkd-route.h +++ b/src/network/networkd-route.h @@ -15,18 +15,6 @@ typedef struct Manager Manager; typedef struct Network Network; -/* See struct rtvia in rtnetlink.h */ -typedef struct RouteVia { - uint16_t family; - union in_addr_union address; -} _packed_ RouteVia; - -typedef struct MultipathRoute { - RouteVia gateway; - int ifindex; - uint32_t weight; -} MultipathRoute; - typedef struct Route { Network *network; NetworkConfigSection *section; From de52a83cb78ad39ea9a8fdce867861af08831971 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 7 Oct 2020 14:24:08 +0900 Subject: [PATCH 13/19] sd-netlink: introduce sd_netlink_message_read_data() --- src/libsystemd/sd-netlink/netlink-message.c | 24 +++++++++++++++++++++ src/systemd/sd-netlink.h | 1 + 2 files changed, 25 insertions(+) diff --git a/src/libsystemd/sd-netlink/netlink-message.c b/src/libsystemd/sd-netlink/netlink-message.c index 0d9a57ee9e..da4f5ba688 100644 --- a/src/libsystemd/sd-netlink/netlink-message.c +++ b/src/libsystemd/sd-netlink/netlink-message.c @@ -694,6 +694,30 @@ int sd_netlink_message_read(sd_netlink_message *m, unsigned short type, size_t s return r; } +int sd_netlink_message_read_data(sd_netlink_message *m, unsigned short type, size_t *ret_size, void **ret_data) { + void *attr_data, *data; + int r; + + assert_return(m, -EINVAL); + + r = netlink_message_read_internal(m, type, &attr_data, NULL); + if (r < 0) + return r; + + if (ret_data) { + data = memdup(attr_data, r); + if (!data) + return -ENOMEM; + + *ret_data = data; + } + + if (ret_size) + *ret_size = r; + + return r; +} + int sd_netlink_message_read_string_strdup(sd_netlink_message *m, unsigned short type, char **data) { void *attr_data; char *str; diff --git a/src/systemd/sd-netlink.h b/src/systemd/sd-netlink.h index ce7ed36d85..e01d960e37 100644 --- a/src/systemd/sd-netlink.h +++ b/src/systemd/sd-netlink.h @@ -103,6 +103,7 @@ int sd_netlink_message_open_container_union(sd_netlink_message *m, unsigned shor int sd_netlink_message_close_container(sd_netlink_message *m); int sd_netlink_message_read(sd_netlink_message *m, unsigned short type, size_t size, void *data); +int sd_netlink_message_read_data(sd_netlink_message *m, unsigned short type, size_t *ret_size, void **ret_data); int sd_netlink_message_read_string_strdup(sd_netlink_message *m, unsigned short type, char **data); int sd_netlink_message_read_string(sd_netlink_message *m, unsigned short type, const char **data); int sd_netlink_message_read_strv(sd_netlink_message *m, unsigned short container_type, unsigned short type_id, char ***ret); From f9bb33383223bc197014cd2799f87c350feb49c7 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 7 Oct 2020 14:25:11 +0900 Subject: [PATCH 14/19] network: manage multipath routes separately --- src/network/networkd-route.c | 310 +++++++++++++++++++++++------------ src/network/networkd-route.h | 1 + 2 files changed, 207 insertions(+), 104 deletions(-) diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index feb62ee783..31a009b088 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -457,36 +457,50 @@ static int route_get(const Manager *manager, const Link *link, const Route *in, return -ENOENT; } -static int route_add_internal(Manager *manager, Link *link, Set **routes, const Route *in, Route **ret) { +static void route_copy(Route *dest, const Route *src, const MultipathRoute *m) { + assert(dest); + assert(src); + dest->family = src->family; + dest->src = src->src; + dest->src_prefixlen = src->src_prefixlen; + dest->dst = src->dst; + dest->dst_prefixlen = src->dst_prefixlen; + dest->prefsrc = src->prefsrc; + dest->scope = src->scope; + dest->protocol = src->protocol; + dest->type = src->type; + dest->tos = src->tos; + dest->priority = src->priority; + dest->table = src->table; + dest->initcwnd = src->initcwnd; + dest->initrwnd = src->initrwnd; + dest->lifetime = src->lifetime; + + if (m) { + dest->gw_family = m->gateway.family; + dest->gw = m->gateway.address; + dest->gw_weight = m->weight; + } else { + dest->gw_family = src->gw_family; + dest->gw = src->gw; + } +} + +static int route_add_internal(Manager *manager, Link *link, Set **routes, const Route *in, const MultipathRoute *m, Route **ret) { _cleanup_(route_freep) Route *route = NULL; int r; assert(manager || link); assert(routes); assert(in); + assert(!m || (link && (m->ifindex == 0 || m->ifindex == link->ifindex))); r = route_new(&route); if (r < 0) return r; - route->family = in->family; - route->src = in->src; - route->src_prefixlen = in->src_prefixlen; - route->dst = in->dst; - route->dst_prefixlen = in->dst_prefixlen; - route->gw_family = in->gw_family; - route->gw = in->gw; - route->prefsrc = in->prefsrc; - route->scope = in->scope; - route->protocol = in->protocol; - route->type = in->type; - route->tos = in->tos; - route->priority = in->priority; - route->table = in->table; - route->initcwnd = in->initcwnd; - route->initrwnd = in->initrwnd; - route->lifetime = in->lifetime; + route_copy(route, in, m); r = set_ensure_put(routes, &route_hash_ops, route); if (r < 0) @@ -507,10 +521,10 @@ static int route_add_internal(Manager *manager, Link *link, Set **routes, const static int route_add_foreign(Manager *manager, Link *link, const Route *in, Route **ret) { assert(manager || link); - return route_add_internal(manager, link, link ? &link->routes_foreign : &manager->routes_foreign, in, ret); + return route_add_internal(manager, link, link ? &link->routes_foreign : &manager->routes_foreign, in, NULL, ret); } -static int route_add(Manager *manager, Link *link, const Route *in, Route **ret) { +static int route_add(Manager *manager, Link *link, const Route *in, const MultipathRoute *m, Route **ret) { Route *route; int r; @@ -520,7 +534,7 @@ static int route_add(Manager *manager, Link *link, const Route *in, Route **ret) r = route_get(manager, link, in, &route); if (r == -ENOENT) { /* Route does not exist, create a new one */ - r = route_add_internal(manager, link, link ? &link->routes : &manager->routes, in, &route); + r = route_add_internal(manager, link, link ? &link->routes : &manager->routes, in, m, &route); if (r < 0) return r; } else if (r == 0) { @@ -724,7 +738,7 @@ int link_drop_foreign_routes(Link *link) { continue; if (link_is_static_route_configured(link, route)) - k = route_add(NULL, link, route, NULL); + k = route_add(NULL, link, route, NULL, NULL); else k = route_remove(route, NULL, link, NULL); if (k < 0 && r >= 0) @@ -768,6 +782,47 @@ static int route_expire_handler(sd_event_source *s, uint64_t usec, void *userdat return 1; } +static int route_add_and_setup_timer(Link *link, const Route *route, const MultipathRoute *m, Route **ret) { + _cleanup_(sd_event_source_unrefp) sd_event_source *expire = NULL; + Route *nr; + int r; + + assert(link); + assert(route); + + if (IN_SET(route->type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE, RTN_THROW)) + r = route_add(link->manager, NULL, route, NULL, &nr); + else if (!m || m->ifindex == 0 || m->ifindex == link->ifindex) + r = route_add(NULL, link, route, m, &nr); + else { + Link *link_gw; + + r = link_get(link->manager, m->ifindex, &link_gw); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get link with ifindex %d: %m", m->ifindex); + + r = route_add(NULL, link_gw, route, m, &nr); + } + if (r < 0) + return log_link_error_errno(link, r, "Could not add route: %m"); + + /* TODO: drop expiration handling once it can be pushed into the kernel */ + if (nr->lifetime != USEC_INFINITY && !kernel_route_expiration_supported()) { + r = sd_event_add_time(link->manager->event, &expire, clock_boottime_or_monotonic(), + nr->lifetime, 0, route_expire_handler, nr); + if (r < 0) + return log_link_error_errno(link, r, "Could not arm expiration timer: %m"); + } + + sd_event_source_unref(nr->expire); + nr->expire = TAKE_PTR(expire); + + if (ret) + *ret = nr; + + return 0; +} + static int append_nexthop_one(const Route *route, const MultipathRoute *m, struct rtattr **rta, size_t offset) { struct rtnexthop *rtnh; struct rtattr *new_rta; @@ -856,9 +911,7 @@ int route_configure( Route **ret) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; - _cleanup_(sd_event_source_unrefp) sd_event_source *expire = NULL; unsigned flags; - Route *nr; int r; assert(link); @@ -1054,27 +1107,27 @@ int route_configure( link_ref(link); - if (IN_SET(route->type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE, RTN_THROW) || !ordered_set_isempty(route->multipath_routes)) - r = route_add(link->manager, NULL, route, &nr); - else - r = route_add(NULL, link, route, &nr); - if (r < 0) - return log_link_error_errno(link, r, "Could not add route: %m"); + if (ordered_set_isempty(route->multipath_routes)) { + Route *nr; - /* TODO: drop expiration handling once it can be pushed into the kernel */ - if (nr->lifetime != USEC_INFINITY && !kernel_route_expiration_supported()) { - r = sd_event_add_time(link->manager->event, &expire, clock_boottime_or_monotonic(), - nr->lifetime, 0, route_expire_handler, nr); + r = route_add_and_setup_timer(link, route, NULL, &nr); if (r < 0) - return log_link_error_errno(link, r, "Could not arm expiration timer: %m"); + return r; + + if (ret) + *ret = nr; + } else { + MultipathRoute *m; + + assert(!ret); + + ORDERED_SET_FOREACH(m, route->multipath_routes) { + r = route_add_and_setup_timer(link, route, m, NULL); + if (r < 0) + return r; + } } - sd_event_source_unref(nr->expire); - nr->expire = TAKE_PTR(expire); - - if (ret) - *ret = nr; - return 1; } @@ -1161,14 +1214,105 @@ int link_set_routes(Link *link) { return 0; } -int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) { - _cleanup_(route_freep) Route *tmp = NULL; +static int process_route_one(Manager *manager, Link *link, uint16_t type, const Route *tmp, const MultipathRoute *m) { + _cleanup_(route_freep) Route *nr = NULL; Route *route = NULL; + int r; + + assert(manager); + assert(tmp); + assert(IN_SET(type, RTM_NEWROUTE, RTM_DELROUTE)); + + if (m) { + if (link) + return log_link_warning_errno(link, SYNTHETIC_ERRNO(EINVAL), + "rtnl: received route contains both RTA_OIF and RTA_MULTIPATH, ignoring."); + + if (m->ifindex <= 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "rtnl: received multipath route with invalid ifindex, ignoring."); + + r = link_get(manager, m->ifindex, &link); + if (r < 0) { + log_warning_errno(r, "rtnl: received multipath route for link (%d) we do not know, ignoring: %m", m->ifindex); + return 0; + } + + r = route_new(&nr); + if (r < 0) + return log_oom(); + + route_copy(nr, tmp, m); + + tmp = nr; + } + + (void) route_get(manager, link, tmp, &route); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *buf_dst = NULL, *buf_dst_prefixlen = NULL, + *buf_src = NULL, *buf_gw = NULL, *buf_prefsrc = NULL; + char buf_scope[ROUTE_SCOPE_STR_MAX], buf_table[ROUTE_TABLE_STR_MAX], + buf_protocol[ROUTE_PROTOCOL_STR_MAX]; + + if (!in_addr_is_null(tmp->family, &tmp->dst)) { + (void) in_addr_to_string(tmp->family, &tmp->dst, &buf_dst); + (void) asprintf(&buf_dst_prefixlen, "/%u", tmp->dst_prefixlen); + } + if (!in_addr_is_null(tmp->family, &tmp->src)) + (void) in_addr_to_string(tmp->family, &tmp->src, &buf_src); + if (!in_addr_is_null(tmp->gw_family, &tmp->gw)) + (void) in_addr_to_string(tmp->gw_family, &tmp->gw, &buf_gw); + if (!in_addr_is_null(tmp->family, &tmp->prefsrc)) + (void) in_addr_to_string(tmp->family, &tmp->prefsrc, &buf_prefsrc); + + log_link_debug(link, + "%s route: dst: %s%s, src: %s, gw: %s, prefsrc: %s, scope: %s, table: %s, proto: %s, type: %s", + (!route && !manager->manage_foreign_routes) ? "Ignoring received foreign" : + type == RTM_DELROUTE ? "Forgetting" : + route ? "Received remembered" : "Remembering", + strna(buf_dst), strempty(buf_dst_prefixlen), + strna(buf_src), strna(buf_gw), strna(buf_prefsrc), + format_route_scope(tmp->scope, buf_scope, sizeof buf_scope), + format_route_table(tmp->table, buf_table, sizeof buf_table), + format_route_protocol(tmp->protocol, buf_protocol, sizeof buf_protocol), + strna(route_type_to_string(tmp->type))); + } + + switch (type) { + case RTM_NEWROUTE: + if (!route && manager->manage_foreign_routes) { + /* A route appeared that we did not request */ + r = route_add_foreign(manager, link, tmp, NULL); + if (r < 0) { + log_link_warning_errno(link, r, "Failed to remember foreign route, ignoring: %m"); + return 0; + } + } + + break; + + case RTM_DELROUTE: + route_free(route); + break; + + default: + assert_not_reached("Received route message with invalid RTNL message type"); + } + + return 1; +} + +int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) { + _cleanup_ordered_set_free_free_ OrderedSet *multipath_routes = NULL; + _cleanup_(route_freep) Route *tmp = NULL; + _cleanup_free_ void *rta_multipath = NULL; Link *link = NULL; uint32_t ifindex; uint16_t type; unsigned char table; RouteVia via; + size_t rta_len; int r; assert(rtnl); @@ -1370,57 +1514,28 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Ma } } - (void) route_get(m, link, tmp, &route); - - if (DEBUG_LOGGING) { - _cleanup_free_ char *buf_dst = NULL, *buf_dst_prefixlen = NULL, - *buf_src = NULL, *buf_gw = NULL, *buf_prefsrc = NULL; - char buf_scope[ROUTE_SCOPE_STR_MAX], buf_table[ROUTE_TABLE_STR_MAX], - buf_protocol[ROUTE_PROTOCOL_STR_MAX]; - - if (!in_addr_is_null(tmp->family, &tmp->dst)) { - (void) in_addr_to_string(tmp->family, &tmp->dst, &buf_dst); - (void) asprintf(&buf_dst_prefixlen, "/%u", tmp->dst_prefixlen); + r = sd_netlink_message_read_data(message, RTA_MULTIPATH, &rta_len, &rta_multipath); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: failed to read RTA_MULTIPATH attribute, ignoring: %m"); + return 0; + } else if (r >= 0) { + r = rtattr_read_nexthop(rta_multipath, rta_len, tmp->family, &multipath_routes); + if (r < 0) { + log_link_warning_errno(link, r, "rtnl: failed to parse RTA_MULTIPATH attribute, ignoring: %m"); + return 0; } - if (!in_addr_is_null(tmp->family, &tmp->src)) - (void) in_addr_to_string(tmp->family, &tmp->src, &buf_src); - if (!in_addr_is_null(tmp->gw_family, &tmp->gw)) - (void) in_addr_to_string(tmp->gw_family, &tmp->gw, &buf_gw); - if (!in_addr_is_null(tmp->family, &tmp->prefsrc)) - (void) in_addr_to_string(tmp->family, &tmp->prefsrc, &buf_prefsrc); - - log_link_debug(link, - "%s route: dst: %s%s, src: %s, gw: %s, prefsrc: %s, scope: %s, table: %s, proto: %s, type: %s", - (!route && !m->manage_foreign_routes) ? "Ignoring received foreign" : - type == RTM_DELROUTE ? "Forgetting" : - route ? "Received remembered" : "Remembering", - strna(buf_dst), strempty(buf_dst_prefixlen), - strna(buf_src), strna(buf_gw), strna(buf_prefsrc), - format_route_scope(tmp->scope, buf_scope, sizeof buf_scope), - format_route_table(tmp->table, buf_table, sizeof buf_table), - format_route_protocol(tmp->protocol, buf_protocol, sizeof buf_protocol), - strna(route_type_to_string(tmp->type))); } - switch (type) { - case RTM_NEWROUTE: - if (!route && m->manage_foreign_routes) { - /* A route appeared that we did not request */ - r = route_add_foreign(m, link, tmp, &route); - if (r < 0) { - log_link_warning_errno(link, r, "Failed to remember foreign route, ignoring: %m"); - return 0; - } + if (ordered_set_isempty(multipath_routes)) + (void) process_route_one(m, link, type, tmp, NULL); + else { + MultipathRoute *mr; + + ORDERED_SET_FOREACH(mr, multipath_routes) { + r = process_route_one(m, link, type, tmp, mr); + if (r < 0) + break; } - - break; - - case RTM_DELROUTE: - route_free(route); - break; - - default: - assert_not_reached("Received route message with invalid RTNL message type"); } return 1; @@ -1457,11 +1572,9 @@ int link_deserialize_routes(Link *link, const char *routes) { assert(link); for (const char *p = routes;; ) { - _cleanup_(sd_event_source_unrefp) sd_event_source *expire = NULL; _cleanup_(route_freep) Route *tmp = NULL; _cleanup_free_ char *route_str = NULL; char *prefixlen_str; - Route *route; r = extract_first_word(&p, &route_str, NULL, 0); if (r < 0) @@ -1500,20 +1613,9 @@ int link_deserialize_routes(Link *link, const char *routes) { continue; } - r = route_add(NULL, link, tmp, &route); + r = route_add_and_setup_timer(link, tmp, NULL, NULL); if (r < 0) return log_link_debug_errno(link, r, "Failed to add route: %m"); - - if (route->lifetime != USEC_INFINITY && !kernel_route_expiration_supported()) { - r = sd_event_add_time(link->manager->event, &expire, - clock_boottime_or_monotonic(), - route->lifetime, 0, route_expire_handler, route); - if (r < 0) - log_link_debug_errno(link, r, "Could not arm route expiration handler: %m"); - } - - sd_event_source_unref(route->expire); - route->expire = TAKE_PTR(expire); } } diff --git a/src/network/networkd-route.h b/src/network/networkd-route.h index ecc0aa4224..8207b67a2d 100644 --- a/src/network/networkd-route.h +++ b/src/network/networkd-route.h @@ -24,6 +24,7 @@ typedef struct Route { int family; int gw_family; + uint32_t gw_weight; int quickack; int fast_open_no_cookie; int ttl_propagate; From bff94a84c27cbca20e539315a53f5206bf53d6ea Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 7 Oct 2020 19:39:59 +0900 Subject: [PATCH 15/19] util: make local_gateways() support RT_VIA and RT_MULTIPATH Then, `networkctl status` correctly shows gateways. --- src/shared/local-addresses.c | 119 ++++++++++++++++++++++++++--------- 1 file changed, 88 insertions(+), 31 deletions(-) diff --git a/src/shared/local-addresses.c b/src/shared/local-addresses.c index 2f3d675643..aaad0d9ff0 100644 --- a/src/shared/local-addresses.c +++ b/src/shared/local-addresses.c @@ -142,11 +142,40 @@ int local_addresses(sd_netlink *context, int ifindex, int af, struct local_addre return (int) n_list; } +static int add_local_gateway( + struct local_address **list, + size_t *n_list, + size_t *n_allocated, + int af, + int ifindex, + uint32_t metric, + const RouteVia *via) { + + assert(list); + assert(n_list); + assert(n_allocated); + assert(via); + + if (af != AF_UNSPEC && af != via->family) + return 0; + + if (!GREEDY_REALLOC(*list, *n_allocated, *n_list + 1)) + return -ENOMEM; + + (*list)[(*n_list)++] = (struct local_address) { + .ifindex = ifindex, + .metric = metric, + .family = via->family, + .address = via->address, + }; + + return 0; +} + int local_gateways(sd_netlink *context, int ifindex, int af, struct local_address **ret) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; _cleanup_free_ struct local_address *list = NULL; - sd_netlink_message *m = NULL; size_t n_list = 0, n_allocated = 0; int r; @@ -172,12 +201,16 @@ int local_gateways(sd_netlink *context, int ifindex, int af, struct local_addres if (r < 0) return r; - for (m = reply; m; m = sd_netlink_message_next(m)) { - struct local_address *a; + for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) { + _cleanup_ordered_set_free_free_ OrderedSet *multipath_routes = NULL; + _cleanup_free_ void *rta_multipath = NULL; + union in_addr_union gateway; uint16_t type; unsigned char dst_len, src_len, table; - uint32_t ifi; + uint32_t ifi, metric = 0; + size_t rta_len; int family; + RouteVia via; r = sd_netlink_message_get_errno(m); if (r < 0) @@ -208,48 +241,72 @@ int local_gateways(sd_netlink *context, int ifindex, int af, struct local_addres if (table != RT_TABLE_MAIN) continue; - r = sd_netlink_message_read_u32(m, RTA_OIF, &ifi); - if (r == -ENODATA) /* Not all routes have an RTA_OIF attribute (for example nexthop ones) */ - continue; - if (r < 0) + r = sd_netlink_message_read_u32(m, RTA_PRIORITY, &metric); + if (r < 0 && r != -ENODATA) return r; - if (ifindex > 0 && (int) ifi != ifindex) - continue; r = sd_rtnl_message_route_get_family(m, &family); if (r < 0) return r; - if (af != AF_UNSPEC && af != family) + if (!IN_SET(family, AF_INET, AF_INET6)) continue; - if (!GREEDY_REALLOC0(list, n_allocated, n_list + 1)) - return -ENOMEM; - - a = list + n_list; - - switch (family) { - case AF_INET: - r = sd_netlink_message_read_in_addr(m, RTA_GATEWAY, &a->address.in); - if (r < 0) + r = sd_netlink_message_read_u32(m, RTA_OIF, &ifi); + if (r < 0 && r != -ENODATA) + return r; + if (r >= 0) { + if (ifi <= 0) + return -EINVAL; + if (ifindex > 0 && (int) ifi != ifindex) continue; - break; - case AF_INET6: - r = sd_netlink_message_read_in6_addr(m, RTA_GATEWAY, &a->address.in6); - if (r < 0) + r = netlink_message_read_in_addr_union(m, RTA_GATEWAY, family, &gateway); + if (r < 0 && r != -ENODATA) + return r; + if (r >= 0) { + via.family = family; + via.address = gateway; + r = add_local_gateway(&list, &n_list, &n_allocated, af, ifi, metric, &via); + if (r < 0) + return r; + + continue; + } + + if (family != AF_INET) continue; - break; - default: - continue; + r = sd_netlink_message_read(m, RTA_VIA, sizeof(via), &via); + if (r < 0 && r != -ENODATA) + return r; + if (r >= 0) { + r = add_local_gateway(&list, &n_list, &n_allocated, af, ifi, metric, &via); + if (r < 0) + return r; + + continue; + } } - sd_netlink_message_read_u32(m, RTA_PRIORITY, &a->metric); + r = sd_netlink_message_read_data(m, RTA_MULTIPATH, &rta_len, &rta_multipath); + if (r < 0 && r != -ENODATA) + return r; + if (r >= 0) { + MultipathRoute *mr; - a->ifindex = ifi; - a->family = family; + r = rtattr_read_nexthop(rta_multipath, rta_len, family, &multipath_routes); + if (r < 0) + return r; - n_list++; + ORDERED_SET_FOREACH(mr, multipath_routes) { + if (ifindex > 0 && mr->ifindex != ifindex) + continue; + + r = add_local_gateway(&list, &n_list, &n_allocated, af, ifi, metric, &mr->gateway); + if (r < 0) + return r; + } + } } typesafe_qsort(list, n_list, address_compare); From d9005dec6e977715fe4a6ffd146b4af1050c1e11 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 7 Oct 2020 13:02:00 +0200 Subject: [PATCH 16/19] test-network: do not fail when multiple ipv6 default gateways are configured When multiple ipv6 default gateways are set, kernel seems to merge them into a multipath route. --- test/test-network/systemd-networkd-tests.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 3411c30cc8..e4ac070787 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -2145,10 +2145,11 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): self.assertRegex(output, '2001:1234:5:8fff:ff:ff:ff:ff proto static') self.assertRegex(output, '2001:1234:5:8f63::1 proto kernel') - print('### ip -6 route show dev dummy98 default') - output = check_output('ip -6 route show dev dummy98 default') + print('### ip -6 route show default') + output = check_output('ip -6 route show default') print(output) - self.assertRegex(output, 'default via 2001:1234:5:8fff:ff:ff:ff:ff proto static metric 1024 pref medium') + self.assertRegex(output, 'default') + self.assertRegex(output, 'via 2001:1234:5:8fff:ff:ff:ff:ff') print('### ip -4 route show dev dummy98') output = check_output('ip -4 route show dev dummy98') @@ -2455,9 +2456,10 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): output = check_output('ip -4 route show dev dummy98') print(output) self.assertRegex(output, '10.2.0.0/16 proto kernel scope link src 10.2.3.4') - output = check_output('ip -6 route show dev dummy98') + output = check_output('ip -6 route show default') print(output) - self.assertRegex(output, 'default via 2607:5300:203:39ff:ff:ff:ff:ff proto static') + self.assertRegex(output, 'default') + self.assertRegex(output, 'via 2607:5300:203:39ff:ff:ff:ff:ff') check_output('ip link del dummy98') @@ -2478,9 +2480,9 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): output = check_output('ip -4 route show dev dummy98') print(output) self.assertRegex(output, '10.2.0.0/16 proto kernel scope link src 10.2.3.4') - output = check_output('ip -6 route show dev dummy98') + output = check_output('ip -6 route show default') print(output) - self.assertRegex(output, 'default via 2607:5300:203:39ff:ff:ff:ff:ff proto static') + self.assertRegex(output, 'via 2607:5300:203:39ff:ff:ff:ff:ff') def test_bind_carrier(self): check_output('ip link add dummy98 type dummy') From d306d1d0ca2e6559a598a6eb459f65f9377f909e Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 7 Oct 2020 13:34:00 +0200 Subject: [PATCH 17/19] network: introduce Gateway=_dhcp4 and _dhcp6, and deprecate "_dhcp" Fixes #17249. --- man/systemd.network.xml | 7 ++++--- src/network/networkd-route.c | 33 ++++++++++++++++++++++++++++----- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index b8610b3786..a8582ec6f8 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -1282,9 +1282,10 @@ IPv6Token=prefixstable:2002:da8:1:: Gateway= - Takes the gateway address or special value _dhcp. If - _dhcp, then the gateway address provided by DHCP (or in the IPv6 case, - provided by IPv6 RA) is used. + Takes the gateway address or the special values _dhcp4 and + _dhcp6. If _dhcp4 or _dhcp6 is + set, then the gateway address provided by DHCP (or in the IPv6 case, provided by IPv6 RA) + is used. diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index 31a009b088..12a344049f 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -1725,6 +1725,20 @@ int config_parse_gateway( TAKE_PTR(n); return 0; } + + if (streq(rvalue, "_dhcp4")) { + n->gw_family = AF_INET; + n->gateway_from_dhcp = true; + TAKE_PTR(n); + return 0; + } + + if (streq(rvalue, "_dhcp6")) { + n->gw_family = AF_INET6; + n->gateway_from_dhcp = true; + TAKE_PTR(n); + return 0; + } } r = in_addr_from_string_auto(rvalue, &n->gw_family, &n->gw); @@ -1734,6 +1748,7 @@ int config_parse_gateway( return 0; } + n->gateway_from_dhcp = false; TAKE_PTR(n); return 0; } @@ -2361,11 +2376,19 @@ static int route_section_verify(Route *route, Network *network) { if (route->family == AF_UNSPEC) { assert(route->section); - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: Route section without Gateway=, Destination=, Source=, " - "or PreferredSource= field configured. " - "Ignoring [Route] section from line %u.", - route->section->filename, route->section->line); + if (route->gateway_from_dhcp) { + log_warning("%s: Deprecated value \"_dhcp\" is specified for Gateway= in [Route] section from line %u. " + "Please use \"_dhcp4\" or \"_dhcp6\" instead. Assuming \"_dhcp4\".", + route->section->filename, route->section->line); + + route->family = AF_INET; + route->gw_family = AF_INET; + } else + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Route section without Gateway=, Destination=, Source=, " + "or PreferredSource= field configured. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); } if (route->family == AF_INET6 && route->gw_family == AF_INET) From d442bb372895302b824b41656243c772a7e636f5 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 7 Oct 2020 13:39:47 +0200 Subject: [PATCH 18/19] network: make Gateway= in [Route] section accept an empty string --- src/network/networkd-route.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index 12a344049f..b59c48d482 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -1720,6 +1720,14 @@ int config_parse_gateway( return 0; } + if (isempty(rvalue)) { + n->gateway_from_dhcp = false; + n->gw_family = AF_UNSPEC; + n->gw = IN_ADDR_NULL; + TAKE_PTR(n); + return 0; + } + if (streq(rvalue, "_dhcp")) { n->gateway_from_dhcp = true; TAKE_PTR(n); From ceea6c1affc5681eac9b2141a4e48454184f7867 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 7 Oct 2020 13:42:54 +0200 Subject: [PATCH 19/19] network: introduce IPV4_ADDRESS_FMT_STR macro This also moves ADDRESS_FMT_VAL() macro to networkd-address.h, and renames it to IPV4_ADDRESS_FMT_VAL(). --- src/network/networkd-address.h | 7 +++++++ src/network/networkd-dhcp4.c | 24 ++++++++++++------------ src/network/networkd-ipv4ll.c | 6 +++--- src/network/networkd-link.h | 6 ------ 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/network/networkd-address.h b/src/network/networkd-address.h index 431e507851..d12120f00d 100644 --- a/src/network/networkd-address.h +++ b/src/network/networkd-address.h @@ -83,3 +83,10 @@ CONFIG_PARSER_PROTOTYPE(config_parse_lifetime); CONFIG_PARSER_PROTOTYPE(config_parse_address_flags); CONFIG_PARSER_PROTOTYPE(config_parse_address_scope); CONFIG_PARSER_PROTOTYPE(config_parse_duplicate_address_detection); + +#define IPV4_ADDRESS_FMT_STR "%u.%u.%u.%u" +#define IPV4_ADDRESS_FMT_VAL(address) \ + be32toh((address).s_addr) >> 24, \ + (be32toh((address).s_addr) >> 16) & 0xFFu, \ + (be32toh((address).s_addr) >> 8) & 0xFFu, \ + be32toh((address).s_addr) & 0xFFu diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 81a9594512..e7dc488e37 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -791,20 +791,20 @@ static int dhcp4_update_address(Link *link, bool announce) { if (r > 0 && !in4_addr_is_null(&router[0])) log_struct(LOG_INFO, LOG_LINK_INTERFACE(link), - LOG_LINK_MESSAGE(link, "DHCPv4 address %u.%u.%u.%u/%u via %u.%u.%u.%u", - ADDRESS_FMT_VAL(address), + LOG_LINK_MESSAGE(link, "DHCPv4 address "IPV4_ADDRESS_FMT_STR"/%u via "IPV4_ADDRESS_FMT_STR, + IPV4_ADDRESS_FMT_VAL(address), prefixlen, - ADDRESS_FMT_VAL(router[0])), - "ADDRESS=%u.%u.%u.%u", ADDRESS_FMT_VAL(address), + IPV4_ADDRESS_FMT_VAL(router[0])), + "ADDRESS="IPV4_ADDRESS_FMT_STR, IPV4_ADDRESS_FMT_VAL(address), "PREFIXLEN=%u", prefixlen, - "GATEWAY=%u.%u.%u.%u", ADDRESS_FMT_VAL(router[0])); + "GATEWAY="IPV4_ADDRESS_FMT_STR, IPV4_ADDRESS_FMT_VAL(router[0])); else log_struct(LOG_INFO, LOG_LINK_INTERFACE(link), - LOG_LINK_MESSAGE(link, "DHCPv4 address %u.%u.%u.%u/%u", - ADDRESS_FMT_VAL(address), + LOG_LINK_MESSAGE(link, "DHCPv4 address "IPV4_ADDRESS_FMT_STR"/%u", + IPV4_ADDRESS_FMT_VAL(address), prefixlen), - "ADDRESS=%u.%u.%u.%u", ADDRESS_FMT_VAL(address), + "ADDRESS="IPV4_ADDRESS_FMT_STR, IPV4_ADDRESS_FMT_VAL(address), "PREFIXLEN=%u", prefixlen); } @@ -955,8 +955,8 @@ static int dhcp_server_is_deny_listed(Link *link, sd_dhcp_client *client) { if (set_contains(link->network->dhcp_deny_listed_ip, UINT32_TO_PTR(addr.s_addr))) { log_struct(LOG_DEBUG, LOG_LINK_INTERFACE(link), - LOG_LINK_MESSAGE(link, "DHCPv4 IP '%u.%u.%u.%u' found in deny-listed IP addresses, ignoring offer", - ADDRESS_FMT_VAL(addr))); + LOG_LINK_MESSAGE(link, "DHCPv4 server IP address "IPV4_ADDRESS_FMT_STR" found in deny-list, ignoring offer", + IPV4_ADDRESS_FMT_VAL(addr))); return true; } @@ -983,8 +983,8 @@ static int dhcp_server_is_allow_listed(Link *link, sd_dhcp_client *client) { if (set_contains(link->network->dhcp_allow_listed_ip, UINT32_TO_PTR(addr.s_addr))) { log_struct(LOG_DEBUG, LOG_LINK_INTERFACE(link), - LOG_LINK_MESSAGE(link, "DHCPv4 IP '%u.%u.%u.%u' found in allow-listed IP addresses, accepting offer", - ADDRESS_FMT_VAL(addr))); + LOG_LINK_MESSAGE(link, "DHCPv4 server IP address "IPV4_ADDRESS_FMT_STR" found in allow-list, accepting offer", + IPV4_ADDRESS_FMT_VAL(addr))); return true; } diff --git a/src/network/networkd-ipv4ll.c b/src/network/networkd-ipv4ll.c index b0e0a97ad8..03f29e3b66 100644 --- a/src/network/networkd-ipv4ll.c +++ b/src/network/networkd-ipv4ll.c @@ -23,7 +23,7 @@ static int ipv4ll_address_lost(Link *link) { if (r < 0) return 0; - log_link_debug(link, "IPv4 link-local release %u.%u.%u.%u", ADDRESS_FMT_VAL(addr)); + log_link_debug(link, "IPv4 link-local release "IPV4_ADDRESS_FMT_STR, IPV4_ADDRESS_FMT_VAL(addr)); r = address_new(&address); if (r < 0) @@ -79,8 +79,8 @@ static int ipv4ll_address_claimed(sd_ipv4ll *ll, Link *link) { else if (r < 0) return r; - log_link_debug(link, "IPv4 link-local claim %u.%u.%u.%u", - ADDRESS_FMT_VAL(address)); + log_link_debug(link, "IPv4 link-local claim "IPV4_ADDRESS_FMT_STR, + IPV4_ADDRESS_FMT_VAL(address)); r = address_new(&ll_addr); if (r < 0) diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 08cfc60c1f..b712bb262f 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -243,9 +243,3 @@ int log_link_message_full_errno(Link *link, sd_netlink_message *m, int level, in #define log_link_message_notice_errno(link, m, err, msg) log_link_message_full_errno(link, m, LOG_NOTICE, err, msg) #define log_link_message_info_errno(link, m, err, msg) log_link_message_full_errno(link, m, LOG_INFO, err, msg) #define log_link_message_debug_errno(link, m, err, msg) log_link_message_full_errno(link, m, LOG_DEBUG, err, msg) - -#define ADDRESS_FMT_VAL(address) \ - be32toh((address).s_addr) >> 24, \ - (be32toh((address).s_addr) >> 16) & 0xFFu, \ - (be32toh((address).s_addr) >> 8) & 0xFFu, \ - be32toh((address).s_addr) & 0xFFu