[PATCH net-next v3 1/2] seg6: skip the route refcount in seg6local IPv6 lookups
flat view
WARM1d
From: Yuya Kusakabe <hidden>
Date: 2026-10-15 14:39:35
Also in:
lkml
Subsystem:
networking [general], networking [srv6], the rest · Maintainers:
"David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Andrea Mayer, Linus Torvalds
Revision v3 of 3 in this series.
Revisions (3)
- v1 [diff vs current]
- v2 [diff vs current]
- v3 current
ip6_route_input() attaches the input route to the skb without taking a reference, relying on the RCU read-side section the lookup runs in. The seg6local input actions also look the route up and hand the skb to dst_input() within one RCU read-side section: the one of the receive path, or that of the nfnetlink verdict handler for a packet reinjected from NFQUEUE. Yet seg6_lookup_any_nexthop() takes and drops a route reference for every packet. Give seg6_lookup_any_nexthop() a noref argument, and set it for the input and table lookups of those actions, as ip6_route_input() does. A packet that races with the release of its route can then be dropped without an ICMPv6 error, where ip6_hold_safe() used to send it on to the next rule or to ip6_null_entry; ip6_route_input() has the same window. seg6_lookup_nexthop(), which the BPF LWT helpers also call, keeps the refcounted lookup: BPF_PROG_TEST_RUN runs them repeatedly on the same skb and may leave the RCU read-side section between runs. 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 <redacted> --- net/ipv6/seg6_local.c | 48 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 13 deletions(-)
diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c
index 0c3d1be7f129..965263ff65d3 100644
--- a/net/ipv6/seg6_local.c
+++ b/net/ipv6/seg6_local.c@@ -319,7 +319,7 @@ 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) + u32 tbl_id, bool local_delivery, int oif, bool noref) { struct net *net = dev_net(skb->dev); struct ipv6hdr *hdr = ipv6_hdr(skb);
@@ -342,6 +342,8 @@ seg6_lookup_any_nexthop(struct sk_buff *skb, struct in6_addr *nhaddr, fl6.flowi6_flags = FLOWI_FLAG_KNOWN_NH; if (!tbl_id && !oif) { + if (noref) + 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 +352,8 @@ seg6_lookup_any_nexthop(struct sk_buff *skb, struct in6_addr *nhaddr, if (!table) goto out; + if (noref) + flags |= RT6_LOOKUP_F_DST_NOREF; rt = ip6_pol_route(net, table, oif, &fl6, skb, flags); dst = &rt->dst; } else {
@@ -363,7 +367,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 +376,35 @@ 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_any_nexthop_noref(struct sk_buff *skb, struct in6_addr *nhaddr, + u32 tbl_id, bool local_delivery, int oif) +{ + return seg6_lookup_any_nexthop(skb, nhaddr, tbl_id, local_delivery, oif, + true); +} + +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); + return seg6_lookup_any_nexthop(skb, nhaddr, tbl_id, false, 0, false); } static __u8 seg6_flv_lcblock_octects(const struct seg6_flavors_info *finfo)
@@ -446,7 +468,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 +519,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 +961,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 +1045,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 +1405,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 +1413,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 +1468,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 +1504,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 +1593,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