[PATCH net-next v2 1/2] seg6: skip the route refcount in seg6local IPv6 lookups

From: Yuya Kusakabe

Date: Sat Oct 10 2026 - 06:11:25 EST


ip6_route_input() attaches the input route to the skb without taking a
reference, relying on the RCU read-side section of the receive path.
The seg6local input actions run in that same section, either directly
from the seg6local input handler or from a netfilter okfn, yet
seg6_lookup_any_nexthop() takes and drops a route reference for every
packet.

Look the route up with RT6_LOOKUP_F_DST_NOREF, as ip6_route_input()
does, for the input and table lookups of those actions. Keep
seg6_lookup_nexthop(), which the BPF LWT helpers also call, refcounted:
BPF_PROG_TEST_RUN runs them repeatedly on the same skb and may leave
the RCU read-side section between runs. Like ip6_route_output_flags(),
it now takes the reference on top of the noref lookup.

Throughput at 0.5% packet loss of 94-byte End packets (two-segment
SRH, no payload), spread by RSS on an ixgbe 82599ES across two 2.30 GHz
Xeon E5-2650 v3 with 10 cores each, offered by TRex and binary-searched
over 10 runs of 10 s, in Mpps:

queues cores before after
1 1 0.740 0.756 (+2.2%)
10 10 on the NIC's socket 6.404 6.950 (+8.5%)
16 10 + 6 on two sockets 6.550 10.124 (+54.6%)

With the default rules, fib6_rule_lookup() tries the local table
first, and the miss takes and drops a reference on ip6_null_entry,
one route shared by every CPU. At 16 queues that cache line bounces
between the sockets, and ip6_hold_safe() and dst_release() together
take about a third of the cycles.

Assisted-by: LLM
Signed-off-by: Yuya Kusakabe <yuya.kusakabe@xxxxxxxxx>
---
net/ipv6/seg6_local.c | 54 ++++++++++++++++++++++++++++++++++++++-------------
1 file changed, 40 insertions(+), 14 deletions(-)

diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c
index 0c3d1be7f129..f23330278922 100644
--- a/net/ipv6/seg6_local.c
+++ b/net/ipv6/seg6_local.c
@@ -318,8 +318,8 @@ static struct ipv6_sr_hdr *advance_nextseg(struct sk_buff *skb,
}

static int
-seg6_lookup_any_nexthop(struct sk_buff *skb, struct in6_addr *nhaddr,
- u32 tbl_id, bool local_delivery, int oif)
+seg6_lookup_any_nexthop_noref(struct sk_buff *skb, struct in6_addr *nhaddr,
+ u32 tbl_id, bool local_delivery, int oif)
{
struct net *net = dev_net(skb->dev);
struct ipv6hdr *hdr = ipv6_hdr(skb);
@@ -342,6 +342,7 @@ seg6_lookup_any_nexthop(struct sk_buff *skb, struct in6_addr *nhaddr,
fl6.flowi6_flags = FLOWI_FLAG_KNOWN_NH;

if (!tbl_id && !oif) {
+ flags |= RT6_LOOKUP_F_DST_NOREF;
dst = ip6_route_input_lookup(net, skb->dev, &fl6, skb, flags);
} else if (tbl_id) {
struct fib6_table *table;
@@ -350,6 +351,7 @@ seg6_lookup_any_nexthop(struct sk_buff *skb, struct in6_addr *nhaddr,
if (!table)
goto out;

+ flags |= RT6_LOOKUP_F_DST_NOREF;
rt = ip6_pol_route(net, table, oif, &fl6, skb, flags);
dst = &rt->dst;
} else {
@@ -363,7 +365,7 @@ seg6_lookup_any_nexthop(struct sk_buff *skb, struct in6_addr *nhaddr,
dev_flags |= IFF_LOOPBACK;

if (dst && (dst_dev(dst)->flags & dev_flags) && !dst->error) {
- dst_release(dst);
+ ip6_rt_put_flags(dst_rt6_info(dst), flags);
dst = NULL;
}

@@ -372,17 +374,41 @@ seg6_lookup_any_nexthop(struct sk_buff *skb, struct in6_addr *nhaddr,
rt = net->ipv6.ip6_blk_hole_entry;
dst = &rt->dst;
dst_hold(dst);
+ flags &= ~RT6_LOOKUP_F_DST_NOREF;
}

skb_dst_drop(skb);
- skb_dst_set(skb, dst);
+ if ((flags & RT6_LOOKUP_F_DST_NOREF) && !dst->rt_uncached_list)
+ skb_dst_set_noref(skb, dst);
+ else
+ skb_dst_set(skb, dst);
return dst->error;
}

+static int seg6_lookup_nexthop_noref(struct sk_buff *skb,
+ struct in6_addr *nhaddr, u32 tbl_id)
+{
+ return seg6_lookup_any_nexthop_noref(skb, nhaddr, tbl_id, false, 0);
+}
+
int seg6_lookup_nexthop(struct sk_buff *skb,
struct in6_addr *nhaddr, u32 tbl_id)
{
- return seg6_lookup_any_nexthop(skb, nhaddr, tbl_id, false, 0);
+ int err;
+
+ rcu_read_lock();
+ err = seg6_lookup_nexthop_noref(skb, nhaddr, tbl_id);
+ if (!skb_dst_force(skb)) {
+ struct dst_entry *dst;
+
+ dst = &dev_net(skb->dev)->ipv6.ip6_blk_hole_entry->dst;
+ dst_hold(dst);
+ skb_dst_set(skb, dst);
+ err = dst->error;
+ }
+ rcu_read_unlock();
+
+ return err;
}

static __u8 seg6_flv_lcblock_octects(const struct seg6_flavors_info *finfo)
@@ -446,7 +472,7 @@ seg6_next_csid_advance_arg(struct sk_buff *skb,
static int input_action_end_finish(struct sk_buff *skb,
struct seg6_local_lwt *slwt)
{
- seg6_lookup_nexthop(skb, NULL, 0);
+ seg6_lookup_nexthop_noref(skb, NULL, 0);

return dst_input(skb);
}
@@ -497,7 +523,7 @@ static int end_next_csid_core(struct sk_buff *skb, struct seg6_local_lwt *slwt)
static int input_action_end_x_finish(struct sk_buff *skb,
struct seg6_local_lwt *slwt)
{
- seg6_lookup_any_nexthop(skb, &slwt->nh6, 0, false, slwt->oif);
+ seg6_lookup_any_nexthop_noref(skb, &slwt->nh6, 0, false, slwt->oif);

return dst_input(skb);
}
@@ -939,7 +965,7 @@ static int input_action_end_t(struct sk_buff *skb, struct seg6_local_lwt *slwt)
goto drop;
}

- seg6_lookup_nexthop(skb, NULL, slwt->table);
+ seg6_lookup_nexthop_noref(skb, NULL, slwt->table);

return dst_input(skb);

@@ -1023,7 +1049,7 @@ static int input_action_end_dx6_finish(struct net *net, struct sock *sk,
if (!ipv6_addr_any(&slwt->nh6))
nhaddr = &slwt->nh6;

- seg6_lookup_nexthop(skb, nhaddr, 0);
+ seg6_lookup_nexthop_noref(skb, nhaddr, 0);

return dst_input(skb);
}
@@ -1383,7 +1409,7 @@ static int input_action_end_dt6(struct sk_buff *skb,
/* note: this time we do not need to specify the table because the VRF
* takes care of selecting the correct table.
*/
- seg6_lookup_any_nexthop(skb, NULL, 0, true, 0);
+ seg6_lookup_any_nexthop_noref(skb, NULL, 0, true, 0);

return dst_input(skb);

@@ -1391,7 +1417,7 @@ static int input_action_end_dt6(struct sk_buff *skb,
#endif
skb_set_transport_header(skb, sizeof(struct ipv6hdr));

- seg6_lookup_any_nexthop(skb, NULL, slwt->table, true, 0);
+ seg6_lookup_any_nexthop_noref(skb, NULL, slwt->table, true, 0);

return dst_input(skb);

@@ -1446,7 +1472,7 @@ static int input_action_end_b6(struct sk_buff *skb, struct seg6_local_lwt *slwt)

skb_set_transport_header(skb, sizeof(struct ipv6hdr));

- seg6_lookup_nexthop(skb, NULL, 0);
+ seg6_lookup_nexthop_noref(skb, NULL, 0);

return dst_input(skb);

@@ -1482,7 +1508,7 @@ static int input_action_end_b6_encap(struct sk_buff *skb,

skb_set_transport_header(skb, sizeof(struct ipv6hdr));

- seg6_lookup_nexthop(skb, NULL, 0);
+ seg6_lookup_nexthop_noref(skb, NULL, 0);

return dst_input(skb);

@@ -1571,7 +1597,7 @@ static int input_action_end_bpf(struct sk_buff *skb,
local_unlock_nested_bh(&seg6_bpf_srh_states.bh_lock);

if (ret != BPF_REDIRECT)
- seg6_lookup_nexthop(skb, NULL, 0);
+ seg6_lookup_nexthop_noref(skb, NULL, 0);

return dst_input(skb);


--
2.50.1