From: Andrea Mayer <andrea@common-net.org>
To: Yuya Kusakabe <yuya.kusakabe@gmail.com>
Cc: "David S. Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
David Ahern <dsahern@kernel.org>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
Simon Horman <horms@kernel.org>,
Justin Iurman <justin.iurman@gmail.com>,
Shuah Khan <shuah@kernel.org>, Jonathan Corbet <corbet@lwn.net>,
Shuah Khan <skhan@linuxfoundation.org>,
linux-kernel@vger.kernel.org, netdev@vger.kernel.org,
linux-kselftest@vger.kernel.org, linux-doc@vger.kernel.org,
stefano.salsano@uniroma2.it, ahabdels@cisco.com,
Andrea Mayer <andrea.mayer@uniroma2.it>,
andrea@common-net.org
Subject: Re: [PATCH v2 3/7] seg6: add End.M.GTP6.E behavior
Date: Fri, 5 Jun 2026 03:20:01 +0200 [thread overview]
Message-ID: <20260605032001.2f46e6a55f69896d29da69df@common-net.org> (raw)
In-Reply-To: <20260505-seg6-mobile-v2-3-9e8022bdfdb6@gmail.com>
On Tue, 05 May 2026 01:30:13 +0900
Yuya Kusakabe <yuya.kusakabe@gmail.com> wrote:
Hi Yuya,
I do not repeat below the points from my cover letter and patch 1-2 replies
(drop reasons, OIF/VRF removal, C helper, coding style, etc.).
> Add the End.M.GTP6.E behavior (RFC 9433 Section 6.5), the IPv6 dual
> of End.M.GTP4.E. An End.M.GTP6.E SID always sits in the penultimate
> position of an SR Policy (RFC 9433 Section 6.5 Notes); when it
> becomes the active SID (segments_left == 1) the kernel pops the
> IPv6/SRH outer, recovers TEID and QFI from the 40-bit
> Args.Mob.Session field encoded in the locator-relative slice of the
> SID, and re-encapsulates the inner T-PDU in IPv6/UDP/GTP-U toward
> the next segment held in SRH[0].
>
> The flow info, traffic class and hop limit are propagated from the
> inbound IPv6 outer to the new outer (RFC 6040).
>
> When net.netfilter.nf_hooks_lwtunnel=1, the inner T-PDU traverses
> NF_INET_PRE_ROUTING between the SRv6 strip and the GTP-U push,
> mirroring End.DX4 / End.DX6.
>
> Configuration:
>
> ip -6 route add 2001:db8:e::/64 \
> encap seg6local action End.M.GTP6.E src 2001:db8:2::1 \
> dev <dev>
SEG6_LOCAL_MOBILE_SRC_ADDR (the "src" attribute) is copied verbatim into
the outer IPv6 source address. In patch 2 (End.M.GTP4.E) the same
attribute is used as a template from which bits are extracted to form
the IPv4 source address, and may be entirely unused depending on
v4_mask_len.
This UAPI overload needs revision.
>
> Link: https://www.rfc-editor.org/rfc/rfc9433.html#section-6.5
> Link: https://www.rfc-editor.org/rfc/rfc6040
> Signed-off-by: Yuya Kusakabe <yuya.kusakabe@gmail.com>
> ---
> include/uapi/linux/seg6_local.h | 2 +
> net/ipv6/seg6_local.c | 312 ++++++++++++++++
> tools/testing/selftests/net/Makefile | 1 +
> .../selftests/net/srv6_end_m_gtp6_e_test.sh | 402 +++++++++++++++++++++
> 4 files changed, 717 insertions(+)
>
> diff --git a/include/uapi/linux/seg6_local.h b/include/uapi/linux/seg6_local.h
> index b42cb526bb81..8e46ede2980d 100644
> --- a/include/uapi/linux/seg6_local.h
> +++ b/include/uapi/linux/seg6_local.h
> @@ -75,6 +75,8 @@ enum {
> SEG6_LOCAL_ACTION_END_MAP = 17,
> /* SRv6 to IPv4/GTP-U encap (RFC 9433 Section 6.6) */
> SEG6_LOCAL_ACTION_END_M_GTP4_E = 18,
> + /* SRv6 to IPv6/GTP-U encap (RFC 9433 Section 6.5) */
> + SEG6_LOCAL_ACTION_END_M_GTP6_E = 19,
>
> __SEG6_LOCAL_ACTION_MAX,
> };
> diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c
> index 4051fe89e6d1..4e5d138c3657 100644
> --- a/net/ipv6/seg6_local.c
> +++ b/net/ipv6/seg6_local.c
> + [snip]
> +static int input_action_end_m_gtp6_e_finish(struct net *net,
> + struct sock *sk,
> + struct sk_buff *skb)
> +{
> + enum skb_drop_reason reason = SKB_DROP_REASON_SEG6_MOBILE_NOMEM;
> + struct seg6_mobile_gtp6_e_cb cb = *SEG6_MOBILE_GTP6_E_CB(skb);
> + struct dst_entry *orig_dst = skb_dst(skb);
> + const struct seg6_mobile_info *minfo;
> + struct seg6_local_lwt *slwt;
> + struct ipv6hdr *new_ip6h;
> + struct udphdr *uh;
> +
> + slwt = seg6_local_lwtunnel(orig_dst->lwtstate);
> + minfo = &slwt->mobile_info;
> +
Same dst/lwtstate issue as patch 2.
> + /* Reject GSO packets that would not fit the egress IPv6/UDP/GTP-U
> + * path after our outer headers are added; the GSO segmenter cannot
> + * adjust mss across SRv6 -> GTP-U conversion. Skip the check
> + * entirely when no MTU is known on the current dst.
> + */
> + if (skb_is_gso(skb)) {
> + unsigned int ovhd = sizeof(*new_ip6h) + sizeof(*uh) +
> + sizeof(struct gtp1_header_long) +
> + sizeof(struct seg6_mobile_pdu_session_ext);
> + unsigned int mtu = dst_mtu(skb_dst(skb));
> +
> + if (mtu && (mtu <= ovhd ||
> + !skb_gso_validate_network_len(skb, mtu - ovhd))) {
> + reason = SKB_DROP_REASON_SEG6_MOBILE_MTU_EXCEEDED;
> + goto drop;
> + }
> + }
> +
> + /* Reserve worst-case headroom for the entire outer chain we are about
> + * to push: IPv6 + UDP + GTP-U long header + PDU Session extension.
> + * Subsequent skb_cow_head() calls inside seg6_mobile_push_gtpu() then
> + * become no-ops.
> + */
> + if (skb_cow_head(skb,
> + sizeof(*new_ip6h) + sizeof(*uh) +
> + sizeof(struct gtp1_header_long) +
> + sizeof(struct seg6_mobile_pdu_session_ext)))
Same ovhd scoping point as patch 2.
> + goto drop;
> +
Same missing iptunnel_handle_offloads() as patch 2.
> + if (seg6_mobile_push_gtpu(skb, cb.teid, cb.qfi, cb.pdu_type,
> + cb.pdu_type_set))
> + goto drop;
> +
> + uh = skb_push(skb, sizeof(*uh));
> + skb_reset_transport_header(skb);
> + uh->source = htons(GTP1U_PORT);
> + uh->dest = htons(GTP1U_PORT);
> + uh->len = htons(skb->len);
> +
Same fixed source port question as patch 2.
> + new_ip6h = skb_push(skb, sizeof(*new_ip6h));
> + skb_reset_network_header(skb);
> + memset(new_ip6h, 0, sizeof(*new_ip6h));
> + ip6_flow_hdr(new_ip6h, cb.tclass, cb.flowlabel);
> + new_ip6h->payload_len = htons(skb->len - sizeof(*new_ip6h));
> + new_ip6h->nexthdr = IPPROTO_UDP;
> + new_ip6h->hop_limit = cb.hop_limit;
> + new_ip6h->saddr = minfo->src_addr;
> + new_ip6h->daddr = cb.next_sid;
> +
> + /* RFC 8200 requires UDP/IPv6 checksums. Initialise the
> + * pseudo-header sum and let the stack/NIC complete it via
> + * CHECKSUM_PARTIAL so we do not pay a per-packet linear sum and
> + * we cooperate with offload.
> + */
> + skb->ip_summed = CHECKSUM_PARTIAL;
> + skb->csum_start = (unsigned char *)uh - skb->head;
> + skb->csum_offset = offsetof(struct udphdr, check);
> + uh->check = ~csum_ipv6_magic(&new_ip6h->saddr, &new_ip6h->daddr,
> + skb->len - sizeof(*new_ip6h),
> + IPPROTO_UDP, 0);
> +
udp6_set_csum() already handles the CHECKSUM_PARTIAL + pseudo-header seed
setup and also covers the GSO case. Using it would avoid open-coding this
sequence.
> + skb->protocol = htons(ETH_P_IPV6);
> + nf_reset_ct(skb);
> + skb_dst_drop(skb);
> +
> + seg6_lookup_any_nexthop(skb, &cb.next_sid, 0, false, slwt->oif);
> + return dst_input(skb);
> +
> +drop:
> + kfree_skb_reason(skb, reason);
> + return -EINVAL;
> +}
seg6_lookup_any_nexthop() already calls skb_dst_drop() internally. The
explicit call above is redundant.
> + [snip]
> +static int input_action_end_m_gtp6_e(struct sk_buff *skb,
> + struct seg6_local_lwt *slwt)
> +{
> + enum skb_drop_reason reason = SKB_DROP_REASON_SEG6_MOBILE_BAD_SID;
> + const struct seg6_mobile_info *minfo = &slwt->mobile_info;
> + struct seg6_mobile_gtp6_e_cb *cb;
> + struct in6_addr next_sid;
> + struct ipv6_sr_hdr *srh;
> + u8 hop_limit, tclass, qfi;
> + unsigned int outer_len;
> + struct ipv6hdr *ip6h;
> + int inner_nfproto;
> + __be32 flowlabel;
> + __be16 frag_off;
> + u64 args_mob;
> + u32 teid;
> + int off;
> + u8 nh;
> +
Same reverse Christmas tree issue as patch 2.
> + [snip]
> + /* RFC 6040 outer-to-outer propagation: copy DSCP+ECN (tclass) and
> + * the flow label from the SRv6 outer to the new IPv6 outer. Use
> + * ip6_flowlabel() (not ip6_flowinfo()) so the tclass byte is
> + * supplied exactly once via the @tclass argument of ip6_flow_hdr().
> + */
> + flowlabel = ip6_flowlabel(ip6h);
> + tclass = ipv6_get_dsfield(ip6h);
> + hop_limit = ip6h->hop_limit;
> +
Same RFC 6040 question as patch 2 (here also flow label).
> + /* RFC 9433 Section 6.5 upper-layer S02 mandates "Pop the IPv6
> + * header and all its extension headers". ipv6_skip_exthdr()
> + * walks every extension header (HBH/Routing/Dest-Opts/Fragment)
> + * so HBH-before-SRH and DOpts-after-SRH are handled too. The
> + * terminal next-header value also selects NFPROTO_IPV4 /
> + * NFPROTO_IPV6 for the NF_INET_PRE_ROUTING hook below.
> + */
> + nh = ip6h->nexthdr;
> + off = ipv6_skip_exthdr(skb, sizeof(*ip6h), &nh, &frag_off);
> + if (off < 0) {
> + reason = SKB_DROP_REASON_SEG6_MOBILE_BAD_INNER;
> + goto drop;
> + }
> + outer_len = off;
> +
Same BAD_INNER misuse as patch 2.
Same frag_off check missing after ipv6_skip_exthdr() as patch 2.
> + [snip]
> + /* For inner IP traffic that may traverse NF_INET_PRE_ROUTING below,
> + * pull the full inner IP header into the linear area so a netfilter
> + * hook reading skb_transport_header() does not access stale data.
> + * Non-IP inner is forwarded as-is via the GTP-U T-PDU payload.
> + */
> + if (!pskb_may_pull(skb, outer_len + ((inner_nfproto == NFPROTO_IPV4) ?
> + sizeof(struct iphdr) :
> + (inner_nfproto == NFPROTO_IPV6) ?
> + sizeof(struct ipv6hdr) : 0))) {
> + reason = SKB_DROP_REASON_SEG6_MOBILE_BAD_INNER;
> + goto drop;
> + }
> +
Same repeated ternary as patch 2.
> + [snip]
> static struct seg6_action_desc seg6_action_table[] = {
> {
> @@ -2153,6 +2431,17 @@ static struct seg6_action_desc seg6_action_table[] = {
> .build_state = seg6_mobile_v4_validate,
> },
> },
> + {
> + .action = SEG6_LOCAL_ACTION_END_M_GTP6_E,
> + .attrs = SEG6_F_ATTR(SEG6_LOCAL_MOBILE_SRC_ADDR),
> + .optattrs = SEG6_F_LOCAL_COUNTERS |
> + SEG6_F_ATTR(SEG6_LOCAL_MOBILE_PDU_TYPE) |
> + SEG6_F_ATTR(SEG6_LOCAL_OIF),
> + .input = input_action_end_m_gtp6_e,
> + .slwt_ops = {
> + .build_state = seg6_mobile_gtp6_e_validate,
> + },
> + },
> + [snip]
> +/* End.M.GTP6.E SID layout (RFC 9433 Section 6.5):
> + *
> + * | locator (route prefix) | Args.Mob.Session (40) | pad |
> + *
> + * The locator length is the route's IPv6 destination prefix length.
> + * Reject route additions whose prefix leaves no room for the 40-bit
> + * Args.Mob.Session field at setup time so the operator gets a clear
> + * error from `ip route add` instead of silent per-packet drops.
> + */
> +static int seg6_mobile_gtp6_e_validate(struct seg6_local_lwt *slwt,
> + const void *cfg,
> + struct netlink_ext_ack *extack)
> +{
> + const struct fib6_config *fib6_cfg = cfg;
> +
> + if ((unsigned int)fib6_cfg->fc_dst_len + SEG6_MOBILE_ARGS_MOB_LEN > 128) {
Nit: fc_dst_len is int in struct fib6_config (IPv6 prefix length, range
0..128); the (unsigned int) cast is not needed.
> + [snip]
Thanks,
Ciao,
Andrea
P.S. I am temporarily writing from another address due to a mail
delivery issue at my @uniroma2.it address. Please always Cc my default
andrea.mayer@uniroma2.it address on replies.
next prev parent reply other threads:[~2026-06-05 1:27 UTC|newest]
Thread overview: 25+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-04 16:30 [PATCH v2 0/7] seg6: add SRv6 Mobile User Plane (RFC 9433) behaviors Yuya Kusakabe
2026-05-04 16:30 ` [PATCH v2 1/7] seg6: add End.MAP behavior Yuya Kusakabe
2026-05-19 1:31 ` Andrea Mayer
2026-05-25 1:44 ` Yuya Kusakabe
2026-05-04 16:30 ` [PATCH v2 2/7] seg6: add End.M.GTP4.E behavior Yuya Kusakabe
2026-05-27 1:09 ` Andrea Mayer
2026-06-11 2:59 ` Yuya Kusakabe
2026-05-04 16:30 ` [PATCH v2 3/7] seg6: add End.M.GTP6.E behavior Yuya Kusakabe
2026-06-05 1:20 ` Andrea Mayer [this message]
2026-06-12 3:14 ` Yuya Kusakabe
2026-05-04 16:30 ` [PATCH v2 4/7] seg6: add End.M.GTP6.D behavior Yuya Kusakabe
2026-06-07 0:05 ` Andrea Mayer
2026-05-04 16:30 ` [PATCH v2 5/7] seg6: add End.M.GTP6.D.Di behavior Yuya Kusakabe
2026-06-07 14:01 ` Andrea Mayer
2026-05-04 16:30 ` [PATCH v2 6/7] seg6: add H.M.GTP4.D behavior Yuya Kusakabe
2026-05-04 16:30 ` [PATCH v2 7/7] Documentation: networking: add seg6_mobile guide Yuya Kusakabe
2026-05-04 23:39 ` [PATCH v2 0/7] seg6: add SRv6 Mobile User Plane (RFC 9433) behaviors Jakub Kicinski
2026-05-05 1:22 ` Yuya Kusakabe
2026-05-05 1:28 ` Jakub Kicinski
2026-05-08 1:32 ` Andrea Mayer
2026-05-09 1:53 ` Yuya Kusakabe
2026-05-10 12:02 ` Yuya Kusakabe
2026-05-16 16:25 ` Andrea Mayer
2026-05-20 3:12 ` Yuya Kusakabe
2026-06-08 0:39 ` Andrea Mayer
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260605032001.2f46e6a55f69896d29da69df@common-net.org \
--to=andrea@common-net.org \
--cc=ahabdels@cisco.com \
--cc=andrea.mayer@uniroma2.it \
--cc=corbet@lwn.net \
--cc=davem@davemloft.net \
--cc=dsahern@kernel.org \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=justin.iurman@gmail.com \
--cc=kuba@kernel.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=shuah@kernel.org \
--cc=skhan@linuxfoundation.org \
--cc=stefano.salsano@uniroma2.it \
--cc=yuya.kusakabe@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox