From nobody Sun Jul 26 01:48:10 2026 Received: from mail-pl1-f179.google.com (mail-pl1-f179.google.com [209.85.214.179]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8E81B3D6662 for ; Fri, 10 Jul 2026 08:58:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.179 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783673906; cv=none; b=hQacDurqCpeeDfr8tCHN28pOUAekS+JpLTU3lTEk3dHwXquKEt2pIPHYK2+pa+J7VAUVvKxOn7B8fQwhswmvBnjxowsMEipOUoP3sISXCSMryc/bzavrnyTKbMSCLyMdnvpkrWhM9M/qbZgMH4brYCZbY8vLRBDFa/A5WftakFo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783673906; c=relaxed/simple; bh=8b0YWFl1jOZkSbsJYVqgUyLVLRAENO/JZu2ZKO4uNSM=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Myj93C8oGetsDcTd/Sn9RZTtJ/+ZZvN75E9bs14SIvCjeSvN/fB2OVbd2/WuRDQCkwC2AeH4gpLgCy1QZIZr40SLdcdS1LBOLIiDyqKxbGKSKU1kwl7AzU9Q6nnjOH9dlEJb0TY97rhDnhbHJ8NuTQC383ZX5zBFYAthNoyEdLk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=geQfQL1T; arc=none smtp.client-ip=209.85.214.179 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="geQfQL1T" Received: by mail-pl1-f179.google.com with SMTP id d9443c01a7336-2cace7da66eso7174215ad.0 for ; Fri, 10 Jul 2026 01:58:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1783673904; x=1784278704; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=I1IJVmQhNbSpmwt5Gbasi/oFpMc5bPEoaKCJfi/iAHg=; b=geQfQL1TAB4Lz5msWMZoOvSMDZ1C9JhbKHFF5snek1ahhtZa+GU7kliBQ+GzWWwnJZ eunnhcCSlr0GJ0SZg8lN9kQV0QS4B52OY7iKgFCh8Z3w2LmyvujGcTLl3rIHYPQT7C72 N5BN5o1mJgdCWxwnd+itGpUsW+PZ31FP0PoMcgSvoht+W7/v2e+DyvhcfC8swa0gukC3 kZp8w9yDqa02HbupobsTQPIZp7TIwwDx6F7CncWSf9mq+WNZSAJfrAt+wdyQZz6FMvlV lVcLOQxauNVkBLNkX5vpijzaNTgivNUb9QSjbrlGiXulcEJXqCeuFW/q445seNhO8zLu ORNg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783673904; x=1784278704; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=I1IJVmQhNbSpmwt5Gbasi/oFpMc5bPEoaKCJfi/iAHg=; b=HPT1XPMLWXeCypsFhqUVQmucVT4/L9Do3sEaT0N282H71uIr7ZLWJ94+S7gHMmwAKT IBIR926D0hmecz584Kcxbj3ED0uvM/dfFgKo+7GScfHofCmKTIILRn6ap92x5NR8hdSY kl3KzMA+iem6vHwGp1vssPtwTDcIja0WSVzKYhEYLtZ3+vmACIOCiDqmRXR3TacULiCL wI4QosZU8t0/RKQ2xxQrIikrDRHwES/1CpFKIWSMFCZZOSWcVZuse55Ki2sMMyOLp2j2 3a+zsw8X/Sk6qm1B5M65M2yC3wxY7sdlQ6O9i4N9N3TCCPBcoj8LhtqQCPp5HsEbtNI4 a8uw== X-Gm-Message-State: AOJu0YzovdQ2/7WbeUhhYMFmo+J+F7iwM3fPx8+6WzBZHeTaWdCOR/2P h/mjbKD8W++W0ZjdyX9u6fZRMDIW2e1wiag71MaNqWSdtvIfDrksLDCX X-Gm-Gg: AfdE7cmcAFsJ8ZMH0A6HlmJRR8XLdB1Dj7QjoKcifquEgE7o7fUq+q8E9bFuzCzCY52 4i64lDE7Ljz4gQzFCvWy8Q0kD2EWAd94p2FdOz/u12btgOLgvlbzZ5SC8KUcUCXK5XY6IXnXpy0 EJRU+w0vU/sPhT3P5NHWsprRER9mNQWeBqaY4K5eAI8f9rgeM7zEPEoiyzWPIUpLC/RtYwBP1En u29SlJY+HZXA503c2rO9DSYMpglCJeG69e+ojCYRLhDQb1oFoly55FUq3Di3vIWgGMFhGSESxZN mFW+Uzo+UTtLPZnlinYi526ASpQEUq43h6FmDCHbDMJX9qhzhOoFZtdINFf6qY0VF7DAiFuEOaV d8fpJdThHeZLC6EKatOTUEZK6ZoiRq9EwIM+89CSPSNNibOYHyocUkA1R8RhCmrxI6SrBPriNS+ 0+944D5Dd5JQH5furviHihFLo= X-Received: by 2002:a17:903:1a87:b0:2cc:e620:e307 with SMTP id d9443c01a7336-2ccea3b0e71mr109533785ad.20.1783673903567; Fri, 10 Jul 2026 01:58:23 -0700 (PDT) Received: from [163.43.103.131] ([163.43.103.131]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2ccc9d3da55sm56674275ad.71.2026.07.10.01.58.20 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 10 Jul 2026 01:58:22 -0700 (PDT) From: Yuya Kusakabe Date: Fri, 10 Jul 2026 17:58:16 +0900 Subject: [PATCH RFC net-next 1/2] seg6: add support for the SRv6 End.MAP behavior Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260710-seg6-mobile-end-map-v1-1-99be02d68143@gmail.com> References: <20260710-seg6-mobile-end-map-v1-0-99be02d68143@gmail.com> In-Reply-To: <20260710-seg6-mobile-end-map-v1-0-99be02d68143@gmail.com> To: Andrea Mayer , Andrea Mayer , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , David Ahern , Ido Schimmel , Shuah Khan Cc: linux-kernel@vger.kernel.org, netdev@vger.kernel.org, linux-kselftest@vger.kernel.org, Yuya Kusakabe X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=27649; i=yuya.kusakabe@gmail.com; h=from:subject:message-id; bh=8b0YWFl1jOZkSbsJYVqgUyLVLRAENO/JZu2ZKO4uNSM=; b=owEBbQKS/ZANAwAIASrX0XUqXRtNAcsmYgBqULQoU9/9h5TQbXTOfPtR62wfWv7Q9hKmjSg2q sppwcV4kDmJAjMEAAEIAB0WIQTaB7usAfxNKMeqa6Yq19F1Kl0bTQUCalC0KAAKCRAq19F1Kl0b TdAfEACIyTU2CJMnzMzYtS/5uFlQNGgUSly6/cll2arNBW12bAkFUS8Idxy4Sz7AE7WEimWa9BC rTL5A3SK0UReVSGPpVwujsnyBuBPypAc/JAYl01NfBAa90QppeWUrEsa+uo4qOHzx/86lBSh2zN /CDopZcZtKBkKJeJJnlri0jqDsfQKkOR9HWNeKf/uh2naY8aTL6sxvFCZx+a0rDGlmZCtBEgzMd u9ohN1gEGvqfgmK8V6BjVc6VhDr+qfjBf+en9aAumg62zWdyuTWjTQMwxp8m/FJlkHk7Gc0xaYj dS3a/rHjDzDYY8xKGW3nzIhslxeZqibI7Sf2JpFrV2X6hOPYbqzcB8aKOBfvbxSMaZl4O0lEdAC WXOrazv8UrGmehApN9E046Piz5FL7lm+iucop4VPBBlGgg9HNLYcAjbfJ1cTZKJz+oZOOGOc2UL pTB0yY9ynKoCH+LHhO8L3q9z4OfogBynU+AQvaNEesHk29oU5DoLWMi8KzkhNbSPba/MdSHGnyl AvWr1Ic3x1dKM4JVTwUPBG1l+UNFjQE2ug50QQ18ATCWvxGqsckYx39Kx1craSk0pNE8dMthw5T c4o6yYmeIkdF0ZJ5J6ubATPegmsez6ggCfJzhTwdp64XO7El+KEuBRAzw/7qFqlm5HfrDPfmV6C RsFkyRwZMNdpw1A== X-Developer-Key: i=yuya.kusakabe@gmail.com; a=openpgp; fpr=DA07BBAC01FC4D28C7AA6BA62AD7D1752A5D1B4D SRv6 End.MAP is defined in RFC 9433 [1]. The SRv6 End.MAP is an SRv6 endpoint that replaces the IPv6 destination address with a configured next SID and forwards the packet via the IPv6 FIB without consuming the SRH. The SRv6 End.MAP Linux implementation is the first behavior of the SRv6 Mobile User Plane and introduces a dedicated LWTUNNEL_ENCAP_SEG6_MOBILE encap type, a CONFIG_IPV6_SEG6_MOBILE build option and a net/ipv6/seg6_mobile.c file that hosts the action dispatch table. The user-space ABI lives in include/uapi/linux/seg6_mobile.h under a SEG6_MOBILE_* namespace, kept separate from SEG6_LOCAL_* so that attributes whose semantics differ between behaviors do not overload the same UAPI table. The SRv6 End.MAP behavior can be instantiated using a command similar to the following: $ ip -6 route add 2001:db8:f::/64 encap seg6mobile action End.MAP \ nh6 2001:db8:2::e dev eth0 We introduce the "seg6mobile" extension in iproute2 in a following patch. [1] https://www.rfc-editor.org/rfc/rfc9433.html Signed-off-by: Yuya Kusakabe --- include/net/seg6.h | 8 + include/uapi/linux/lwtunnel.h | 1 + include/uapi/linux/seg6_mobile.h | 58 ++++ net/core/lwtunnel.c | 2 + net/ipv6/Kconfig | 12 + net/ipv6/Makefile | 1 + net/ipv6/seg6.c | 7 + net/ipv6/seg6_mobile.c | 725 +++++++++++++++++++++++++++++++++++= ++++ 8 files changed, 814 insertions(+) diff --git a/include/net/seg6.h b/include/net/seg6.h index 82b3fbbcbb93..789e9bcc4773 100644 --- a/include/net/seg6.h +++ b/include/net/seg6.h @@ -64,6 +64,14 @@ static inline int seg6_local_init(void) { return 0; } static inline void seg6_local_exit(void) {} #endif =20 +#ifdef CONFIG_IPV6_SEG6_MOBILE +extern int seg6_mobile_init(void); +extern void seg6_mobile_exit(void); +#else +static inline int seg6_mobile_init(void) { return 0; } +static inline void seg6_mobile_exit(void) {} +#endif + extern bool seg6_validate_srh(struct ipv6_sr_hdr *srh, int len, bool reduc= ed); extern struct ipv6_sr_hdr *seg6_get_srh(struct sk_buff *skb, int flags); extern void seg6_icmp_srh(struct sk_buff *skb, struct inet6_skb_parm *opt); diff --git a/include/uapi/linux/lwtunnel.h b/include/uapi/linux/lwtunnel.h index 229655ef792f..6e48f79c548e 100644 --- a/include/uapi/linux/lwtunnel.h +++ b/include/uapi/linux/lwtunnel.h @@ -16,6 +16,7 @@ enum lwtunnel_encap_types { LWTUNNEL_ENCAP_RPL, LWTUNNEL_ENCAP_IOAM6, LWTUNNEL_ENCAP_XFRM, + LWTUNNEL_ENCAP_SEG6_MOBILE, __LWTUNNEL_ENCAP_MAX, }; =20 diff --git a/include/uapi/linux/seg6_mobile.h b/include/uapi/linux/seg6_mob= ile.h new file mode 100644 index 000000000000..bb6fd5189138 --- /dev/null +++ b/include/uapi/linux/seg6_mobile.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * SRv6 Mobile User Plane implementation + */ +#ifndef _UAPI_LINUX_SEG6_MOBILE_H +#define _UAPI_LINUX_SEG6_MOBILE_H + +enum { + SEG6_MOBILE_UNSPEC, + SEG6_MOBILE_ACTION, + SEG6_MOBILE_NH6, + SEG6_MOBILE_COUNTERS, + __SEG6_MOBILE_MAX, +}; + +#define SEG6_MOBILE_MAX (__SEG6_MOBILE_MAX - 1) + +enum { + SEG6_MOBILE_ACTION_UNSPEC =3D 0, + /* swap IPv6 DA with the next SID, leave SRH untouched */ + SEG6_MOBILE_ACTION_END_MAP =3D 1, + + __SEG6_MOBILE_ACTION_MAX, +}; + +#define SEG6_MOBILE_ACTION_MAX (__SEG6_MOBILE_ACTION_MAX - 1) + +/* SRv6 Mobile Behavior counters are encoded as netlink attributes + * guaranteeing the correct alignment. + * Each counter is identified by a different attribute type (i.e. + * SEG6_MOBILE_CNT_PACKETS). + * + * - SEG6_MOBILE_CNT_PACKETS: identifies a counter that counts the number + * of packets that have been CORRECTLY processed by an SRv6 Behavior + * instance (i.e., packets that generate errors or are dropped are NOT + * counted). + * + * - SEG6_MOBILE_CNT_BYTES: identifies a counter that counts the total + * amount of traffic in bytes of all packets that have been CORRECTLY + * processed by an SRv6 Behavior instance (i.e., packets that generate + * errors or are dropped are NOT counted). + * + * - SEG6_MOBILE_CNT_ERRORS: identifies a counter that counts the number + * of packets that have NOT been properly processed by an SRv6 Behavior + * instance (i.e., packets that generate errors or are dropped). + */ +enum { + SEG6_MOBILE_CNT_UNSPEC, + SEG6_MOBILE_CNT_PACKETS, + SEG6_MOBILE_CNT_BYTES, + SEG6_MOBILE_CNT_ERRORS, + SEG6_MOBILE_CNT_PAD, /* pad for 64 bits values */ + __SEG6_MOBILE_CNT_MAX, +}; + +#define SEG6_MOBILE_CNT_MAX (__SEG6_MOBILE_CNT_MAX - 1) + +#endif /* _UAPI_LINUX_SEG6_MOBILE_H */ diff --git a/net/core/lwtunnel.c b/net/core/lwtunnel.c index b01a395d9a96..4476293ccb37 100644 --- a/net/core/lwtunnel.c +++ b/net/core/lwtunnel.c @@ -53,6 +53,8 @@ static const char *lwtunnel_encap_str(enum lwtunnel_encap= _types encap_type) case LWTUNNEL_ENCAP_XFRM: /* module autoload not supported for encap type */ return NULL; + case LWTUNNEL_ENCAP_SEG6_MOBILE: + return "SEG6MOBILE"; case LWTUNNEL_ENCAP_IP6: case LWTUNNEL_ENCAP_IP: case LWTUNNEL_ENCAP_NONE: diff --git a/net/ipv6/Kconfig b/net/ipv6/Kconfig index c3806c6ac96f..ba984c8b8fc7 100644 --- a/net/ipv6/Kconfig +++ b/net/ipv6/Kconfig @@ -314,6 +314,18 @@ config IPV6_SEG6_BPF depends on IPV6_SEG6_LWTUNNEL depends on IPV6 =3D y =20 +config IPV6_SEG6_MOBILE + bool "IPv6: SRv6 Mobile User Plane (RFC 9433) behaviors" + depends on IPV6_SEG6_LWTUNNEL + help + Support for the SRv6 Mobile User Plane behaviors defined by + RFC 9433. These behaviors translate between SRv6 and GTP-U, + or operate on SRv6 packets carrying mobile-user-plane traffic, + and are exposed via the LWTUNNEL_ENCAP_SEG6_MOBILE lightweight + tunnel encapsulation. + + If unsure, say N. + config IPV6_RPL_LWTUNNEL bool "IPv6: RPL Source Routing Header support" depends on IPV6 diff --git a/net/ipv6/Makefile b/net/ipv6/Makefile index 5b0cd6488021..515ac8d9d120 100644 --- a/net/ipv6/Makefile +++ b/net/ipv6/Makefile @@ -24,6 +24,7 @@ ipv6-$(CONFIG_SYN_COOKIES) +=3D syncookies.o ipv6-$(CONFIG_NETLABEL) +=3D calipso.o ipv6-$(CONFIG_IPV6_SEG6_LWTUNNEL) +=3D seg6_iptunnel.o seg6_local.o ipv6-$(CONFIG_IPV6_SEG6_HMAC) +=3D seg6_hmac.o +ipv6-$(CONFIG_IPV6_SEG6_MOBILE) +=3D seg6_mobile.o ipv6-$(CONFIG_IPV6_RPL_LWTUNNEL) +=3D rpl_iptunnel.o ipv6-$(CONFIG_IPV6_IOAM6_LWTUNNEL) +=3D ioam6_iptunnel.o =20 diff --git a/net/ipv6/seg6.c b/net/ipv6/seg6.c index 62a7eb779202..14626b15abd5 100644 --- a/net/ipv6/seg6.c +++ b/net/ipv6/seg6.c @@ -525,10 +525,16 @@ int __init seg6_init(void) if (err) goto out_unregister_iptun; =20 + err =3D seg6_mobile_init(); + if (err) + goto out_unregister_local; + pr_info("Segment Routing with IPv6\n"); =20 out: return err; +out_unregister_local: + seg6_local_exit(); out_unregister_iptun: seg6_iptunnel_exit(); out_unregister_genl: @@ -540,6 +546,7 @@ int __init seg6_init(void) =20 void seg6_exit(void) { + seg6_mobile_exit(); seg6_local_exit(); seg6_iptunnel_exit(); genl_unregister_family(&seg6_genl_family); diff --git a/net/ipv6/seg6_mobile.c b/net/ipv6/seg6_mobile.c new file mode 100644 index 000000000000..b2263c778b00 --- /dev/null +++ b/net/ipv6/seg6_mobile.c @@ -0,0 +1,725 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * SRv6 Mobile User Plane implementation + * + * Author: + * Yuya Kusakabe + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_IPV6_SEG6_HMAC +#include +#endif +#include + +#define SEG6_MOBILE_F_ATTR(i) BIT(i) +#define SEG6_F_MOBILE_COUNTERS SEG6_MOBILE_F_ATTR(SEG6_MOBILE_COUNTERS) + +struct seg6_mobile_lwt; + +struct seg6_mobile_action_desc { + int action; + unsigned long attrs; + unsigned long optattrs; + int (*input)(struct sk_buff *skb, struct seg6_mobile_lwt *slwt); +}; + +struct seg6_mobile_action_param { + int (*parse)(struct nlattr **attrs, struct seg6_mobile_lwt *slwt, + struct netlink_ext_ack *extack); + int (*put)(struct sk_buff *skb, struct seg6_mobile_lwt *slwt); + int (*cmp)(struct seg6_mobile_lwt *a, struct seg6_mobile_lwt *b); + + /* optional destroy() callback to release resources acquired in + * the corresponding parse() function. + */ + void (*destroy)(struct seg6_mobile_lwt *slwt); +}; + +struct pcpu_seg6_mobile_counters { + u64_stats_t packets; + u64_stats_t bytes; + u64_stats_t errors; + + struct u64_stats_sync syncp; +}; + +/* User-space aggregate format for the per-CPU counters. Kept private + * to the kernel; userspace receives the values through SEG6_MOBILE_CNT_* + * nested netlink attributes. + */ +struct seg6_mobile_counters { + __u64 packets; + __u64 bytes; + __u64 errors; +}; + +#define seg6_mobile_alloc_pcpu_counters(__gfp) \ + __netdev_alloc_pcpu_stats(struct pcpu_seg6_mobile_counters, \ + ((__gfp) | __GFP_ZERO)) + +struct seg6_mobile_lwt { + int action; + struct in6_addr nh6; + const struct seg6_mobile_action_desc *desc; + struct pcpu_seg6_mobile_counters __percpu *pcpu_counters; + + /* required attrs are tracked by desc->attrs; optional attrs that + * the user actually configured are tracked here so that fill_encap + * / cmp / destroy can iterate only over what was parsed. + */ + unsigned long parsed_optattrs; +}; + +static struct seg6_mobile_lwt *seg6_mobile_lwtunnel(struct lwtunnel_state = *lwt) +{ + return (struct seg6_mobile_lwt *)lwt->data; +} + +enum seg6_mobile_srh_state { + SEG6_MOBILE_SRH_ABSENT, + SEG6_MOBILE_SRH_PRESENT, + SEG6_MOBILE_SRH_MALFORMED, +}; + +/* Return the SRH if present and valid. @state separates ABSENT from + * MALFORMED so End.MAP can forward an SRH-less packet while still + * dropping a malformed one. + */ +static struct ipv6_sr_hdr * +seg6_mobile_get_and_validate_srh(struct sk_buff *skb, + enum seg6_mobile_srh_state *state) +{ + struct ipv6_sr_hdr *srh; + unsigned int srhoff =3D 0; + int hdr_proto; + int flags =3D 0; + + srh =3D seg6_get_srh(skb, 0); + if (srh) { +#ifdef CONFIG_IPV6_SEG6_HMAC + if (!seg6_hmac_validate_skb(skb)) { + *state =3D SEG6_MOBILE_SRH_MALFORMED; + return NULL; + } +#endif + *state =3D SEG6_MOBILE_SRH_PRESENT; + return srh; + } + + hdr_proto =3D ipv6_find_hdr(skb, &srhoff, IPPROTO_ROUTING, NULL, &flags); + *state =3D hdr_proto =3D=3D -ENOENT ? SEG6_MOBILE_SRH_ABSENT + : SEG6_MOBILE_SRH_MALFORMED; + return NULL; +} + +/* Length of the L4 header that must be made writable so its checksum + * field can be patched when the IPv6 DA changes. Returns 0 for L4 + * protocols whose checksum does not cover the IPv6 pseudo-header. + */ +static int seg6_mobile_l4_csum_hlen(u8 nexthdr) +{ + switch (nexthdr) { + case IPPROTO_TCP: + return sizeof(struct tcphdr); + case IPPROTO_UDP: + return sizeof(struct udphdr); + case IPPROTO_ICMPV6: + return sizeof(struct icmp6hdr); + } + return 0; +} + +/* Return a pointer to the L4 checksum field that needs the IPv6 DA + * diff applied, or NULL if patching must be skipped. Must be called + * after the L4 header has been made writable. + */ +static __sum16 *seg6_mobile_l4_csum(struct sk_buff *skb, int l4_off, + u8 nexthdr) +{ + switch (nexthdr) { + case IPPROTO_TCP: + return &((struct tcphdr *)(skb->data + l4_off))->check; + case IPPROTO_UDP: { + struct udphdr *uh =3D (struct udphdr *)(skb->data + l4_off); + + /* A zero UDPv6 checksum on a fully assembled skb signals + * "no checksum" (e.g. tunneled UDP); patching it would + * invent a spurious non-zero value. + */ + if (!uh->check && skb->ip_summed !=3D CHECKSUM_PARTIAL) + return NULL; + return &uh->check; + } + case IPPROTO_ICMPV6: + return &((struct icmp6hdr *)(skb->data + l4_off))->icmp6_cksum; + } + return NULL; +} + +/* Rewrite the IPv6 destination address with @nh. When @srh_present is + * false the packet has no routing header, so the receiver delivers it + * straight to the transport: walk any Hop-by-Hop / Destination Options / + * Fragment chain to the L4 header and, when that transport uses the IPv6 + * pseudo-header, patch its checksum by the DA diff. When a routing + * header is present the receiver first advances the SID list (SRv6 + * restores DA to segments[0]) before delivering to L4, so the original + * checksum stays valid and only skb->csum needs maintenance for + * CHECKSUM_COMPLETE skbs. + */ +static int seg6_mobile_advance_da(struct sk_buff *skb, + const struct in6_addr *nh, bool srh_present) +{ + int l4_off =3D 0, l4_hlen =3D 0; + struct in6_addr old_da; + struct ipv6hdr *ip6h; + __be16 frag_off; + u8 nexthdr =3D 0; + __sum16 *csum; + int write_len; + + if (!pskb_may_pull(skb, sizeof(*ip6h))) + return -EINVAL; + + ip6h =3D ipv6_hdr(skb); + write_len =3D sizeof(*ip6h); + + if (!srh_present) { + nexthdr =3D ip6h->nexthdr; + l4_off =3D ipv6_skip_exthdr(skb, sizeof(*ip6h), &nexthdr, + &frag_off); + if (l4_off < 0) + return -EINVAL; + + /* Non-first fragments carry no L4 header at @l4_off (the + * Fragment header reports a non-zero offset); only the + * first fragment, which holds the transport header, is + * patched. + */ + if (frag_off =3D=3D 0) + l4_hlen =3D seg6_mobile_l4_csum_hlen(nexthdr); + if (l4_hlen) + write_len =3D l4_off + l4_hlen; + } + + if (skb_ensure_writable(skb, write_len)) + return -ENOMEM; + + /* skb_ensure_writable() may change skb pointers; evaluate ip6h again */ + ip6h =3D ipv6_hdr(skb); + old_da =3D ip6h->daddr; + + csum =3D l4_hlen ? seg6_mobile_l4_csum(skb, l4_off, nexthdr) : NULL; + if (csum) { + inet_proto_csum_replace16(csum, skb, old_da.s6_addr32, + nh->s6_addr32, true); + /* A real UDPv6 checksum of 0x0000 is illegal, replace it + * with 0xffff. inet_proto_csum_replace16() keeps skb->csum + * consistent for CHECKSUM_COMPLETE because the IPv6 DA diff + * and the L4 csum diff cancel each other. + */ + if (nexthdr =3D=3D IPPROTO_UDP && !*csum) + *csum =3D CSUM_MANGLED_0; + } else if (skb->ip_summed =3D=3D CHECKSUM_COMPLETE) { + update_csum_diff16(skb, old_da.s6_addr32, (__be32 *)nh); + } + + ip6h->daddr =3D *nh; + skb_clear_hash(skb); + + return 0; +} + +/* seg6_lookup_nexthop() releases the original dst itself, so no + * skb_dst_drop() is needed before the call. + */ +static int seg6_mobile_forward(struct sk_buff *skb) +{ + seg6_lookup_nexthop(skb, NULL, 0); + return dst_input(skb); +} + +static int input_action_end_map(struct sk_buff *skb, + struct seg6_mobile_lwt *slwt) +{ + enum seg6_mobile_srh_state srh_state; + + seg6_mobile_get_and_validate_srh(skb, &srh_state); + if (srh_state =3D=3D SEG6_MOBILE_SRH_MALFORMED) + goto drop; + + if (seg6_mobile_advance_da(skb, &slwt->nh6, + srh_state =3D=3D SEG6_MOBILE_SRH_PRESENT)) + goto drop; + + return seg6_mobile_forward(skb); + +drop: + kfree_skb(skb); + return -EINVAL; +} + +static int parse_nla_nh6(struct nlattr **attrs, struct seg6_mobile_lwt *sl= wt, + struct netlink_ext_ack *extack) +{ + memcpy(&slwt->nh6, nla_data(attrs[SEG6_MOBILE_NH6]), + sizeof(struct in6_addr)); + + return 0; +} + +static int put_nla_nh6(struct sk_buff *skb, struct seg6_mobile_lwt *slwt) +{ + if (nla_put_in6_addr(skb, SEG6_MOBILE_NH6, &slwt->nh6)) + return -EMSGSIZE; + + return 0; +} + +static int cmp_nla_nh6(struct seg6_mobile_lwt *a, struct seg6_mobile_lwt *= b) +{ + return memcmp(&a->nh6, &b->nh6, sizeof(struct in6_addr)); +} + +static const struct +nla_policy seg6_mobile_counters_policy[SEG6_MOBILE_CNT_MAX + 1] =3D { + [SEG6_MOBILE_CNT_PACKETS] =3D { .type =3D NLA_U64 }, + [SEG6_MOBILE_CNT_BYTES] =3D { .type =3D NLA_U64 }, + [SEG6_MOBILE_CNT_ERRORS] =3D { .type =3D NLA_U64 }, +}; + +static int parse_nla_counters(struct nlattr **attrs, + struct seg6_mobile_lwt *slwt, + struct netlink_ext_ack *extack) +{ + struct pcpu_seg6_mobile_counters __percpu *pcounters; + struct nlattr *tb[SEG6_MOBILE_CNT_MAX + 1]; + int ret; + + ret =3D nla_parse_nested_deprecated(tb, SEG6_MOBILE_CNT_MAX, + attrs[SEG6_MOBILE_COUNTERS], + seg6_mobile_counters_policy, extack); + if (ret < 0) + return ret; + + /* basic support for SRv6 Behavior counters requires at least: + * packets, bytes and errors. + */ + if (!tb[SEG6_MOBILE_CNT_PACKETS] || !tb[SEG6_MOBILE_CNT_BYTES] || + !tb[SEG6_MOBILE_CNT_ERRORS]) + return -EINVAL; + + /* counters are always zero initialized */ + pcounters =3D seg6_mobile_alloc_pcpu_counters(GFP_KERNEL); + if (!pcounters) + return -ENOMEM; + + slwt->pcpu_counters =3D pcounters; + + return 0; +} + +static int seg6_mobile_fill_nla_counters(struct sk_buff *skb, + struct seg6_mobile_counters *counters) +{ + if (nla_put_u64_64bit(skb, SEG6_MOBILE_CNT_PACKETS, counters->packets, + SEG6_MOBILE_CNT_PAD)) + return -EMSGSIZE; + + if (nla_put_u64_64bit(skb, SEG6_MOBILE_CNT_BYTES, counters->bytes, + SEG6_MOBILE_CNT_PAD)) + return -EMSGSIZE; + + if (nla_put_u64_64bit(skb, SEG6_MOBILE_CNT_ERRORS, counters->errors, + SEG6_MOBILE_CNT_PAD)) + return -EMSGSIZE; + + return 0; +} + +static int put_nla_counters(struct sk_buff *skb, struct seg6_mobile_lwt *s= lwt) +{ + struct seg6_mobile_counters counters =3D { 0, 0, 0 }; + struct nlattr *nest; + int rc, i; + + nest =3D nla_nest_start(skb, SEG6_MOBILE_COUNTERS); + if (!nest) + return -EMSGSIZE; + + for_each_possible_cpu(i) { + struct pcpu_seg6_mobile_counters *pcounters; + u64 packets, bytes, errors; + unsigned int start; + + pcounters =3D per_cpu_ptr(slwt->pcpu_counters, i); + do { + start =3D u64_stats_fetch_begin(&pcounters->syncp); + + packets =3D u64_stats_read(&pcounters->packets); + bytes =3D u64_stats_read(&pcounters->bytes); + errors =3D u64_stats_read(&pcounters->errors); + + } while (u64_stats_fetch_retry(&pcounters->syncp, start)); + + counters.packets +=3D packets; + counters.bytes +=3D bytes; + counters.errors +=3D errors; + } + + rc =3D seg6_mobile_fill_nla_counters(skb, &counters); + if (rc < 0) { + nla_nest_cancel(skb, nest); + return rc; + } + + return nla_nest_end(skb, nest); +} + +static int cmp_nla_counters(struct seg6_mobile_lwt *a, + struct seg6_mobile_lwt *b) +{ + /* tunnels with counters enabled and disabled are different. */ + return (!!((unsigned long)a->pcpu_counters)) ^ + (!!((unsigned long)b->pcpu_counters)); +} + +static void destroy_attr_counters(struct seg6_mobile_lwt *slwt) +{ + free_percpu(slwt->pcpu_counters); +} + +static const struct seg6_mobile_action_desc seg6_mobile_action_table[] =3D= { + { + .action =3D SEG6_MOBILE_ACTION_END_MAP, + .attrs =3D SEG6_MOBILE_F_ATTR(SEG6_MOBILE_NH6), + .optattrs =3D SEG6_F_MOBILE_COUNTERS, + .input =3D input_action_end_map, + }, +}; + +static const struct seg6_mobile_action_param +seg6_mobile_action_params[SEG6_MOBILE_MAX + 1] =3D { + [SEG6_MOBILE_NH6] =3D { + .parse =3D parse_nla_nh6, + .put =3D put_nla_nh6, + .cmp =3D cmp_nla_nh6, + }, + [SEG6_MOBILE_COUNTERS] =3D { + .parse =3D parse_nla_counters, + .put =3D put_nla_counters, + .cmp =3D cmp_nla_counters, + .destroy =3D destroy_attr_counters, + }, +}; + +static const struct nla_policy +seg6_mobile_policy[SEG6_MOBILE_MAX + 1] =3D { + [SEG6_MOBILE_ACTION] =3D { .type =3D NLA_U32 }, + [SEG6_MOBILE_NH6] =3D NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)), + [SEG6_MOBILE_COUNTERS] =3D { .type =3D NLA_NESTED }, +}; + +static const struct seg6_mobile_action_desc * +seg6_mobile_get_action_desc(int action) +{ + int i; + + for (i =3D 0; i < ARRAY_SIZE(seg6_mobile_action_table); i++) { + if (seg6_mobile_action_table[i].action =3D=3D action) + return &seg6_mobile_action_table[i]; + } + + return NULL; +} + +/* call the destroy() callback (if available) for each set attribute in + * @parsed_attrs, starting from the first attribute up to the @max_parsed + * (excluded) attribute. + */ +static void __destroy_attrs(unsigned long parsed_attrs, int max_parsed, + struct seg6_mobile_lwt *slwt) +{ + const struct seg6_mobile_action_param *param; + int i; + + for (i =3D SEG6_MOBILE_ACTION + 1; i < max_parsed; i++) { + if (!(parsed_attrs & SEG6_MOBILE_F_ATTR(i))) + continue; + + param =3D &seg6_mobile_action_params[i]; + if (param->destroy) + param->destroy(slwt); + } +} + +static void destroy_attrs(struct seg6_mobile_lwt *slwt) +{ + unsigned long attrs =3D slwt->desc->attrs | slwt->parsed_optattrs; + + __destroy_attrs(attrs, SEG6_MOBILE_MAX + 1, slwt); +} + +static int seg6_mobile_parse_attrs(struct nlattr **attrs, + struct seg6_mobile_lwt *slwt, + struct netlink_ext_ack *extack) +{ + const struct seg6_mobile_action_param *param; + const struct seg6_mobile_action_desc *desc; + unsigned long parsed_optattrs =3D 0; + int i, err; + + desc =3D slwt->desc; + + if (WARN_ON_ONCE(desc->attrs & desc->optattrs)) + return -EINVAL; + + for (i =3D SEG6_MOBILE_ACTION + 1; i <=3D SEG6_MOBILE_MAX; i++) { + bool required =3D desc->attrs & SEG6_MOBILE_F_ATTR(i); + bool optional =3D desc->optattrs & SEG6_MOBILE_F_ATTR(i); + + if (!required && !optional) + continue; + + if (required && !attrs[i]) { + NL_SET_ERR_MSG_MOD(extack, + "missing required attribute"); + err =3D -EINVAL; + goto err; + } + + if (!attrs[i]) + continue; + + param =3D &seg6_mobile_action_params[i]; + err =3D param->parse(attrs, slwt, extack); + if (err < 0) + goto err; + + if (optional) + parsed_optattrs |=3D SEG6_MOBILE_F_ATTR(i); + } + + slwt->parsed_optattrs =3D parsed_optattrs; + + return 0; + +err: + __destroy_attrs(desc->attrs | parsed_optattrs, i, slwt); + return err; +} + +static bool seg6_mobile_counters_enabled(struct seg6_mobile_lwt *slwt) +{ + return slwt->parsed_optattrs & SEG6_F_MOBILE_COUNTERS; +} + +static void seg6_mobile_update_counters(struct seg6_mobile_lwt *slwt, + unsigned int len, int err) +{ + struct pcpu_seg6_mobile_counters *pcounters; + + pcounters =3D this_cpu_ptr(slwt->pcpu_counters); + u64_stats_update_begin(&pcounters->syncp); + + if (likely(!err)) { + u64_stats_inc(&pcounters->packets); + u64_stats_add(&pcounters->bytes, len); + } else { + u64_stats_inc(&pcounters->errors); + } + + u64_stats_update_end(&pcounters->syncp); +} + +static int seg6_mobile_input(struct sk_buff *skb) +{ + struct dst_entry *orig_dst =3D skb_dst(skb); + struct seg6_mobile_lwt *slwt; + unsigned int len =3D skb->len; + int rc; + + if (skb->protocol !=3D htons(ETH_P_IPV6)) { + kfree_skb(skb); + return -EINVAL; + } + + slwt =3D seg6_mobile_lwtunnel(orig_dst->lwtstate); + + rc =3D slwt->desc->input(skb, slwt); + + if (seg6_mobile_counters_enabled(slwt)) + seg6_mobile_update_counters(slwt, len, rc); + + return rc; +} + +static int seg6_mobile_build_state(struct net *net, struct nlattr *nla, + unsigned int family, const void *cfg, + struct lwtunnel_state **ts, + struct netlink_ext_ack *extack) +{ + const struct seg6_mobile_action_desc *desc; + struct nlattr *tb[SEG6_MOBILE_MAX + 1]; + struct lwtunnel_state *newts; + struct seg6_mobile_lwt *slwt; + int err; + + if (family !=3D AF_INET6) + return -EINVAL; + + err =3D nla_parse_nested_deprecated(tb, SEG6_MOBILE_MAX, nla, + seg6_mobile_policy, extack); + if (err < 0) + return err; + + if (!tb[SEG6_MOBILE_ACTION]) { + NL_SET_ERR_MSG_MOD(extack, "missing SEG6_MOBILE_ACTION"); + return -EINVAL; + } + + desc =3D seg6_mobile_get_action_desc(nla_get_u32(tb[SEG6_MOBILE_ACTION])); + if (!desc) { + NL_SET_ERR_MSG_MOD(extack, "unknown SRv6 Mobile action"); + return -EOPNOTSUPP; + } + + newts =3D lwtunnel_state_alloc(sizeof(*slwt)); + if (!newts) + return -ENOMEM; + + slwt =3D seg6_mobile_lwtunnel(newts); + slwt->action =3D desc->action; + slwt->desc =3D desc; + + err =3D seg6_mobile_parse_attrs(tb, slwt, extack); + if (err < 0) { + kfree(newts); + return err; + } + + newts->type =3D LWTUNNEL_ENCAP_SEG6_MOBILE; + newts->flags =3D LWTUNNEL_STATE_INPUT_REDIRECT; + + *ts =3D newts; + + return 0; +} + +static void seg6_mobile_destroy_state(struct lwtunnel_state *lwt) +{ + destroy_attrs(seg6_mobile_lwtunnel(lwt)); +} + +static int seg6_mobile_fill_encap(struct sk_buff *skb, + struct lwtunnel_state *lwt) +{ + struct seg6_mobile_lwt *slwt =3D seg6_mobile_lwtunnel(lwt); + const struct seg6_mobile_action_param *param; + unsigned long attrs; + int i, err; + + if (nla_put_u32(skb, SEG6_MOBILE_ACTION, slwt->action)) + return -EMSGSIZE; + + attrs =3D slwt->desc->attrs | slwt->parsed_optattrs; + for (i =3D SEG6_MOBILE_ACTION + 1; i <=3D SEG6_MOBILE_MAX; i++) { + if (!(attrs & SEG6_MOBILE_F_ATTR(i))) + continue; + + param =3D &seg6_mobile_action_params[i]; + err =3D param->put(skb, slwt); + if (err < 0) + return err; + } + + return 0; +} + +static int seg6_mobile_get_encap_size(struct lwtunnel_state *lwt) +{ + struct seg6_mobile_lwt *slwt =3D seg6_mobile_lwtunnel(lwt); + unsigned long attrs; + int nlsize; + + nlsize =3D nla_total_size(sizeof(u32)); /* SEG6_MOBILE_ACTION */ + + attrs =3D slwt->desc->attrs | slwt->parsed_optattrs; + if (attrs & SEG6_MOBILE_F_ATTR(SEG6_MOBILE_NH6)) + nlsize +=3D nla_total_size(sizeof(struct in6_addr)); + + if (attrs & SEG6_F_MOBILE_COUNTERS) + nlsize +=3D nla_total_size(0) + /* nest SEG6_MOBILE_COUNTERS */ + /* SEG6_MOBILE_CNT_PACKETS */ + nla_total_size_64bit(sizeof(__u64)) + + /* SEG6_MOBILE_CNT_BYTES */ + nla_total_size_64bit(sizeof(__u64)) + + /* SEG6_MOBILE_CNT_ERRORS */ + nla_total_size_64bit(sizeof(__u64)); + + return nlsize; +} + +static int seg6_mobile_cmp_encap(struct lwtunnel_state *a, + struct lwtunnel_state *b) +{ + struct seg6_mobile_lwt *slwt_a =3D seg6_mobile_lwtunnel(a); + struct seg6_mobile_lwt *slwt_b =3D seg6_mobile_lwtunnel(b); + const struct seg6_mobile_action_param *param; + unsigned long attrs_a, attrs_b; + int i; + + if (slwt_a->action !=3D slwt_b->action) + return 1; + + attrs_a =3D slwt_a->desc->attrs | slwt_a->parsed_optattrs; + attrs_b =3D slwt_b->desc->attrs | slwt_b->parsed_optattrs; + + if (attrs_a !=3D attrs_b) + return 1; + + for (i =3D SEG6_MOBILE_ACTION + 1; i <=3D SEG6_MOBILE_MAX; i++) { + if (!(attrs_a & SEG6_MOBILE_F_ATTR(i))) + continue; + + param =3D &seg6_mobile_action_params[i]; + if (param->cmp(slwt_a, slwt_b)) + return 1; + } + + return 0; +} + +static const struct lwtunnel_encap_ops seg6_mobile_ops =3D { + .build_state =3D seg6_mobile_build_state, + .destroy_state =3D seg6_mobile_destroy_state, + .input =3D seg6_mobile_input, + .fill_encap =3D seg6_mobile_fill_encap, + .get_encap_size =3D seg6_mobile_get_encap_size, + .cmp_encap =3D seg6_mobile_cmp_encap, + .owner =3D THIS_MODULE, +}; + +int __init seg6_mobile_init(void) +{ + BUILD_BUG_ON(SEG6_MOBILE_MAX + 1 > BITS_PER_TYPE(unsigned long)); + + return lwtunnel_encap_add_ops(&seg6_mobile_ops, + LWTUNNEL_ENCAP_SEG6_MOBILE); +} + +void seg6_mobile_exit(void) +{ + lwtunnel_encap_del_ops(&seg6_mobile_ops, LWTUNNEL_ENCAP_SEG6_MOBILE); +} --=20 2.50.1 From nobody Sun Jul 26 01:48:10 2026 Received: from mail-pl1-f174.google.com (mail-pl1-f174.google.com [209.85.214.174]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9EC283CB8EF for ; Fri, 10 Jul 2026 08:58:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783673909; cv=none; b=Shh9l4f1XO+XpFioDy0JUW2M6CiMgU26S3sH+iJTZcutbLOFIFSQ1bpf+gFBEhcTCGGaXJAcNxSVchklkMTuBPj2C3PV+PEJ4zXqvDeOwPG3PaAf+ZKKxFMypZeDHmqFzSQ1T4etgDM8RjoBvvSjtAVJYwtAOCQ+ZO39UHfwGd4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783673909; c=relaxed/simple; bh=ndX6aQet6+bY8N8rkyjG+9hY1rp5uolLAQLvAF+KO2Y=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=tLYrG8VtXHGylQz2suZp4G/1kjg5qxB7LDqJcPXYazVZCQiCIGDBVxPtsvNY47U5JP4Je/EK/4w6JPmkPocAbI4QhFddLAG2okL4psu0WoEZ0m4LPCkbh40HwofhuqBMk+I31kL3R9rjKxw1Y99Umx2qbobXnTnzEJ5gbiYYuik= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=k7uVzNcq; arc=none smtp.client-ip=209.85.214.174 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="k7uVzNcq" Received: by mail-pl1-f174.google.com with SMTP id d9443c01a7336-2cc8e87f29bso4899545ad.2 for ; Fri, 10 Jul 2026 01:58:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1783673907; x=1784278707; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=6OkQKVlwvD+WNl1QpiIUbf+i0jakbMBoOvXvnGzvw6Q=; b=k7uVzNcqrqsRAahz8VcgJGwHWzsRnrde6H2CIDd3QaXn/8Amm0mGopcEppgRpJJMw1 yGCmO89bqX5CO3NP8yQL+6833K62wYli4ewRa3G/9D6MfUnzrFL1/0bzqMSbYiDKqaRg UNjwlwfYdQM/cxPIXbruYxtdpZLajsIZQ4r+DgSd8DdNCjydvl0S5iSu3ZoAdfSWPdzL v98zUmF982eLhoBwFGb9gIk90j6YF9ndSeMiCCfkyz5zCT7BMbCHMtcwxPy96v50Gbjy wdmql52iwmhP2KKgmWS6K76z1pYYXfl8C6hkropGNN9gokn0lRJ9C7LUZo+cTXljUyXT 6OSw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783673907; x=1784278707; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=6OkQKVlwvD+WNl1QpiIUbf+i0jakbMBoOvXvnGzvw6Q=; b=BxwfZ8ChL5lpLa4kQVGFj3I+0XH9hJF6ddRt7oeCTAD6yb8OjBlwJwuBIqWzYCaajk 8WF03hv5ng9AirFkfy4S9MbbxhF42rL2ywWQeLksnInH/EiC7o9bRQJanRlxPGvwRCx1 VzTAO82lgY2OSl0fRh0uOiXY2VSvJq8IVYFkCsjrCfUKOQ7AvrVcLuPWXMZhl0JTjbYF 4f9Q32ez2E+zJk4FnqY2w8NPNNx673v4e32ExFmsp4ulO0qgql2MStqkha20+1XFEhN4 PDDiOoNzohrvq47imjtmZOjA5qnn3I8Q8R60MjVAc33tWQzrhM0hpAgzXmwb76zHPexh ipOA== X-Gm-Message-State: AOJu0Yx/Tmn1JDeEQAvYT4sSYfjpmu4DZ0bemr5PRijTBRn85d7ZoXce V+fxtZYsafIhhR3jorA6s3nX7K57sQ6/+o+7o8cGHIvoIkiMSRhKzn3k X-Gm-Gg: AfdE7clTqx0yPfdq4AE1q7aysGjDRPDh/wUTeunCz90KKRKFYWXQfIlXckq7LACcjyM gbrtpIMm3+Dbtt/miiyBRXdYlfn8Bgs1uVLHTctpoyf9P+Mvd/A7ufZixvAVMPnwb1+Tz6f+Wf9 wj8EZs7BQl8w2PQT42OqKIXhKr1yay7y7JPCfGy2ne9a3EB8TsVhGXb9Pu64T7far7PSLBPIyNk HKXnK7aPKiz95dht16n1G5qcPNQpl4YKOsqh9FioOpvejYC4SprmRahYeY62zwupKshsBXxFmkt xh5cTEVMyq4FNddOUQQ5zuRazoi0WcRjA3nBzx1w3KKoMPeOzfZZgKlo14cR0MiSA+TawQH4EAr mYnVqJgbjUzoAN1UPpZHhA+yGKBvbUyHh05pmz7dKOXD8ypsF2MpRSJI0hVGTWb57zwV6zuMi+B BWipJqdaW8DI7EDudGLRoFTRA= X-Received: by 2002:a17:903:1a08:b0:2cc:fe03:dc75 with SMTP id d9443c01a7336-2ccfe03ebf4mr81373155ad.27.1783673906628; Fri, 10 Jul 2026 01:58:26 -0700 (PDT) Received: from [163.43.103.131] ([163.43.103.131]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2ccc9d3da55sm56674275ad.71.2026.07.10.01.58.23 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 10 Jul 2026 01:58:25 -0700 (PDT) From: Yuya Kusakabe Date: Fri, 10 Jul 2026 17:58:17 +0900 Subject: [PATCH RFC net-next 2/2] selftests: seg6: add selftest for End.MAP behavior Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260710-seg6-mobile-end-map-v1-2-99be02d68143@gmail.com> References: <20260710-seg6-mobile-end-map-v1-0-99be02d68143@gmail.com> In-Reply-To: <20260710-seg6-mobile-end-map-v1-0-99be02d68143@gmail.com> To: Andrea Mayer , Andrea Mayer , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , David Ahern , Ido Schimmel , Shuah Khan Cc: linux-kernel@vger.kernel.org, netdev@vger.kernel.org, linux-kselftest@vger.kernel.org, Yuya Kusakabe X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=17841; i=yuya.kusakabe@gmail.com; h=from:subject:message-id; bh=ndX6aQet6+bY8N8rkyjG+9hY1rp5uolLAQLvAF+KO2Y=; b=owEBbQKS/ZANAwAIASrX0XUqXRtNAcsmYgBqULQp+ZXxPG7wcdcD6qqIPSvsWyE/KR5fJ7w0l xm0mek0griJAjMEAAEIAB0WIQTaB7usAfxNKMeqa6Yq19F1Kl0bTQUCalC0KQAKCRAq19F1Kl0b Tcm0D/9PJmh78M/HePx1+5k0Y9uvjYbT/gFq70tfBunZUVoi8K1fdPUt9hmcwnX7Z49sY41FIlA eQRgn92t6ao4CHh6YLoTDzkNi+rptb2FvhgSJeobRGsmSRPCOEeXRC4mwOKnLisbS6ULsFAf9I+ Q7tGK9+TSE4ZIm7K56QsVQtTYPcSEZl58kFzD9Ba/8lxWf/XCS+WLxe3O1czn8VvgyKRUqkd9z0 1VpnswipX7nmey58LJ6RPz7BLbu+mycysllIpf0qyHITPajvquUCAVBTOr579su0x7NxbWKbx+N QtNryU8OPTLy2yCv0kaix8OXaOsZjlOtCV4xmJ63khahdEIAhwfnZy7O7lyPRkBTiqv3VcGjZYF PVBE27QK/ktAgPs+DOW6MXhgUMFxgrF7BfdponqVAC/MJNxrULgYNm6GeLUx6pk2HPuFr47DPJa ATbgXVvhCSTw4HbcWmxEIvnI1H42gQOio1EtUyYjODPY81Nj96MFHGR5WTuyYPzpXW7tHdzDDRQ 2nfNmv1n5qqIMx73Opm421CfxcJAaTUoOHJnWg74/l6aegJ8E9iZYysK88RnGKRYYHc+TASbSOw iP+zAdh+7Yl/6MTwPsSD7w5deOZvSpTO6rs0E0M1+t2lVkHd2u/CY5xeKN0AfNxd/WLKZ3ZXGTY QSWrOkp7rX7dXJA== X-Developer-Key: i=yuya.kusakabe@gmail.com; a=openpgp; fpr=DA07BBAC01FC4D28C7AA6BA62AD7D1752A5D1B4D Add tools/testing/selftests/net/srv6_end_map_test.sh covering the End.MAP behavior over a three-node topology (the script header documents the address plan). Five cases are exercised: SRH-absent, SRH-present (H.Encaps) and SRH-inline (H.Insert) forwarding verified by ICMPv6 echo with Icmp6InCsumErrors asserted to stay zero, a malformed routing header verified to be dropped via the route's own errors counter, and a Hop Limit expiring at the End.MAP node answered with an ICMPv6 Time Exceeded. The original and replacement addresses are chosen with different 16-bit word sums so a transport-checksum regression cannot cancel out. The malformed packet is crafted by a small C helper (srv6_mobile_send) added to TEST_GEN_FILES; the test SKIPs when iproute2 lacks the seg6mobile keyword. Signed-off-by: Yuya Kusakabe --- tools/testing/selftests/net/Makefile | 2 + tools/testing/selftests/net/srv6_end_map_test.sh | 360 +++++++++++++++++++= ++++ tools/testing/selftests/net/srv6_mobile_send.c | 139 +++++++++ 3 files changed, 501 insertions(+) diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests= /net/Makefile index 708d960ae07d..9acdd4187762 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -91,6 +91,7 @@ TEST_PROGS :=3D \ srv6_end_dx4_netfilter_test.sh \ srv6_end_dx6_netfilter_test.sh \ srv6_end_flavors_test.sh \ + srv6_end_map_test.sh \ srv6_end_next_csid_l3vpn_test.sh \ srv6_end_x_next_csid_l3vpn_test.sh \ srv6_hencap_red_l3vpn_test.sh \ @@ -161,6 +162,7 @@ TEST_GEN_FILES :=3D \ so_netns_cookie \ so_rcv_listener \ socket \ + srv6_mobile_send \ stress_reuseport_listen \ tcp_fastopen_backup_key \ tcp_inq \ diff --git a/tools/testing/selftests/net/srv6_end_map_test.sh b/tools/testi= ng/selftests/net/srv6_end_map_test.sh new file mode 100755 index 000000000000..2a45997b6728 --- /dev/null +++ b/tools/testing/selftests/net/srv6_end_map_test.sh @@ -0,0 +1,360 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Selftest for the SRv6 End.MAP behavior (RFC 9433). +# +# +------+ 2001:db8:1::/64 +------+ 2001:db8:2::/64 +------+ +# | rt-1 | --------------------- | rt-2 | --------------------- | rt-3 | +# +------+ veth1 +------+ veth2 +------+ +# (End.MAP) +# +# rt-2 holds the End.MAP route for 2001:db8:f::/64 that replaces the +# IPv6 destination with 2001:db8:3::3 (an address on rt-3's loopback, +# also used as the final SRv6 segment in the SRH-present scenarios). +# +# The original destination 2001:db8:f::1 and the replacement +# 2001:db8:3::3 have different 16-bit word sums, so any regression in +# the transport-checksum diff update would corrupt the ICMPv6 +# checksum and bump Icmp6InCsumErrors -- this test asserts that the +# counter stays at zero across the run. +# +# Five cases are exercised: +# +# 1. SRH absent -- plain ICMPv6 echo to the End.MAP SID. +# 2. SRH present -- the destination is reached through an +# H.Encaps wrapper that carries an SRH with +# two segments; End.MAP must leave the SRH +# structurally intact. +# 3. SRH inline -- the destination is reached through an +# H.Insert wrapper that inserts an SRH whose +# first hop is the End.MAP SID; End.MAP must +# NOT patch the L4 checksum, because the +# receiver's SRv6 processing restores the +# destination from segments[0] before the +# ICMPv6 handler verifies it. +# 4. SRH malformed -- a C helper sends a packet whose Routing +# Header type is not 4; End.MAP must drop it. +# The behavior's own errors counter binds the +# assertion to the drop. +# 5. Hop Limit -- an echo whose Hop Limit is 1 on arrival at +# the End.MAP node must yield an ICMPv6 Time +# Exceeded from that node, confirming Hop Limit +# handling is delegated to the ip6_forward path. + +source lib.sh + +readonly PING_TIMEOUT_SEC=3D4 +readonly END_MAP_PREFIX=3D"2001:db8:f::/64" +readonly END_MAP_SID=3D"2001:db8:f::1" +readonly RT3_SID=3D"2001:db8:3::3" +HELPER_DIR=3D$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +readonly HELPER_DIR +readonly HELPER=3D"${HELPER_DIR}/srv6_mobile_send" + +ret=3D0 +nsuccess=3D0 +nfail=3D0 + +PAUSE_ON_FAIL=3D${PAUSE_ON_FAIL:=3Dno} + +log_test() +{ + local rc=3D$1 + local expected=3D$2 + local msg=3D"$3" + + if [ "${rc}" -eq "${expected}" ]; then + nsuccess=3D$((nsuccess + 1)) + printf "\n TEST: %-60s [ OK ]\n" "${msg}" + else + ret=3D1 + nfail=3D$((nfail + 1)) + printf "\n TEST: %-60s [FAIL]\n" "${msg}" + if [ "${PAUSE_ON_FAIL}" =3D "yes" ]; then + echo + echo "hit enter to continue, 'q' to quit" + read -r a + [ "$a" =3D "q" ] && exit 1 + fi + fi +} + +print_log_test_results() +{ + printf "\nTests passed: %3d\n" "${nsuccess}" + printf "Tests failed: %3d\n" "${nfail}" +} + +cleanup() +{ + cleanup_all_ns +} + +trap cleanup EXIT + +check_dependencies() +{ + if [ "$(id -u)" -ne 0 ]; then + echo "SKIP: need root privileges" + exit "${ksft_skip}" + fi + + for cmd in ip ping; do + if ! command -v "$cmd" >/dev/null; then + echo "SKIP: ${cmd} is required" + exit "${ksft_skip}" + fi + done + + if [ ! -x "${HELPER}" ]; then + echo "SKIP: ${HELPER} not built" + exit "${ksft_skip}" + fi + + if ! ip route help 2>&1 | grep -qF "seg6mobile"; then + echo "SKIP: iproute2 lacks seg6mobile support" + exit "${ksft_skip}" + fi + + if ! ip route help 2>&1 | grep -qF "End.MAP"; then + echo "SKIP: iproute2 lacks End.MAP action" + exit "${ksft_skip}" + fi +} + +setup() +{ + setup_ns rt1 rt2 rt3 + + ip -n "$rt1" link set lo up + ip -n "$rt2" link set lo up + ip -n "$rt3" link set lo up + + ip link add veth1 netns "$rt1" \ + type veth peer name veth1-rt2 netns "$rt2" + ip link add veth2 netns "$rt2" \ + type veth peer name veth2-rt3 netns "$rt3" + + ip -n "$rt1" addr add 2001:db8:1::1/64 dev veth1 nodad + ip -n "$rt2" addr add 2001:db8:1::2/64 dev veth1-rt2 nodad + ip -n "$rt2" addr add 2001:db8:2::1/64 dev veth2 nodad + ip -n "$rt3" addr add 2001:db8:2::2/64 dev veth2-rt3 nodad + # rt-3 also owns the End.MAP replacement SID / SRH endpoint. + ip -n "$rt3" addr add "$RT3_SID/128" dev lo nodad + + ip -n "$rt1" link set veth1 up + ip -n "$rt2" link set veth1-rt2 up + ip -n "$rt2" link set veth2 up + ip -n "$rt3" link set veth2-rt3 up + + ip netns exec "$rt2" sysctl -wq net.ipv6.conf.all.forwarding=3D1 + + # rt-3 must accept SRv6 packets so ipv6_srh_rcv lets the + # extension header chain through to local delivery. + ip netns exec "$rt3" sysctl -wq net.ipv6.conf.all.seg6_enabled=3D1 + ip netns exec "$rt3" \ + sysctl -wq net.ipv6.conf.veth2-rt3.seg6_enabled=3D1 + ip netns exec "$rt3" sysctl -wq net.ipv6.conf.lo.seg6_enabled=3D1 + + # Disable HW checksum offload so the kernel software checksum + # path runs unconditionally and any csum bug surfaces. + ip netns exec "$rt1" ethtool -K veth1 tx off rx off 2>/dev/null + ip netns exec "$rt2" ethtool -K veth1-rt2 tx off rx off \ + 2>/dev/null + ip netns exec "$rt2" ethtool -K veth2 tx off rx off 2>/dev/null + ip netns exec "$rt3" ethtool -K veth2-rt3 tx off rx off \ + 2>/dev/null + + # rt-1: route the End.MAP locator into rt-2. + ip -n "$rt1" -6 route add "$END_MAP_PREFIX" via 2001:db8:1::2 + + # rt-1: a separate H.Encaps route for the SRH-present scenario, + # wrapping the inner ICMPv6 echo in an outer IPv6+SRH carrying + # [End.MAP_SID, RT3_SID]. + ip -n "$rt1" -6 route add "$RT3_SID/128" via 2001:db8:1::2 \ + encap seg6 mode encap \ + segs "$END_MAP_SID","$RT3_SID" \ + dev veth1 + + # rt-2: End.MAP -- swap DA from the End.MAP SID to RT3_SID + # (an address on rt-3) and forward via the IPv6 FIB. "count" + # enables the per-behavior counters the malformed-SRH test reads. + ip -n "$rt2" -6 route add "$END_MAP_PREFIX" \ + encap seg6mobile action End.MAP nh6 "$RT3_SID" count \ + dev veth2 + + # rt-2: reach RT3_SID (on rt-3's loopback) through the + # directly connected neighbour 2001:db8:2::2. + ip -n "$rt2" -6 route add "$RT3_SID/128" via 2001:db8:2::2 + + # rt-3: return route for the ICMPv6 echo reply. + ip -n "$rt3" -6 route add 2001:db8:1::/64 via 2001:db8:2::1 +} + +read_nstat_counter() +{ + local ns=3D$1 + local name=3D$2 + + # nstat -az reports a counter that has never incremented as 0, + # which is what we rely on for a clean before/after delta. + ip netns exec "$ns" nstat -az "$name" \ + | awk -v n=3D"$name" '$1 =3D=3D n {print $2}' +} + +read_route_errors() +{ + # The End.MAP route carries "count", so its errors counter + # increments once for every packet the behavior drops. Reading it + # binds the negative test to the drop itself rather than to any + # unrelated loss on the path to rt-3. + ip -n "$rt2" -j -s -6 route show "$END_MAP_PREFIX" \ + | grep -oE '"errors":[0-9]+' | grep -oE '[0-9]+' +} + +# Test 1: SRH absent. +test_srh_absent() +{ + local before after rc=3D0 + + before=3D$(read_nstat_counter "$rt3" Icmp6InCsumErrors) + + if ! ip netns exec "$rt1" \ + ping -6 -c 1 -W "$PING_TIMEOUT_SEC" "$END_MAP_SID" \ + >/dev/null 2>&1; then + rc=3D1 + fi + + if [ "$rc" -eq 0 ]; then + after=3D$(read_nstat_counter "$rt3" Icmp6InCsumErrors) + [ "$before" !=3D "$after" ] && rc=3D1 + fi + + log_test "$rc" 0 "End.MAP forwards an ICMPv6 echo without an SRH" +} + +# Test 2: SRH present (H.Encaps from rt-1). +# +# The packet rt-1 emits carries an SRH whose only intermediate +# segment is the End.MAP SID; the final segment is RT3_SID. End.MAP +# rewrites the outer destination from END_MAP_SID to RT3_SID +# without touching the SRH, so on rt-3 the SRH processing advances +# to segments[0] =3D RT3_SID (local) and the encapsulated inner +# packet is decapsulated and replied to. A regression that mangles +# the SRH (e.g. flipping bits in segments_left) would break the chain +# and the ping would not return. +test_srh_present() +{ + local before after rc=3D0 + + before=3D$(read_nstat_counter "$rt3" Icmp6InCsumErrors) + + if ! ip netns exec "$rt1" \ + ping -6 -c 1 -W "$PING_TIMEOUT_SEC" "$RT3_SID" \ + >/dev/null 2>&1; then + rc=3D1 + fi + + if [ "$rc" -eq 0 ]; then + after=3D$(read_nstat_counter "$rt3" Icmp6InCsumErrors) + [ "$before" !=3D "$after" ] && rc=3D1 + fi + + log_test "$rc" 0 "End.MAP preserves an SRH carried by H.Encaps" +} + +# Test 3: SRH inserted in-place by H.Insert (mode inline). +# +# rt-1 inserts an SRH between the IPv6 header and the L4 payload; +# segments[0] is the original DA (2001:db8:2::2), segments[1] is the +# End.MAP SID. End.MAP rewrites the outer DA to RT3_SID but must +# NOT patch the L4 checksum, because the kernel's standard SRv6 +# processing on rt-3 will decrement Segments Left to 0 and restore +# the destination to segments[0] before the ICMPv6 handler verifies +# the checksum against the original pseudo-header. Over-patching the +# checksum would corrupt it and bump Icmp6InCsumErrors. +test_srh_inline() +{ + local before after rc=3D0 + + ip -n "$rt1" -6 route add 2001:db8:2::2/128 via 2001:db8:1::2 \ + encap seg6 mode inline segs "$END_MAP_SID" \ + dev veth1 + + before=3D$(read_nstat_counter "$rt3" Icmp6InCsumErrors) + + if ! ip netns exec "$rt1" \ + ping -6 -c 1 -W "$PING_TIMEOUT_SEC" 2001:db8:2::2 \ + >/dev/null 2>&1; then + rc=3D1 + fi + + if [ "$rc" -eq 0 ]; then + after=3D$(read_nstat_counter "$rt3" Icmp6InCsumErrors) + [ "$before" !=3D "$after" ] && rc=3D1 + fi + + log_test "$rc" 0 "End.MAP preserves L4 csum across mode inline SRH" +} + +# Test 4: SRH malformed (negative test). +# +# A C helper crafts an IPv6 packet whose Routing Header type is 0 +# rather than 4 (SRH), then sends it from rt-1 toward the End.MAP +# SID. End.MAP's seg6_mobile_get_and_validate_srh() must return +# MALFORMED and the handler must drop the packet. The assertion reads +# the behavior's own errors counter, so it is satisfied only by the +# End.MAP drop and not by any unrelated loss. +test_srh_malformed() +{ + local before after rc=3D0 + + before=3D$(read_route_errors) + + ip netns exec "$rt1" "$HELPER" 2001:db8:1::1 "$END_MAP_SID" \ + >/dev/null 2>&1 + + after=3D$(read_route_errors) + [ "$((after - before))" -eq 1 ] || rc=3D1 + + log_test "$rc" 0 "End.MAP drops a packet carrying a malformed SRH" +} + +# Test 5: Hop Limit expiry. +# +# End.MAP delegates the Hop Limit check and decrement to the ip6_forward +# path. An echo whose Hop Limit is 1 when it reaches rt-2 must elicit +# an ICMPv6 Time Exceeded from rt-2 instead of being forwarded, so +# rt-2's Icmp6OutTimeExcds increments by exactly one. +test_hoplimit_expiry() +{ + local before after rc=3D0 + + before=3D$(read_nstat_counter "$rt2" Icmp6OutTimeExcds) + + ip netns exec "$rt1" \ + ping -6 -c 1 -t 1 -W "$PING_TIMEOUT_SEC" "$END_MAP_SID" \ + >/dev/null 2>&1 + + after=3D$(read_nstat_counter "$rt2" Icmp6OutTimeExcds) + [ "$((after - before))" -eq 1 ] || rc=3D1 + + log_test "$rc" 0 "End.MAP delegates Hop Limit expiry to ip6_forward" +} + +main() +{ + check_dependencies + setup + + test_srh_absent + test_srh_present + test_srh_inline + test_srh_malformed + test_hoplimit_expiry + + print_log_test_results + exit "${ret}" +} + +main "$@" diff --git a/tools/testing/selftests/net/srv6_mobile_send.c b/tools/testing= /selftests/net/srv6_mobile_send.c new file mode 100644 index 000000000000..37d75df3704b --- /dev/null +++ b/tools/testing/selftests/net/srv6_mobile_send.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Helper for SRv6 Mobile (RFC 9433) selftests. + * + * Usage: srv6_mobile_send + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* RFC 8200 Routing header common fields are 4 bytes; an additional + * 4 bytes of type-specific data follow (the Reserved field for the + * deprecated type 0, or first_segment/flags/tag for SRH type 4). The + * segment list then runs in 16-byte units, giving a total of 24 bytes + * for one segment -- which is what ip6r_len =3D 2 advertises. + */ +struct srh { + struct ip6_rthdr rthdr; + uint32_t type_data; + struct in6_addr segments[]; +}; + +#define SRH_ONE_SEG_LEN (sizeof(struct srh) + sizeof(struct in6_addr)) + +static uint16_t csum_fold(uint32_t sum) +{ + while (sum >> 16) + sum =3D (sum & 0xffff) + (sum >> 16); + return ~sum; +} + +static uint32_t csum_partial(const void *buf, size_t len, uint32_t sum) +{ + const uint16_t *p =3D buf; + + while (len > 1) { + sum +=3D *p++; + len -=3D 2; + } + if (len) + sum +=3D *(const uint8_t *)p; + return sum; +} + +static uint16_t icmpv6_checksum(const struct in6_addr *src, + const struct in6_addr *dst, + const void *payload, size_t len) +{ + uint32_t nexthdr =3D htonl(IPPROTO_ICMPV6); + uint32_t plen =3D htonl(len); + uint32_t sum; + + sum =3D csum_partial(src, sizeof(*src), 0); + sum =3D csum_partial(dst, sizeof(*dst), sum); + sum =3D csum_partial(&plen, sizeof(plen), sum); + sum =3D csum_partial(&nexthdr, sizeof(nexthdr), sum); + sum =3D csum_partial(payload, len, sum); + return csum_fold(sum); +} + +int main(int argc, char **argv) +{ + uint8_t frame[sizeof(struct ip6_hdr) + SRH_ONE_SEG_LEN + + sizeof(struct icmp6_hdr)]; + struct sockaddr_in6 dst_addr =3D {}; + struct icmp6_hdr *icmp6; + struct ip6_hdr *ip6; + struct srh *srh; + ssize_t res; + int fd; + + if (argc !=3D 3) { + fprintf(stderr, "usage: %s \n", argv[0]); + return 1; + } + + memset(frame, 0, sizeof(frame)); + ip6 =3D (struct ip6_hdr *)frame; + srh =3D (struct srh *)(frame + sizeof(*ip6)); + icmp6 =3D (struct icmp6_hdr *)(frame + sizeof(*ip6) + SRH_ONE_SEG_LEN); + + ip6->ip6_flow =3D htonl(6u << 28); + ip6->ip6_plen =3D htons(SRH_ONE_SEG_LEN + sizeof(*icmp6)); + ip6->ip6_nxt =3D IPPROTO_ROUTING; + ip6->ip6_hops =3D 64; + if (inet_pton(AF_INET6, argv[1], &ip6->ip6_src) !=3D 1) { + fprintf(stderr, "invalid src %s\n", argv[1]); + return 1; + } + if (inet_pton(AF_INET6, argv[2], &ip6->ip6_dst) !=3D 1) { + fprintf(stderr, "invalid dst %s\n", argv[2]); + return 1; + } + + srh->rthdr.ip6r_nxt =3D IPPROTO_ICMPV6; + srh->rthdr.ip6r_len =3D 2; /* (1 + ip6r_len) * 8 =3D 24 */ + srh->rthdr.ip6r_type =3D 0; /* RFC 8754: SRH is type 4 */ + srh->rthdr.ip6r_segleft =3D 0; + srh->segments[0] =3D ip6->ip6_dst; + + icmp6->icmp6_type =3D ICMP6_ECHO_REQUEST; + icmp6->icmp6_code =3D 0; + icmp6->icmp6_cksum =3D 0; + icmp6->icmp6_dataun.icmp6_un_data16[0] =3D htons(0x1234); + icmp6->icmp6_dataun.icmp6_un_data16[1] =3D htons(1); + icmp6->icmp6_cksum =3D + icmpv6_checksum(&ip6->ip6_src, &ip6->ip6_dst, + icmp6, sizeof(*icmp6)); + + fd =3D socket(AF_INET6, SOCK_RAW, IPPROTO_RAW); + if (fd < 0) { + perror("socket"); + return 1; + } + + dst_addr.sin6_family =3D AF_INET6; + dst_addr.sin6_addr =3D ip6->ip6_dst; + + res =3D sendto(fd, frame, sizeof(frame), 0, + (struct sockaddr *)&dst_addr, sizeof(dst_addr)); + if (res !=3D (ssize_t)sizeof(frame)) { + perror("sendto"); + close(fd); + return 1; + } + + close(fd); + return 0; +} --=20 2.50.1