[PATCH v2] net/ipv6: allow device-only routes via the multipath API

From: azey
Date: Mon Nov 24 2025 - 08:53:52 EST


At some point after b5d2d75e079a ("net/ipv6: Do not allow device only
routes via the multipath API"), the IPv6 stack was updated such that
device-only multipath routes can be installed and work correctly.

This change removes checks that prevent them from being installed,
and adds a fib6_explicit_ecmp flag to fib6_info. The flag is only
checked in rt6_qualify_for_ecmp() and exists to prevent regressions.

Signed-off-by: azey <me@xxxxxxxx>
---
Changes in v2:
- Added fib6_explicit_ecmp flag to fib6_info to prevent regressions.
Very simple (and naive) fix, all it does is flag routes created as
multipath and check for the flag in rt6_qualify_for_ecmp().
I'm not sure whether it should be an RTF_ flag in fib6_flags instead,
but there aren't any unused bits in that field so I made it separate.
- Removed hanging has_gateway as reported by <lkp@xxxxxxxxx> bot

Link to v1:
https://lore.kernel.org/netdev/a6vmtv3ylu224fnj5awi6xrgnjoib5r2jm3kny672hemsk5ifi@ychcxqnmy5us/
---
include/net/ip6_fib.h | 3 ++-
include/net/ip6_route.h | 5 +++--
net/ipv6/route.c | 11 ++---------
3 files changed, 7 insertions(+), 12 deletions(-)

diff --git a/include/net/ip6_fib.h b/include/net/ip6_fib.h
index 88b0dd4d8e09..da9d03cbbab4 100644
--- a/include/net/ip6_fib.h
+++ b/include/net/ip6_fib.h
@@ -196,7 +196,8 @@ struct fib6_info {
dst_nocount:1,
dst_nopolicy:1,
fib6_destroying:1,
- unused:4;
+ fib6_explicit_ecmp:1,
+ unused:3;

struct list_head purge_link;
struct rcu_head rcu;
diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h
index 7c5512baa4b2..5f00e9e252c2 100644
--- a/include/net/ip6_route.h
+++ b/include/net/ip6_route.h
@@ -73,8 +73,9 @@ static inline bool rt6_need_strict(const struct in6_addr *daddr)
static inline bool rt6_qualify_for_ecmp(const struct fib6_info *f6i)
{
/* the RTF_ADDRCONF flag filters out RA's */
- return !(f6i->fib6_flags & RTF_ADDRCONF) && !f6i->nh &&
- f6i->fib6_nh->fib_nh_gw_family;
+ return f6i->fib6_explicit_ecmp ||
+ (!(f6i->fib6_flags & RTF_ADDRCONF) && !f6i->nh &&
+ f6i->fib6_nh->fib_nh_gw_family);
}

void ip6_route_input(struct sk_buff *skb);
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index aee6a10b112a..7ac69bf5ccf2 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -5119,7 +5119,6 @@ static int rtm_to_fib6_multipath_config(struct fib6_config *cfg,
}

do {
- bool has_gateway = cfg->fc_flags & RTF_GATEWAY;
int attrlen = rtnh_attrlen(rtnh);

if (attrlen > 0) {
@@ -5133,17 +5132,9 @@ static int rtm_to_fib6_multipath_config(struct fib6_config *cfg,
"Invalid IPv6 address in RTA_GATEWAY");
return -EINVAL;
}
-
- has_gateway = true;
}
}

- if (newroute && (cfg->fc_nh_id || !has_gateway)) {
- NL_SET_ERR_MSG(extack,
- "Device only routes can not be added for IPv6 using the multipath API.");
- return -EINVAL;
- }
-
rtnh = rtnh_next(rtnh, &remaining);
} while (rtnh_ok(rtnh, remaining));

@@ -5448,6 +5439,8 @@ static int ip6_route_multipath_add(struct fib6_config *cfg,
goto cleanup;
}

+ rt->fib6_explicit_ecmp = true;
+
err = ip6_route_info_create_nh(rt, &r_cfg, GFP_KERNEL, extack);
if (err) {
rt = NULL;

base-commit: bd10acae08aeb9cd2f555acdbacb98b9fbb02a27
--
2.51.0

Attachment: signature.asc
Description: PGP signature