From f02e6c7e28b1cbb0f57f7361f1579b40179fc9c8 Mon Sep 17 00:00:00 2001 From: Jonas Gorski Date: Tue, 14 May 2024 12:45:16 +0200 Subject: [PATCH] route: properly handle multiple kernel prefix routes with same dst When configuring IP(v6) addresses on interfaces, the kernel will install appropriate prefix routes (except when using noprefixroute). When configuring addresses from the same subnet on multiple interfaces, the kernel will happily configure multiple prefix routes. Most prominent example are per interface IPv6 link local fe80::/64 routes. Currently libnl treats these routes as identical, and thus will only keep the newest one of the routes in the cache (as each additional one is treated as an update to the previous route). Therefore we need to extend route_id_attrs_get() to properly treat these routes as different: * For IPv4, these routes will have a preferred source set. * For IPv6, these routes will have a single nexthop pointing to the interface (OIF). Also, only kernel may create these routes, attempts from userspace will be rejected when trying to add a second route for the same prefix. ip route output: $ ip r show table all ... 10.0.0.0/24 dev tun0 proto kernel scope link src 10.0.0.1 10.0.0.0/24 dev enp0s31f6 proto kernel scope link src 10.0.0.2 ... broadcast 10.0.0.255 dev tun0 table local proto kernel scope link src 10.0.0.1 broadcast 10.0.0.255 dev enp0s31f6 table local proto kernel scope link src 10.0.0.2 ... fe80::/64 dev tun0 proto kernel metric 256 pref medium fe80::/64 dev enp0s31f6 proto kernel metric 1024 pref medium ... multicast ff00::/8 dev enp0s31f6 table local proto kernel metric 256 pref medium multicast ff00::/8 dev tun0 table local proto kernel metric 256 pref medium $ ip r show table all | wc -l 37 Before: $ ./src/nl-route-list ... inet 10.0.0.0/24 table main type unicast via dev tun0 ... inet 10.0.0.255 table local type broadcast via dev tun0 ... inet6 fe80::/64 table main type unicast via dev tun0 inet6 fe80::/64 table main type unicast via dev enp0s31f6 ... inet6 ff00::/8 table local type multicast via dev enp0s31f6 $ ./src/nl-route-list | grep -v cache | wc -l 34 After: $ ./src/nl-route-list ... inet 10.0.0.0/24 table main type unicast via dev tun0 inet 10.0.0.0/24 table main type unicast via dev enp0s31f6 ... inet6 fe80::/64 table main type unicast via dev tun0 inet6 fe80::/64 table main type unicast via dev enp0s31f6 ... inet6 ff00::/8 table local type multicast via dev enp0s31f6 inet6 ff00::/8 table local type multicast via dev tun0 $ ./src/nl-route-list | grep -v cache | wc -l 37 (ip route doesn't show cache routes) Signed-off-by: Jonas Gorski --- lib/route/route_obj.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/lib/route/route_obj.c b/lib/route/route_obj.c index aba1a1bbf..42b5fd748 100644 --- a/lib/route/route_obj.c +++ b/lib/route/route_obj.c @@ -387,6 +387,39 @@ static uint32_t route_id_attrs_get(struct nl_object *obj) if (route->rt_family == AF_MPLS) rv &= ~ROUTE_ATTR_PRIO; + if (route->rt_protocol == RTPROT_KERNEL) { + /* + * If configuring Ip(v4) addresses for the same prefix on + * different interfaces, the kernel will install a + * prefix route for each interface with the ip address + * as preferred source. + */ + if (route->rt_family == AF_INET && + route->rt_scope == RT_SCOPE_LINK) + rv |= ROUTE_ATTR_PREF_SRC; + + /* + * For IPv6 addresses, the prefix routes will have + * a single dev nexthop. + */ + if (route->rt_family == AF_INET6 && route->rt_nr_nh == 1) { + struct rtnl_nexthop *first; + + first = nl_list_first_entry(&route->rt_nexthops, + struct rtnl_nexthop, + rtnh_list); + + /* + * Only interface, no other values, so force + * nexthop comparison. + */ + if (!rtnl_route_nh_get_gateway(first) && + !rtnl_route_nh_get_via(first) && + !rtnl_route_nh_get_newdst(first)) + rv |= ROUTE_ATTR_MULTIPATH; + } + } + return rv; }