Netdev List
 help / color / mirror / Atom feed
* [PATCH net v2 0/4] ipv4: restrict on-link fallback to lookup misses
@ 2026-11-10  6:15 liamsmith827
  2026-11-10  6:15 ` [PATCH net v2 1/4] ipv4: add FIB_LOOKUP_REPORT_MISS liamsmith827
                   ` (4 more replies)
  0 siblings, 5 replies; 12+ messages in thread
From: liamsmith827 @ 2026-11-10  6:15 UTC (permalink / raw)
  To: netdev
  Cc: David Ahern, Ido Schimmel, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan,
	linux-kselftest, linux-kernel, liamsmith827

From: liamsmith827 <liamsmith827@proton.me>

For non-vrf unicast, the on-link fallback currently occurs regardless of
the particular error returned by fib_lookup(). As a result, routes and
rules that are supposed to restrict traffic (blackhole, prohibit,
unreachable) are unable to do so comprehensively.

A specific example of this being a problem is within Android, where the
VPN "kill switch" is implemented with prohibit rules. Any process that
can set flowi4_oif (SO_BINDTODEVICE, IP_UNICAST_IF, IP_PKTINFO, etc.) is
able to utilize this fallback, meaning unprivileged Android apps can
trivially bypass the VPN. This is particularly bad on cellular networks,
where ARP is often not needed.

The first patch adds FIB_LOOKUP_REPORT_MISS. IPv4 FIB lookup maps a
routing miss to -ENETUNREACH, which is also returned when an unreachable
rule is encountered. This flag prevents the mapping, returning -ESRCH
instead.

The second patch uses this flag to restrict the fallback to routing
lookup misses for non-vrf unicast. The fallback is still always taken
for multicast, regardless of any restrictions or misses.

The third patch updates tests now that the on-link fallback has been
restricted to routing lookup misses for non-vrf unicast.

The fourth patch replaces an existing comment which describes obsolete
fib_lookup(), MSG_DONTROUTE and IPv6 behavior.

This was originally observed on an Android device, by way of packet
capture.

Tested fcnal-test.sh in UML with CONFIG_IP_MULTIPLE_TABLES both enabled
and disabled.

v2:
- Restrict the fallback to routing lookup misses, instead of only
  honoring prohibit (-EACCES).
- Add FIB_LOOKUP_REPORT_MISS to differentiate a routing miss from an
  unreachable rule.
- Split the comment update out into its own patch and remove the
  signature.
- Enable all of the commented out device bind tests in
  ipv4_ping_novrf(), add a route miss test and update the UDP test
  expectations.
- Target net instead of net-next, add Fixes and Cc stable.
- Drop RFC.
v1: https://lore.kernel.org/netdev/20261019032406.88874-1-liamsmith827@grapheneos.org/

liamsmith827 (4):
  ipv4: add FIB_LOOKUP_REPORT_MISS
  ipv4: restrict on-link fallback to lookup misses
  selftests: net: test restricted on-link fallback
  ipv4: replace obsolete routing comment

 include/net/fib_rules.h                   |  5 +++
 include/net/ip_fib.h                      |  6 +--
 net/ipv4/fib_rules.c                      |  2 +-
 net/ipv4/route.c                          | 34 ++++++++--------
 tools/testing/selftests/net/fcnal-test.sh | 47 +++++++++++++----------
 5 files changed, 51 insertions(+), 43 deletions(-)

-- 
2.47.3


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH net v2 1/4] ipv4: add FIB_LOOKUP_REPORT_MISS
  2026-11-10  6:15 [PATCH net v2 0/4] ipv4: restrict on-link fallback to lookup misses liamsmith827
@ 2026-11-10  6:15 ` liamsmith827
  2026-11-12 19:33   ` Ido Schimmel
  2026-11-10  6:15 ` [PATCH net v2 2/4] ipv4: restrict on-link fallback to lookup misses liamsmith827
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 12+ messages in thread
From: liamsmith827 @ 2026-11-10  6:15 UTC (permalink / raw)
  To: netdev
  Cc: David Ahern, Ido Schimmel, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan,
	linux-kselftest, linux-kernel, liamsmith827, stable

From: liamsmith827 <liamsmith827@proton.me>

IPv4 FIB lookup maps a routing miss to -ENETUNREACH, which is also
returned when an unreachable rule is encountered. This flag prevents the
mapping, returning -ESRCH instead.

Cc: stable@vger.kernel.org
Signed-off-by: liamsmith827 <liamsmith827@proton.me>
---
 include/net/fib_rules.h | 5 +++++
 include/net/ip_fib.h    | 6 +++---
 net/ipv4/fib_rules.c    | 2 +-
 3 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/include/net/fib_rules.h b/include/net/fib_rules.h
index c6b94790fa81..bcb6809dbd10 100644
--- a/include/net/fib_rules.h
+++ b/include/net/fib_rules.h
@@ -59,6 +59,11 @@ struct fib_lookup_arg {
 	int			flags;
 #define FIB_LOOKUP_NOREF		1
 #define FIB_LOOKUP_IGNORE_LINKSTATE	2
+/* Return -ESRCH instead of mapping it to -ENETUNREACH. Only applies to IPv4
+ * lookups. When used with FIB_LOOKUP_NOREF, -ESRCH indicates the lookup
+ * exhausted all rules and tables without finding a usable route.
+ */
+#define FIB_LOOKUP_REPORT_MISS		4
 };
 
 struct fib_rules_ops {
diff --git a/include/net/ip_fib.h b/include/net/ip_fib.h
index 92f4eb8b86d7..d0b4f261585a 100644
--- a/include/net/ip_fib.h
+++ b/include/net/ip_fib.h
@@ -318,7 +318,7 @@ static inline int fib_lookup(struct net *net, const struct flowi4 *flp,
 			     struct fib_result *res, unsigned int flags)
 {
 	struct fib_table *tb;
-	int err = -ENETUNREACH;
+	int err = -EAGAIN;
 
 	rcu_read_lock();
 
@@ -327,7 +327,7 @@ static inline int fib_lookup(struct net *net, const struct flowi4 *flp,
 		err = fib_table_lookup(tb, flp, res, flags | FIB_LOOKUP_NOREF);
 
 	if (err == -EAGAIN)
-		err = -ENETUNREACH;
+		err = (flags & FIB_LOOKUP_REPORT_MISS) ? -ESRCH : -ENETUNREACH;
 
 	rcu_read_unlock();
 
@@ -398,7 +398,7 @@ static inline int fib_lookup(struct net *net, struct flowi4 *flp,
 		err = fib_table_lookup(tb, flp, res, flags);
 
 	if (err == -EAGAIN)
-		err = -ENETUNREACH;
+		err = (flags & FIB_LOOKUP_REPORT_MISS) ? -ESRCH : -ENETUNREACH;
 out:
 	rcu_read_unlock();
 
diff --git a/net/ipv4/fib_rules.c b/net/ipv4/fib_rules.c
index 060501b376a8..3d7dcb8d0865 100644
--- a/net/ipv4/fib_rules.c
+++ b/net/ipv4/fib_rules.c
@@ -101,7 +101,7 @@ int __fib_lookup(struct net *net, struct flowi4 *flp,
 		res->tclassid = 0;
 #endif
 
-	if (err == -ESRCH)
+	if (err == -ESRCH && !(flags & FIB_LOOKUP_REPORT_MISS))
 		err = -ENETUNREACH;
 
 	return err;
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH net v2 2/4] ipv4: restrict on-link fallback to lookup misses
  2026-11-10  6:15 [PATCH net v2 0/4] ipv4: restrict on-link fallback to lookup misses liamsmith827
  2026-11-10  6:15 ` [PATCH net v2 1/4] ipv4: add FIB_LOOKUP_REPORT_MISS liamsmith827
@ 2026-11-10  6:15 ` liamsmith827
  2026-11-12 17:38   ` Jakub Kicinski
  2026-11-12 21:17   ` netdev-bot+sashiko
  2026-11-10  6:15 ` [PATCH net v2 3/4] selftests: net: test restricted on-link fallback liamsmith827
                   ` (2 subsequent siblings)
  4 siblings, 2 replies; 12+ messages in thread
From: liamsmith827 @ 2026-11-10  6:15 UTC (permalink / raw)
  To: netdev
  Cc: David Ahern, Ido Schimmel, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan,
	linux-kselftest, linux-kernel, liamsmith827, stable

From: liamsmith827 <liamsmith827@proton.me>

For non-vrf unicast, the on-link fallback currently occurs regardless of
the particular error returned by fib_lookup(). As a result, routes and
rules that are supposed to restrict traffic (blackhole, prohibit,
unreachable) are unable to do so comprehensively.

A specific example of this being a problem is within Android, where the
VPN "kill switch" is implemented with prohibit rules. Any process that
can set flowi4_oif (SO_BINDTODEVICE, IP_UNICAST_IF, IP_PKTINFO, etc.) is
able to utilize this fallback, meaning unprivileged Android apps can
trivially bypass the VPN. This is particularly bad on cellular networks,
where ARP is often not needed.

Restrict the fallback to routing lookup misses.

This was originally observed on an Android device, by way of packet
capture.

Tested fcnal-test.sh in UML with CONFIG_IP_MULTIPLE_TABLES both enabled
and disabled.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Signed-off-by: liamsmith827 <liamsmith827@proton.me>
---
 net/ipv4/route.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/net/ipv4/route.c b/net/ipv4/route.c
index 3a90c8d28b94..80a2c8a3c571 100644
--- a/net/ipv4/route.c
+++ b/net/ipv4/route.c
@@ -2851,12 +2851,13 @@ struct rtable *ip_route_output_key_hash_rcu(struct net *net, struct flowi4 *fl4,
 		goto make_route;
 	}
 
-	err = fib_lookup(net, fl4, res, 0);
+	err = fib_lookup(net, fl4, res, FIB_LOOKUP_REPORT_MISS);
 	if (err) {
 		res->fi = NULL;
 		res->table = NULL;
 		if (fl4->flowi4_oif &&
-		    (ipv4_is_multicast(fl4->daddr) || !fl4->flowi4_l3mdev)) {
+		    (ipv4_is_multicast(fl4->daddr) ||
+		     (!fl4->flowi4_l3mdev && err == -ESRCH))) {
 			/* Apparently, routing tables are wrong. Assume,
 			 * that the destination is on link.
 			 *
@@ -2881,6 +2882,8 @@ struct rtable *ip_route_output_key_hash_rcu(struct net *net, struct flowi4 *fl4,
 			res->type = RTN_UNICAST;
 			goto make_route;
 		}
+		/* Reverse the mapping induced by FIB_LOOKUP_REPORT_MISS. */
+		err = err == -ESRCH ? -ENETUNREACH : err;
 		rth = ERR_PTR(err);
 		goto out;
 	}
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH net v2 3/4] selftests: net: test restricted on-link fallback
  2026-11-10  6:15 [PATCH net v2 0/4] ipv4: restrict on-link fallback to lookup misses liamsmith827
  2026-11-10  6:15 ` [PATCH net v2 1/4] ipv4: add FIB_LOOKUP_REPORT_MISS liamsmith827
  2026-11-10  6:15 ` [PATCH net v2 2/4] ipv4: restrict on-link fallback to lookup misses liamsmith827
@ 2026-11-10  6:15 ` liamsmith827
  2026-11-10  6:15 ` [PATCH net v2 4/4] ipv4: replace obsolete routing comment liamsmith827
  2026-11-12 17:13 ` [PATCH net v2 0/4] ipv4: restrict on-link fallback to lookup misses Ido Schimmel
  4 siblings, 0 replies; 12+ messages in thread
From: liamsmith827 @ 2026-11-10  6:15 UTC (permalink / raw)
  To: netdev
  Cc: David Ahern, Ido Schimmel, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan,
	linux-kselftest, linux-kernel, liamsmith827, stable

From: liamsmith827 <liamsmith827@proton.me>

Update tests now that the on-link fallback has been restricted to
routing lookup misses for non-vrf unicast.

Cc: stable@vger.kernel.org
Signed-off-by: liamsmith827 <liamsmith827@proton.me>
---
 tools/testing/selftests/net/fcnal-test.sh | 47 +++++++++++++----------
 1 file changed, 26 insertions(+), 21 deletions(-)

diff --git a/tools/testing/selftests/net/fcnal-test.sh b/tools/testing/selftests/net/fcnal-test.sh
index a50609535fed..c048d5be139d 100755
--- a/tools/testing/selftests/net/fcnal-test.sh
+++ b/tools/testing/selftests/net/fcnal-test.sh
@@ -619,17 +619,29 @@ ipv4_ping_novrf()
 	run_cmd ping -c1 -w1 -I ${NSA_DEV} ${a}
 	log_test_addr ${a} $? 0 "ping local, device bind"
 
-	# loopback addresses not reachable from device bind
-	# fails in a really weird way though because ipv4 special cases
-	# route lookups with oif set.
+	# loopback addresses not reachable from device bind, unreachable default route
 	for a in ${NSA_LO_IP} 127.0.0.1
 	do
 		log_start
 		show_hint "Fails since address on loopback device is out of device scope"
 		run_cmd ping -c1 -w1 -I ${NSA_DEV} ${a}
-		log_test_addr ${a} $? 1 "ping local, device bind"
+		log_test_addr ${a} $? 2 "ping local, device bind, unreachable default route"
 	done
 
+	# loopback addresses not reachable from device bind, route miss
+	log_start
+	setup_cmd ip route del unreachable default metric 8192
+
+	for a in ${NSA_LO_IP} 127.0.0.1
+	do
+		log_start
+		show_hint "Fails since address on loopback device is out of device scope"
+		run_cmd ping -c1 -w1 -I ${NSA_DEV} ${a}
+		log_test_addr ${a} $? 1 "ping local, device bind, route miss"
+	done
+
+	setup_cmd ip route add unreachable default metric 8192
+
 	#
 	# ip rule blocks reachability to remote address
 	#
@@ -643,10 +655,9 @@ ipv4_ping_novrf()
 	run_cmd ping -c1 -w1 ${a}
 	log_test_addr ${a} $? 2 "ping out, blocked by rule"
 
-	# NOTE: ipv4 actually allows the lookup to fail and yet still create
-	# a viable rtable if the oif (e.g., bind to device) is set, so this
-	# case succeeds despite the rule
-	# run_cmd ping -c1 -w1 -I ${NSA_DEV} ${a}
+	log_start
+	run_cmd ping -c1 -w1 -I ${NSA_DEV} ${a}
+	log_test_addr ${a} $? 2 "ping out, device bind, blocked by rule"
 
 	a=${NSA_LO_IP}
 	log_start
@@ -671,10 +682,9 @@ ipv4_ping_novrf()
 	run_cmd ping -c1 -w1 ${a}
 	log_test_addr ${a} $? 2 "ping out, blocked by route"
 
-	# NOTE: ipv4 actually allows the lookup to fail and yet still create
-	# a viable rtable if the oif (e.g., bind to device) is set, so this
-	# case succeeds despite not having a route for the address
-	# run_cmd ping -c1 -w1 -I ${NSA_DEV} ${a}
+	log_start
+	run_cmd ping -c1 -w1 -I ${NSA_DEV} ${a}
+	log_test_addr ${a} $? 2 "ping out, device bind, blocked by route"
 
 	a=${NSA_LO_IP}
 	log_start
@@ -692,10 +702,9 @@ ipv4_ping_novrf()
 	run_cmd ping -c1 -w1 ${a}
 	log_test_addr ${a} $? 2 "ping out, unreachable default route"
 
-	# NOTE: ipv4 actually allows the lookup to fail and yet still create
-	# a viable rtable if the oif (e.g., bind to device) is set, so this
-	# case succeeds despite not having a route for the address
-	# run_cmd ping -c1 -w1 -I ${NSA_DEV} ${a}
+	log_start
+	run_cmd ping -c1 -w1 -I ${NSA_DEV} ${a}
+	log_test_addr ${a} $? 2 "ping out, device bind, unreachable default route"
 }
 
 ipv4_ping_vrf()
@@ -1602,10 +1611,6 @@ ipv4_udp_novrf()
 	run_cmd nettest -D -d ${NSA_DEV} -S -r ${a} -U
 	log_test_addr ${a} $? 0 "Global server, device client via IP_UNICAST_IF, local connection, with connect()"
 
-
-	# IPv4 with device bind has really weird behavior - it overrides the
-	# fib lookup, generates an rtable and tries to send the packet. This
-	# causes failures for local traffic at different places
 	for a in ${NSA_LO_IP} 127.0.0.1
 	do
 		log_start
@@ -1613,7 +1618,7 @@ ipv4_udp_novrf()
 		run_cmd nettest -D -s &
 		wait_local_port_listen ${NSA} 12345 udp
 		run_cmd nettest -D -r ${a} -d ${NSA_DEV}
-		log_test_addr ${a} $? 2 "Global server, device client, local connection"
+		log_test_addr ${a} $? 1 "Global server, device client, local connection"
 
 		log_start
 		show_hint "Should fail since addresses on loopback are out of device scope"
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH net v2 4/4] ipv4: replace obsolete routing comment
  2026-11-10  6:15 [PATCH net v2 0/4] ipv4: restrict on-link fallback to lookup misses liamsmith827
                   ` (2 preceding siblings ...)
  2026-11-10  6:15 ` [PATCH net v2 3/4] selftests: net: test restricted on-link fallback liamsmith827
@ 2026-11-10  6:15 ` liamsmith827
  2026-11-12 21:17   ` netdev-bot+sashiko
  2026-11-12 17:13 ` [PATCH net v2 0/4] ipv4: restrict on-link fallback to lookup misses Ido Schimmel
  4 siblings, 1 reply; 12+ messages in thread
From: liamsmith827 @ 2026-11-10  6:15 UTC (permalink / raw)
  To: netdev
  Cc: David Ahern, Ido Schimmel, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan,
	linux-kselftest, linux-kernel, liamsmith827

From: liamsmith827 <liamsmith827@proton.me>

Replace an existing comment which describes obsolete fib_lookup(),
MSG_DONTROUTE and IPv6 behavior.

Signed-off-by: liamsmith827 <liamsmith827@proton.me>
---
 net/ipv4/route.c | 27 +++++++++++----------------
 1 file changed, 11 insertions(+), 16 deletions(-)

diff --git a/net/ipv4/route.c b/net/ipv4/route.c
index 80a2c8a3c571..ac94d8262b6e 100644
--- a/net/ipv4/route.c
+++ b/net/ipv4/route.c
@@ -2858,24 +2858,19 @@ struct rtable *ip_route_output_key_hash_rcu(struct net *net, struct flowi4 *fl4,
 		if (fl4->flowi4_oif &&
 		    (ipv4_is_multicast(fl4->daddr) ||
 		     (!fl4->flowi4_l3mdev && err == -ESRCH))) {
-			/* Apparently, routing tables are wrong. Assume,
-			 * that the destination is on link.
+			/* When oif is specified, fib_lookup() is called
+			 * primarily to determine if the destination is
+			 * accessible directly or through a gateway. It also
+			 * checks whether there are any traffic restrictions
+			 * (blackhole, prohibit, unreachable), or if there's a
+			 * routing lookup miss.
 			 *
-			 * WHY? DW.
-			 * Because we are allowed to send to iface
-			 * even if it has NO routes and NO assigned
-			 * addresses. When oif is specified, routing
-			 * tables are looked up with only one purpose:
-			 * to catch if destination is gatewayed, rather than
-			 * direct. Moreover, if MSG_DONTROUTE is set,
-			 * we send packet, ignoring both routing tables
-			 * and ifaddr state. --ANK
-			 *
-			 *
-			 * We could make it even if oif is unknown,
-			 * likely IPv6, but we do not.
+			 * This fallback path is always taken for multicast,
+			 * regardless of any restrictions or misses. Non-vrf
+			 * unicast does honor restrictions, but falls back if
+			 * there's a miss. The destination is assumed on-link
+			 * for both.
 			 */
-
 			if (fl4->saddr == 0)
 				fl4->saddr = inet_select_addr(dev_out, 0,
 							      RT_SCOPE_LINK);
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [PATCH net v2 0/4] ipv4: restrict on-link fallback to lookup misses
  2026-11-10  6:15 [PATCH net v2 0/4] ipv4: restrict on-link fallback to lookup misses liamsmith827
                   ` (3 preceding siblings ...)
  2026-11-10  6:15 ` [PATCH net v2 4/4] ipv4: replace obsolete routing comment liamsmith827
@ 2026-11-12 17:13 ` Ido Schimmel
  4 siblings, 0 replies; 12+ messages in thread
From: Ido Schimmel @ 2026-11-12 17:13 UTC (permalink / raw)
  To: liamsmith827
  Cc: netdev, David Ahern, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan,
	linux-kselftest, linux-kernel, liamsmith827

On Tue, Nov 10, 2026 at 06:15:32AM +0000, liamsmith827 wrote:
> From: liamsmith827 <liamsmith827@proton.me>
> 
> For non-vrf unicast, the on-link fallback currently occurs regardless of
> the particular error returned by fib_lookup(). As a result, routes and
> rules that are supposed to restrict traffic (blackhole, prohibit,
> unreachable) are unable to do so comprehensively.
> 
> A specific example of this being a problem is within Android, where the
> VPN "kill switch" is implemented with prohibit rules. Any process that
> can set flowi4_oif (SO_BINDTODEVICE, IP_UNICAST_IF, IP_PKTINFO, etc.) is
> able to utilize this fallback, meaning unprivileged Android apps can
> trivially bypass the VPN. This is particularly bad on cellular networks,
> where ARP is often not needed.
> 
> The first patch adds FIB_LOOKUP_REPORT_MISS. IPv4 FIB lookup maps a
> routing miss to -ENETUNREACH, which is also returned when an unreachable
> rule is encountered. This flag prevents the mapping, returning -ESRCH
> instead.
> 
> The second patch uses this flag to restrict the fallback to routing
> lookup misses for non-vrf unicast. The fallback is still always taken
> for multicast, regardless of any restrictions or misses.
> 
> The third patch updates tests now that the on-link fallback has been
> restricted to routing lookup misses for non-vrf unicast.
> 
> The fourth patch replaces an existing comment which describes obsolete
> fib_lookup(), MSG_DONTROUTE and IPv6 behavior.
> 
> This was originally observed on an Android device, by way of packet
> capture.
> 
> Tested fcnal-test.sh in UML with CONFIG_IP_MULTIPLE_TABLES both enabled
> and disabled.

The cover letter explains the problem and the solution, but it doesn't
list the user visible changes and doesn't explain why we think that it's
safe to make them. Something like [a].

> 
> v2:
> - Restrict the fallback to routing lookup misses, instead of only
>   honoring prohibit (-EACCES).
> - Add FIB_LOOKUP_REPORT_MISS to differentiate a routing miss from an
>   unreachable rule.
> - Split the comment update out into its own patch and remove the
>   signature.
> - Enable all of the commented out device bind tests in
>   ipv4_ping_novrf(), add a route miss test and update the UDP test
>   expectations.
> - Target net instead of net-next, add Fixes and Cc stable.

All of the patches should be targeted at net-next, without Fixes tags
and without stable tags. The patches don't fix a regression. More
context here [b].

> - Drop RFC.
> v1: https://lore.kernel.org/netdev/20261019032406.88874-1-liamsmith827@grapheneos.org/

[a]
"
When an output interface is specified (e.g., using SO_BINDTODEVICE,
IP_UNICAST_IF or IP_PKTINFO) and the FIB lookup fails, the IPv4 output
path ignores the error and constructs an on-link route via the
specified interface. According to the comment above the code, the
intent was to allow sending packets via an interface that does not have
any routes.

However, the error code is not checked, so the fallback is also taken
when the lookup hits an unreachable, blackhole or prohibit route /
rule. In other words, these routes and rules are ignored when an output
interface is specified [1].

This is a problem for Android, which implements its VPN "kill switch"
using prohibit rules, with the expectation that packets hitting such
rules are never transmitted. Unprivileged applications can bind a
socket to an interface and trivially bypass the VPN. This is especially
bad on cellular interfaces where neighbour resolution is not needed, so
packets can reach any destination and not only on-link ones.

Solve this by only taking the fallback when the lookup did not match
any route (i.e., a miss) and returning the error to the caller
otherwise [2].

It is currently impossible to distinguish between a miss and an
unreachable rule since they both return the same error (-ENETUNREACH).
Therefore, patch #1 adds a lookup flag that makes the lookup return a
different error upon a miss and patch #2 uses it to restrict the
fallback. Patch #3 adjusts the selftests and patch #4 updates the
comment above the fallback.

After the change, a packet that was previously transmitted on-link
despite hitting one of these routes / rules is no longer transmitted
and an error is returned to user space instead. It is unlikely that
anyone is relying on the current behavior:

1. It is a side effect of not checking the error code and it contradicts
ip-route(8) and ip-rule(8), which document these routes and rules as
dropping packets or generating an error.

2. These routes and rules are already honored when the output interface
is a VRF device or enslaved to one.

3. These rules are already honored by IPv6, which does not have such a
fallback.

The following is unchanged:

1. Packets can still be sent via the specified interface when the
lookup does not match any route, which is the original use case.

2. The fallback is still always taken for multicast packets. This is
required for multicast to work in a VRF, where the lookup hits the
unreachable default route in the VRF table.

[1]
 # ip link add name dummy1 up type dummy
 # ip address add 192.0.2.1/24 dev dummy1
 # ip rule add to 198.51.100.1 prohibit
 # ip route get 198.51.100.1
 RTNETLINK answers: Permission denied
 # ip route get 198.51.100.1 oif dummy1
 198.51.100.1 dev dummy1 src 192.0.2.1 uid 0
     cache

[2]
 # ip route get 198.51.100.1 oif dummy1
 RTNETLINK answers: Permission denied
"

[b] https://lore.kernel.org/netdev/20261030151629.GA85001@shredder/

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH net v2 2/4] ipv4: restrict on-link fallback to lookup misses
  2026-11-10  6:15 ` [PATCH net v2 2/4] ipv4: restrict on-link fallback to lookup misses liamsmith827
@ 2026-11-12 17:38   ` Jakub Kicinski
  2026-11-12 21:17   ` netdev-bot+sashiko
  1 sibling, 0 replies; 12+ messages in thread
From: Jakub Kicinski @ 2026-11-12 17:38 UTC (permalink / raw)
  To: liamsmith827
  Cc: netdev, David Ahern, Ido Schimmel, David S. Miller, Eric Dumazet,
	Paolo Abeni, Simon Horman, Shuah Khan, linux-kselftest,
	linux-kernel, liamsmith827, stable

On Tue, 10 Nov 2026 06:15:34 +0000 liamsmith827 wrote:
> From: liamsmith827 <liamsmith827@proton.me>

Author and SoB need to be your legal name with normal spelling.

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH net v2 1/4] ipv4: add FIB_LOOKUP_REPORT_MISS
  2026-11-10  6:15 ` [PATCH net v2 1/4] ipv4: add FIB_LOOKUP_REPORT_MISS liamsmith827
@ 2026-11-12 19:33   ` Ido Schimmel
  0 siblings, 0 replies; 12+ messages in thread
From: Ido Schimmel @ 2026-11-12 19:33 UTC (permalink / raw)
  To: liamsmith827
  Cc: netdev, David Ahern, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan,
	linux-kselftest, linux-kernel, liamsmith827, stable

On Tue, Nov 10, 2026 at 06:15:33AM +0000, liamsmith827 wrote:
> From: liamsmith827 <liamsmith827@proton.me>
> 
> IPv4 FIB lookup maps a routing miss to -ENETUNREACH, which is also
> returned when an unreachable rule is encountered. This flag prevents the
> mapping, returning -ESRCH instead.

Please include the motivation in the commit message. Something like:

"ip_route_output_key_hash_rcu() will need to distinguish between a route
miss and an unreachable rule. Currently, both result in fib_lookup()
returning the same error code (-ENETUNREACH).

Add a new FIB lookup flag that will instruct fib_lookup() to return a
different error code (-ESRCH) when it encounters a route miss."

> 
> Cc: stable@vger.kernel.org
> Signed-off-by: liamsmith827 <liamsmith827@proton.me>
> ---
>  include/net/fib_rules.h | 5 +++++
>  include/net/ip_fib.h    | 6 +++---
>  net/ipv4/fib_rules.c    | 2 +-
>  3 files changed, 9 insertions(+), 4 deletions(-)
> 
> diff --git a/include/net/fib_rules.h b/include/net/fib_rules.h
> index c6b94790fa81..bcb6809dbd10 100644
> --- a/include/net/fib_rules.h
> +++ b/include/net/fib_rules.h
> @@ -59,6 +59,11 @@ struct fib_lookup_arg {
>  	int			flags;
>  #define FIB_LOOKUP_NOREF		1
>  #define FIB_LOOKUP_IGNORE_LINKSTATE	2
> +/* Return -ESRCH instead of mapping it to -ENETUNREACH. Only applies to IPv4
> + * lookups. When used with FIB_LOOKUP_NOREF, -ESRCH indicates the lookup
> + * exhausted all rules and tables without finding a usable route.
> + */

The comment seems redundant and IPv4 always sets FIB_LOOKUP_NOREF. I
suggest removing the comment.

> +#define FIB_LOOKUP_REPORT_MISS		4
>  };
>  
>  struct fib_rules_ops {
> diff --git a/include/net/ip_fib.h b/include/net/ip_fib.h
> index 92f4eb8b86d7..d0b4f261585a 100644
> --- a/include/net/ip_fib.h
> +++ b/include/net/ip_fib.h
> @@ -318,7 +318,7 @@ static inline int fib_lookup(struct net *net, const struct flowi4 *flp,
>  			     struct fib_result *res, unsigned int flags)
>  {
>  	struct fib_table *tb;
> -	int err = -ENETUNREACH;
> +	int err = -EAGAIN;
>  
>  	rcu_read_lock();
>  
> @@ -327,7 +327,7 @@ static inline int fib_lookup(struct net *net, const struct flowi4 *flp,
>  		err = fib_table_lookup(tb, flp, res, flags | FIB_LOOKUP_NOREF);
>  
>  	if (err == -EAGAIN)
> -		err = -ENETUNREACH;
> +		err = (flags & FIB_LOOKUP_REPORT_MISS) ? -ESRCH : -ENETUNREACH;
>  
>  	rcu_read_unlock();
>  
> @@ -398,7 +398,7 @@ static inline int fib_lookup(struct net *net, struct flowi4 *flp,
>  		err = fib_table_lookup(tb, flp, res, flags);
>  
>  	if (err == -EAGAIN)
> -		err = -ENETUNREACH;
> +		err = (flags & FIB_LOOKUP_REPORT_MISS) ? -ESRCH : -ENETUNREACH;
>  out:
>  	rcu_read_unlock();
>  
> diff --git a/net/ipv4/fib_rules.c b/net/ipv4/fib_rules.c
> index 060501b376a8..3d7dcb8d0865 100644
> --- a/net/ipv4/fib_rules.c
> +++ b/net/ipv4/fib_rules.c
> @@ -101,7 +101,7 @@ int __fib_lookup(struct net *net, struct flowi4 *flp,
>  		res->tclassid = 0;
>  #endif
>  
> -	if (err == -ESRCH)
> +	if (err == -ESRCH && !(flags & FIB_LOOKUP_REPORT_MISS))
>  		err = -ENETUNREACH;
>  
>  	return err;
> -- 
> 2.47.3
> 

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH net v2 2/4] ipv4: restrict on-link fallback to lookup misses
  2026-11-10  6:15 ` [PATCH net v2 2/4] ipv4: restrict on-link fallback to lookup misses liamsmith827
  2026-11-12 17:38   ` Jakub Kicinski
@ 2026-11-12 21:17   ` netdev-bot+sashiko
  2026-11-12 23:08     ` liamsmith827
  1 sibling, 1 reply; 12+ messages in thread
From: netdev-bot+sashiko @ 2026-11-12 21:17 UTC (permalink / raw)
  To: liamsmith827
  Cc: netdev, dsahern, idosch, davem, edumazet, kuba, pabeni, horms,
	shuah, linux-kselftest, linux-kernel, liamsmith827

Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.

Critical: 0 · High: 1 · Medium: 0 · Low: 0

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [High] The fix is incomplete for route-based restrictions.

--

ipv4: restrict on-link fallback to lookup misses

This patch changes ip_route_output_key_hash_rcu() to call fib_lookup()
with FIB_LOOKUP_REPORT_MISS. For non-vrf unicast with an oif set, the
on-link fallback is then only taken on a real lookup miss (-ESRCH).
Before this, it was taken for any fib_lookup() error, so blackhole,
prohibit and unreachable restrictions could be bypassed.

> diff --git a/net/ipv4/route.c b/net/ipv4/route.c
> index 3a90c8d28b940..80a2c8a3c571f 100644
> --- a/net/ipv4/route.c
> +++ b/net/ipv4/route.c
> @@ -2851,12 +2851,13 @@ struct rtable *ip_route_output_key_hash_rcu(struct net *net, struct flowi4 *fl4,
>  		goto make_route;
>  	}
>  
> -	err = fib_lookup(net, fl4, res, 0);
> +	err = fib_lookup(net, fl4, res, FIB_LOOKUP_REPORT_MISS);
>  	if (err) {
>  		res->fi = NULL;
>  		res->table = NULL;
>  		if (fl4->flowi4_oif &&
> -		    (ipv4_is_multicast(fl4->daddr) || !fl4->flowi4_l3mdev)) {
> +		    (ipv4_is_multicast(fl4->daddr) ||
> +		     (!fl4->flowi4_l3mdev && err == -ESRCH))) {

[Severity: High]
Can a reject route that scope filtering skips still show up here as
-ESRCH and take the on-link fallback?

An unprivileged socket can set an oif with IP_UNICAST_IF, IP_PKTINFO or
SO_BINDTODEVICE. It can then send with MSG_DONTROUTE, or set
SO_DONTROUTE, which needs no capability. Either way, ip_sendmsg_scope()
returns RT_SCOPE_LINK:

include/net/ip.h:ip_sendmsg_scope() {
	if (sock_flag(&inet->sk, SOCK_LOCALROUTE) ||
	    msg->msg_flags & MSG_DONTROUTE ||
	    (ipc->opt && ipc->opt->opt.is_strictroute))
		return RT_SCOPE_LINK;
    ...
}

udp_sendmsg() passes that scope into flowi4_init_output(), so
fl4->flowi4_scope is RT_SCOPE_LINK by the time fib_lookup() runs.

In fib_table_lookup(), the scope check comes before the reject-type
error check:

net/ipv4/fib_trie.c:fib_table_lookup() {
    ...
		if (fa->fa_info->fib_scope < flp->flowi4_scope)
			continue;
		fib_alias_accessed(fa);
		err = fib_props[fa->fa_type].error;
    ...
}

By default, prohibit, blackhole and unreachable routes have
RT_SCOPE_UNIVERSE, which fib_props allows. So a link-scope lookup skips
them instead of returning -EACCES, -EINVAL or -EHOSTUNREACH.

If the destination is off-link and there is no link-scope route, the
lookup ends in -EAGAIN. Because FIB_LOOKUP_REPORT_MISS is set,
fib_lookup() turns that into -ESRCH, and so does
__fib_lookup()->fib_rules_lookup() when it runs out of rules. The new
err == -ESRCH check then passes, and a route through dev_out gets built.

The full path looks like this:

udp_sendmsg()
  ip_sendmsg_scope()               -> RT_SCOPE_LINK
  ip_route_output_flow()
    ip_route_output_key_hash_rcu()
      fib_lookup(..., FIB_LOOKUP_REPORT_MISS)
        fib_table_lookup()         reject alias skipped, -EAGAIN
      err == -ESRCH                on-link fallback taken

On NOARP or point-to-point links, like the cellular case in the commit
message, would the packet then go out the interface the prohibit route
was meant to block?

The commit message says:

    As a result, routes and rules that are supposed to restrict traffic
    (blackhole, prohibit, unreachable) are unable to do so
    comprehensively.

This looks fixed for rules. fib4_rule_action() returns -EACCES,
-ENETUNREACH or -EINVAL without any scope filtering. Routes seem to
still be bypassable with DONTROUTE plus an oif.

The comment added later by "ipv4: replace obsolete routing comment"
also says "Non-vrf unicast does honor restrictions". That commit calls
the old MSG_DONTROUTE description obsolete, but this case would still
apply to it.

Would it make sense to stop treating scope-filtered reject matches as
misses, or to handle the scope case separately?

>  			/* Apparently, routing tables are wrong. Assume,
>  			 * that the destination is on link.
>  			 *

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261110061536.10811-1-liamsmith827%40grapheneos.org

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH net v2 4/4] ipv4: replace obsolete routing comment
  2026-11-10  6:15 ` [PATCH net v2 4/4] ipv4: replace obsolete routing comment liamsmith827
@ 2026-11-12 21:17   ` netdev-bot+sashiko
  2026-11-12 23:10     ` liamsmith827
  0 siblings, 1 reply; 12+ messages in thread
From: netdev-bot+sashiko @ 2026-11-12 21:17 UTC (permalink / raw)
  To: liamsmith827
  Cc: netdev, dsahern, idosch, davem, edumazet, kuba, pabeni, horms,
	shuah, linux-kselftest, linux-kernel, liamsmith827

Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.

Low priority / nit picks for your consideration:

- [Low] The comment added to ip_route_output_key_hash_rcu()
  (net/ipv4/route.c, in the fib_lookup() error branch) is wrong for one…

--

ipv4: replace obsolete routing comment

This replaces the comment in the fib_lookup() error fallback path of
ip_route_output_key_hash_rcu(). The old comment described obsolete
fib_lookup(), MSG_DONTROUTE and IPv6 behavior. The new one describes
when the on-link fallback is taken.

> diff --git a/net/ipv4/route.c b/net/ipv4/route.c
> index 80a2c8a3c571f..ac94d8262b6ed 100644
> --- a/net/ipv4/route.c
> +++ b/net/ipv4/route.c
> @@ -2858,24 +2858,19 @@ struct rtable *ip_route_output_key_hash_rcu(struct net *net, struct flowi4 *fl4,
>  		if (fl4->flowi4_oif &&
>  		    (ipv4_is_multicast(fl4->daddr) ||
>  		     (!fl4->flowi4_l3mdev && err == -ESRCH))) {
> -			/* Apparently, routing tables are wrong. Assume,
> -			 * that the destination is on link.
> +			/* When oif is specified, fib_lookup() is called
> +			 * primarily to determine if the destination is
> +			 * accessible directly or through a gateway. It also
> +			 * checks whether there are any traffic restrictions
> +			 * (blackhole, prohibit, unreachable), or if there's a
> +			 * routing lookup miss.
>  			 *

[ ... ]

> +			 * This fallback path is always taken for multicast,
> +			 * regardless of any restrictions or misses. Non-vrf
> +			 * unicast does honor restrictions, but falls back if
> +			 * there's a miss. The destination is assumed on-link
> +			 * for both.
>  			 */

[Severity: Low]
Is "always taken for multicast" accurate when the oif is an L3 master
(VRF) device?

With a VRF configured, fib_has_custom_rules is set. That sends
fib_lookup() through __fib_lookup(), which changes the caller's flow in
place:

net/ipv4/fib_rules.c:__fib_lookup() {
    ...
	/* update flow if oif or iif point to device enslaved to l3mdev */
	l3mdev_update_flow(net, flowi4_to_flowi(flp));
    ...
}

net/l3mdev/l3mdev.c:l3mdev_update_flow() {
    ...
			if (netif_is_l3_master(dev))
				fl->flowi_oif = 0;
    ...
}

The fallback condition reads fl4->flowi4_oif after the lookup. Take a
multicast daddr that is not link-local and not IGMP (for example 239.x),
with the oif set to the VRF master. If the VRF table lookup fails, for
example on the usual unreachable default route, the oif test is false
and the lookup error is returned. The on-link fallback is not taken.

This behavior predates the patch. Could the comment mention this
exception, and note that the oif test uses the value left after
fib_lookup()?

The opening sentence has the same problem. For a VRF master oif,
fib_lookup() is used to select the VRF table and the oif match is
dropped. It is not mainly a check for whether the destination is direct
or gatewayed. Should the comment say that as well?

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261110061536.10811-1-liamsmith827%40grapheneos.org

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH net v2 2/4] ipv4: restrict on-link fallback to lookup misses
  2026-11-12 21:17   ` netdev-bot+sashiko
@ 2026-11-12 23:08     ` liamsmith827
  0 siblings, 0 replies; 12+ messages in thread
From: liamsmith827 @ 2026-11-12 23:08 UTC (permalink / raw)
  To: netdev-bot+sashiko
  Cc: netdev, dsahern, idosch, davem, edumazet, kuba, pabeni, horms,
	shuah, linux-kselftest, linux-kernel, liamsmith827

On 11/12/26 21:17, netdev-bot+sashiko@kernel.org wrote:
> - [High] The fix is incomplete for route-based restrictions.

This is not a legitimate issue. A RT_SCOPE_UNIVERSE route not matching a 
RT_SCOPE_LINK lookup is expected. Route maintainers should install the 
correct routes. An unprivileged process can influence which rules/routes 
are matched in many ways other than MSG_DONTROUTE.

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH net v2 4/4] ipv4: replace obsolete routing comment
  2026-11-12 21:17   ` netdev-bot+sashiko
@ 2026-11-12 23:10     ` liamsmith827
  0 siblings, 0 replies; 12+ messages in thread
From: liamsmith827 @ 2026-11-12 23:10 UTC (permalink / raw)
  To: netdev-bot+sashiko
  Cc: netdev, dsahern, idosch, davem, edumazet, kuba, pabeni, horms,
	shuah, linux-kselftest, linux-kernel, liamsmith827

On 11/12/26 21:17, netdev-bot+sashiko@kernel.org wrote:
> [Severity: Low]
> Is "always taken for multicast" accurate when the oif is an L3 master
> (VRF) device?

This is not a legitimate issue.

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2026-11-12 23:10 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-10  6:15 [PATCH net v2 0/4] ipv4: restrict on-link fallback to lookup misses liamsmith827
2026-11-10  6:15 ` [PATCH net v2 1/4] ipv4: add FIB_LOOKUP_REPORT_MISS liamsmith827
2026-11-12 19:33   ` Ido Schimmel
2026-11-10  6:15 ` [PATCH net v2 2/4] ipv4: restrict on-link fallback to lookup misses liamsmith827
2026-11-12 17:38   ` Jakub Kicinski
2026-11-12 21:17   ` netdev-bot+sashiko
2026-11-12 23:08     ` liamsmith827
2026-11-10  6:15 ` [PATCH net v2 3/4] selftests: net: test restricted on-link fallback liamsmith827
2026-11-10  6:15 ` [PATCH net v2 4/4] ipv4: replace obsolete routing comment liamsmith827
2026-11-12 21:17   ` netdev-bot+sashiko
2026-11-12 23:10     ` liamsmith827
2026-11-12 17:13 ` [PATCH net v2 0/4] ipv4: restrict on-link fallback to lookup misses Ido Schimmel

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox