| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/09/18 14:50 | flow | patch-triage |
15mArgs:null Results: {
"EnableConfigs": [
"IPV6",
"IPV6_SEG6_LWTUNNEL",
"IPV6_SEG6_MOBILE"
],
"FocusSymbols": [
"seg6_mobile_build_state",
"seg6_mobile_input",
"input_action_end_map",
"seg6_mobile_advance_da",
"seg6_mobile_fill_encap",
"seg6_mobile_parse_attrs"
],
"KMSANReasoning": "The patch introduces support for the SRv6 Mobile User Plane (RFC 9433) behaviors via the lightweight tunnel (lwtunnel) encapsulation framework, adding `seg6_mobile.c`.\n\nA detailed evaluation of the memory safety aspects shows no risk of uninitialized memory usage or info-leaks:\n1. Dynamic allocations: State memory allocated via `lwtunnel_state_alloc()` uses `kzalloc()`, and per-CPU counters allocated via `seg6_mobile_alloc_pcpu_counters()` use `__netdev_alloc_pcpu_stats()` with `__GFP_ZERO`. All heap and per-CPU structures are completely zero-initialized.\n2. Kernel-to-userspace data flow: In `seg6_mobile_fill_encap()`, attributes are serialized individually using `nla_put_u32()`, `nla_put_in6_addr()`, and `nla_put_u64_64bit()` with alignment padding (`SEG6_MOBILE_CNT_PAD`). Stack structures (`struct seg6_mobile_counters`) are zero-initialized (`= { 0, 0, 0 }`). No uninitialized struct padding or uninitialized memory is copied or leaked to userspace.\n3. Control flow and local variables: All stack variables (`srh_state`, `frag_off`, `nexthdr`, `l4_off`, `l4_hlen`, `csum`, `tb`, etc.) are explicitly initialized before use or initialized unconditionally by the invoked helpers (e.g. `ipv6_skip_exthdr()`).\n4. KASAN vs. KMSAN: Any potential bugs introduced in packet header parsing, SKB linear buffer expansion (`skb_ensure_writable()`), or netlink parsing would manifest as out-of-bounds reads/writes, NULL pointer dereferences, or memory corruption, all of which are caught by KASAN and standard kernel debuggers.\n\nTherefore, a dedicated KMSAN session is not warranted.",
"NeedsKMSAN": false,
"Reasoning": "The patch implements the SRv6 Mobile User Plane (RFC 9433) behaviors under the lightweight tunnel infrastructure (LWTUNNEL_ENCAP_SEG6_MOBILE). It adds new netlink configuration parsing and route encapsulation building (seg6_mobile_build_state, seg6_mobile_parse_attrs), per-CPU counter handling, and packet processing hooks (seg6_mobile_input, input_action_end_map, seg6_mobile_advance_da) that mutate IPv6 destination addresses and recalculate L4 checksums (TCP, UDP, ICMPv6). This reachable core networking code is fully testable in virtual environments via netlink and virtual interfaces (veth/tun/tap/loopback) and warrants fuzzing.",
"WorthFuzzing": true
} |
| 1/1 | 2026/09/18 14:50 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 226895d2aaa75cc1d5087f3e7576b94879980fb8\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Fri Sep 18 14:50:22 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/include/net/seg6.h b/include/net/seg6.h\nindex 82b3fbbcbb935..789e9bcc47738 100644\n--- a/include/net/seg6.h\n+++ b/include/net/seg6.h\n@@ -64,6 +64,14 @@ static inline int seg6_local_init(void) { return 0; }\n static inline void seg6_local_exit(void) {}\n #endif\n \n+#ifdef CONFIG_IPV6_SEG6_MOBILE\n+extern int seg6_mobile_init(void);\n+extern void seg6_mobile_exit(void);\n+#else\n+static inline int seg6_mobile_init(void) { return 0; }\n+static inline void seg6_mobile_exit(void) {}\n+#endif\n+\n extern bool seg6_validate_srh(struct ipv6_sr_hdr *srh, int len, bool reduced);\n extern struct ipv6_sr_hdr *seg6_get_srh(struct sk_buff *skb, int flags);\n extern void seg6_icmp_srh(struct sk_buff *skb, struct inet6_skb_parm *opt);\ndiff --git a/include/uapi/linux/lwtunnel.h b/include/uapi/linux/lwtunnel.h\nindex 229655ef792f6..6e48f79c548e2 100644\n--- a/include/uapi/linux/lwtunnel.h\n+++ b/include/uapi/linux/lwtunnel.h\n@@ -16,6 +16,7 @@ enum lwtunnel_encap_types {\n \tLWTUNNEL_ENCAP_RPL,\n \tLWTUNNEL_ENCAP_IOAM6,\n \tLWTUNNEL_ENCAP_XFRM,\n+\tLWTUNNEL_ENCAP_SEG6_MOBILE,\n \t__LWTUNNEL_ENCAP_MAX,\n };\n \ndiff --git a/include/uapi/linux/seg6_mobile.h b/include/uapi/linux/seg6_mobile.h\nnew file mode 100644\nindex 0000000000000..068ab91b2873e\n--- /dev/null\n+++ b/include/uapi/linux/seg6_mobile.h\n@@ -0,0 +1,61 @@\n+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */\n+/*\n+ * SRv6 Mobile User Plane implementation\n+ *\n+ * Author:\n+ * Yuya Kusakabe \u003cyuya.kusakabe@gmail.com\u003e\n+ */\n+#ifndef _UAPI_LINUX_SEG6_MOBILE_H\n+#define _UAPI_LINUX_SEG6_MOBILE_H\n+\n+enum {\n+\tSEG6_MOBILE_UNSPEC,\n+\tSEG6_MOBILE_ACTION,\n+\tSEG6_MOBILE_MAPPED_SID,\n+\tSEG6_MOBILE_COUNTERS,\n+\t__SEG6_MOBILE_MAX,\n+};\n+\n+#define SEG6_MOBILE_MAX\t(__SEG6_MOBILE_MAX - 1)\n+\n+enum {\n+\tSEG6_MOBILE_ACTION_UNSPEC\t= 0,\n+\t/* swap IPv6 DA with the mapped SID, leave SRH untouched */\n+\tSEG6_MOBILE_ACTION_END_MAP\t= 1,\n+\n+\t__SEG6_MOBILE_ACTION_MAX,\n+};\n+\n+#define SEG6_MOBILE_ACTION_MAX\t(__SEG6_MOBILE_ACTION_MAX - 1)\n+\n+/* SRv6 Mobile Behavior counters are encoded as netlink attributes\n+ * guaranteeing the correct alignment.\n+ * Each counter is identified by a different attribute type (i.e.\n+ * SEG6_MOBILE_CNT_PACKETS).\n+ *\n+ * - SEG6_MOBILE_CNT_PACKETS: identifies a counter that counts the number\n+ * of packets that have been CORRECTLY processed by an SRv6 Behavior\n+ * instance (i.e., packets that generate errors or are dropped are NOT\n+ * counted).\n+ *\n+ * - SEG6_MOBILE_CNT_BYTES: identifies a counter that counts the total\n+ * amount of traffic in bytes of all packets that have been CORRECTLY\n+ * processed by an SRv6 Behavior instance (i.e., packets that generate\n+ * errors or are dropped are NOT counted).\n+ *\n+ * - SEG6_MOBILE_CNT_ERRORS: identifies a counter that counts the number\n+ * of packets that have NOT been properly processed by an SRv6 Behavior\n+ * instance (i.e., packets that generate errors or are dropped).\n+ */\n+enum {\n+\tSEG6_MOBILE_CNT_UNSPEC,\n+\tSEG6_MOBILE_CNT_PACKETS,\n+\tSEG6_MOBILE_CNT_BYTES,\n+\tSEG6_MOBILE_CNT_ERRORS,\n+\tSEG6_MOBILE_CNT_PAD,\t\t/* pad for 64 bits values */\n+\t__SEG6_MOBILE_CNT_MAX,\n+};\n+\n+#define SEG6_MOBILE_CNT_MAX\t(__SEG6_MOBILE_CNT_MAX - 1)\n+\n+#endif /* _UAPI_LINUX_SEG6_MOBILE_H */\ndiff --git a/net/core/lwtunnel.c b/net/core/lwtunnel.c\nindex b01a395d9a966..4476293ccb37f 100644\n--- a/net/core/lwtunnel.c\n+++ b/net/core/lwtunnel.c\n@@ -53,6 +53,8 @@ static const char *lwtunnel_encap_str(enum lwtunnel_encap_types encap_type)\n \tcase LWTUNNEL_ENCAP_XFRM:\n \t\t/* module autoload not supported for encap type */\n \t\treturn NULL;\n+\tcase LWTUNNEL_ENCAP_SEG6_MOBILE:\n+\t\treturn \"SEG6MOBILE\";\n \tcase LWTUNNEL_ENCAP_IP6:\n \tcase LWTUNNEL_ENCAP_IP:\n \tcase LWTUNNEL_ENCAP_NONE:\ndiff --git a/net/ipv6/Kconfig b/net/ipv6/Kconfig\nindex c3806c6ac96f9..e094b835a1186 100644\n--- a/net/ipv6/Kconfig\n+++ b/net/ipv6/Kconfig\n@@ -314,6 +314,16 @@ config IPV6_SEG6_BPF\n \tdepends on IPV6_SEG6_LWTUNNEL\n \tdepends on IPV6 = y\n \n+config IPV6_SEG6_MOBILE\n+\tbool \"IPv6: SRv6 Mobile User Plane (RFC 9433) behaviors\"\n+\tdepends on IPV6_SEG6_LWTUNNEL\n+\thelp\n+\t Support for the SRv6 Mobile User Plane behaviors defined by\n+\t RFC 9433, exposed via the LWTUNNEL_ENCAP_SEG6_MOBILE lightweight\n+\t tunnel encapsulation.\n+\n+\t If unsure, say N.\n+\n config IPV6_RPL_LWTUNNEL\n \tbool \"IPv6: RPL Source Routing Header support\"\n \tdepends on IPV6\ndiff --git a/net/ipv6/Makefile b/net/ipv6/Makefile\nindex cf5e01f83ce3d..95961f1804390 100644\n--- a/net/ipv6/Makefile\n+++ b/net/ipv6/Makefile\n@@ -24,6 +24,7 @@ ipv6-$(CONFIG_SYN_COOKIES) += syncookies.o\n ipv6-$(CONFIG_NETLABEL) += calipso.o\n ipv6-$(CONFIG_IPV6_SEG6_LWTUNNEL) += seg6_iptunnel.o seg6_local.o\n ipv6-$(CONFIG_IPV6_SEG6_HMAC) += seg6_hmac.o\n+ipv6-$(CONFIG_IPV6_SEG6_MOBILE) += seg6_mobile.o\n ipv6-$(CONFIG_IPV6_RPL_LWTUNNEL) += rpl_iptunnel.o\n ipv6-$(CONFIG_IPV6_IOAM6_LWTUNNEL) += ioam6_iptunnel.o\n \ndiff --git a/net/ipv6/seg6.c b/net/ipv6/seg6.c\nindex 62a7eb7792026..14626b15abd5c 100644\n--- a/net/ipv6/seg6.c\n+++ b/net/ipv6/seg6.c\n@@ -525,10 +525,16 @@ int __init seg6_init(void)\n \tif (err)\n \t\tgoto out_unregister_iptun;\n \n+\terr = seg6_mobile_init();\n+\tif (err)\n+\t\tgoto out_unregister_local;\n+\n \tpr_info(\"Segment Routing with IPv6\\n\");\n \n out:\n \treturn err;\n+out_unregister_local:\n+\tseg6_local_exit();\n out_unregister_iptun:\n \tseg6_iptunnel_exit();\n out_unregister_genl:\n@@ -540,6 +546,7 @@ int __init seg6_init(void)\n \n void seg6_exit(void)\n {\n+\tseg6_mobile_exit();\n \tseg6_local_exit();\n \tseg6_iptunnel_exit();\n \tgenl_unregister_family(\u0026seg6_genl_family);\ndiff --git a/net/ipv6/seg6_mobile.c b/net/ipv6/seg6_mobile.c\nnew file mode 100644\nindex 0000000000000..f3757ab458489\n--- /dev/null\n+++ b/net/ipv6/seg6_mobile.c\n@@ -0,0 +1,701 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/*\n+ * SRv6 Mobile User Plane implementation\n+ *\n+ * Author:\n+ * Yuya Kusakabe \u003cyuya.kusakabe@gmail.com\u003e\n+ */\n+\n+#include \u003clinux/icmpv6.h\u003e\n+#include \u003clinux/in6.h\u003e\n+#include \u003clinux/ipv6.h\u003e\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/skbuff.h\u003e\n+#include \u003clinux/tcp.h\u003e\n+#include \u003clinux/types.h\u003e\n+#include \u003clinux/udp.h\u003e\n+#include \u003cnet/checksum.h\u003e\n+#include \u003cnet/ipv6.h\u003e\n+#include \u003cnet/lwtunnel.h\u003e\n+#include \u003cnet/seg6.h\u003e\n+#ifdef CONFIG_IPV6_SEG6_HMAC\n+#include \u003cnet/seg6_hmac.h\u003e\n+#endif\n+#include \u003cuapi/linux/seg6_mobile.h\u003e\n+\n+#define SEG6_MOBILE_F_ATTR(i)\t\tBIT(i)\n+#define SEG6_F_MOBILE_COUNTERS\t\tSEG6_MOBILE_F_ATTR(SEG6_MOBILE_COUNTERS)\n+\n+struct seg6_mobile_lwt;\n+\n+struct seg6_mobile_action_desc {\n+\tint action;\n+\tunsigned long attrs;\n+\tunsigned long optattrs;\n+\tint (*input)(struct sk_buff *skb, struct seg6_mobile_lwt *slwt);\n+};\n+\n+struct seg6_mobile_action_param {\n+\tint (*parse)(struct nlattr **attrs, struct seg6_mobile_lwt *slwt,\n+\t\t struct netlink_ext_ack *extack);\n+\tint (*put)(struct sk_buff *skb, struct seg6_mobile_lwt *slwt);\n+\tint (*cmp)(struct seg6_mobile_lwt *a, struct seg6_mobile_lwt *b);\n+\n+\t/* optional destroy() callback to release resources acquired in\n+\t * the corresponding parse() function.\n+\t */\n+\tvoid (*destroy)(struct seg6_mobile_lwt *slwt);\n+};\n+\n+struct pcpu_seg6_mobile_counters {\n+\tu64_stats_t packets;\n+\tu64_stats_t bytes;\n+\tu64_stats_t errors;\n+\n+\tstruct u64_stats_sync syncp;\n+};\n+\n+/* User-space aggregate format for the per-CPU counters. Kept private\n+ * to the kernel; userspace receives the values through SEG6_MOBILE_CNT_*\n+ * nested netlink attributes.\n+ */\n+struct seg6_mobile_counters {\n+\t__u64 packets;\n+\t__u64 bytes;\n+\t__u64 errors;\n+};\n+\n+#define seg6_mobile_alloc_pcpu_counters(__gfp)\t\t\t\t\\\n+\t__netdev_alloc_pcpu_stats(struct pcpu_seg6_mobile_counters,\t\\\n+\t\t\t\t ((__gfp) | __GFP_ZERO))\n+\n+struct seg6_mobile_lwt {\n+\tint action;\n+\tstruct in6_addr mapped_sid;\n+\tconst struct seg6_mobile_action_desc *desc;\n+\tstruct pcpu_seg6_mobile_counters __percpu *pcpu_counters;\n+\n+\t/* required attrs are tracked by desc-\u003eattrs; optional attrs that\n+\t * the user actually configured are tracked here so that fill_encap\n+\t * / cmp / destroy can iterate only over what was parsed.\n+\t */\n+\tunsigned long parsed_optattrs;\n+};\n+\n+static struct seg6_mobile_lwt *seg6_mobile_lwtunnel(struct lwtunnel_state *lwt)\n+{\n+\treturn (struct seg6_mobile_lwt *)lwt-\u003edata;\n+}\n+\n+/* ABSENT is told apart from MALFORMED so that a behavior which accepts\n+ * SRH-less packets still drops a malformed SRH\n+ */\n+enum seg6_mobile_srh_state {\n+\tSEG6_MOBILE_SRH_ABSENT,\n+\tSEG6_MOBILE_SRH_PRESENT,\n+\tSEG6_MOBILE_SRH_MALFORMED,\n+};\n+\n+static struct ipv6_sr_hdr *\n+seg6_mobile_get_and_validate_srh(struct sk_buff *skb,\n+\t\t\t\t enum seg6_mobile_srh_state *state)\n+{\n+\tstruct ipv6_sr_hdr *srh;\n+\tunsigned int srhoff = 0;\n+\tint hdr_proto;\n+\tint flags = 0;\n+\n+\tsrh = seg6_get_srh(skb, 0);\n+\tif (srh) {\n+#ifdef CONFIG_IPV6_SEG6_HMAC\n+\t\tif (!seg6_hmac_validate_skb(skb)) {\n+\t\t\t*state = SEG6_MOBILE_SRH_MALFORMED;\n+\t\t\treturn NULL;\n+\t\t}\n+#endif\n+\t\t*state = SEG6_MOBILE_SRH_PRESENT;\n+\t\treturn srh;\n+\t}\n+\n+\thdr_proto = ipv6_find_hdr(skb, \u0026srhoff, IPPROTO_ROUTING, NULL, \u0026flags);\n+\t*state = hdr_proto == -ENOENT ? SEG6_MOBILE_SRH_ABSENT\n+\t\t\t\t : SEG6_MOBILE_SRH_MALFORMED;\n+\treturn NULL;\n+}\n+\n+static int seg6_mobile_l4_csum_hlen(u8 nexthdr)\n+{\n+\tswitch (nexthdr) {\n+\tcase IPPROTO_TCP:\n+\t\treturn sizeof(struct tcphdr);\n+\tcase IPPROTO_UDP:\n+\t\treturn sizeof(struct udphdr);\n+\tcase IPPROTO_ICMPV6:\n+\t\treturn sizeof(struct icmp6hdr);\n+\t}\n+\treturn 0;\n+}\n+\n+static __sum16 *seg6_mobile_l4_csum(struct sk_buff *skb, int l4_off,\n+\t\t\t\t u8 nexthdr)\n+{\n+\tswitch (nexthdr) {\n+\tcase IPPROTO_TCP:\n+\t\treturn \u0026((struct tcphdr *)(skb-\u003edata + l4_off))-\u003echeck;\n+\tcase IPPROTO_UDP: {\n+\t\tstruct udphdr *uh = (struct udphdr *)(skb-\u003edata + l4_off);\n+\n+\t\t/* zero UDPv6 checksum on a non-offloaded skb means \"no checksum\" */\n+\t\tif (!uh-\u003echeck \u0026\u0026 skb-\u003eip_summed != CHECKSUM_PARTIAL)\n+\t\t\treturn NULL;\n+\t\treturn \u0026uh-\u003echeck;\n+\t}\n+\tcase IPPROTO_ICMPV6:\n+\t\treturn \u0026((struct icmp6hdr *)(skb-\u003edata + l4_off))-\u003eicmp6_cksum;\n+\t}\n+\treturn NULL;\n+}\n+\n+/* rewrite DA; the L4 checksum follows only when the receiver will verify\n+ * it against the new DA, i.e. no SRH is left for it to process\n+ */\n+static int seg6_mobile_advance_da(struct sk_buff *skb,\n+\t\t\t\t const struct in6_addr *nh, bool srh_present)\n+{\n+\tint l4_off = 0, l4_hlen = 0;\n+\tstruct in6_addr old_da;\n+\tstruct ipv6hdr *ip6h;\n+\t__be16 frag_off;\n+\tu8 nexthdr = 0;\n+\t__sum16 *csum;\n+\tint write_len;\n+\n+\tif (!pskb_may_pull(skb, sizeof(*ip6h)))\n+\t\treturn -EINVAL;\n+\n+\tip6h = ipv6_hdr(skb);\n+\twrite_len = sizeof(*ip6h);\n+\n+\tif (!srh_present) {\n+\t\tnexthdr = ip6h-\u003enexthdr;\n+\t\tl4_off = ipv6_skip_exthdr(skb, sizeof(*ip6h), \u0026nexthdr,\n+\t\t\t\t\t \u0026frag_off);\n+\t\tif (l4_off \u003c 0)\n+\t\t\treturn -EINVAL;\n+\n+\t\t/* only the first fragment carries the L4 header; frag_off is the\n+\t\t * raw field, M flag included\n+\t\t */\n+\t\tif (!(frag_off \u0026 htons(IP6_OFFSET)))\n+\t\t\tl4_hlen = seg6_mobile_l4_csum_hlen(nexthdr);\n+\t\tif (l4_hlen)\n+\t\t\twrite_len = l4_off + l4_hlen;\n+\t}\n+\n+\tif (skb_ensure_writable(skb, write_len))\n+\t\treturn -ENOMEM;\n+\n+\t/* skb_ensure_writable() may change skb pointers; evaluate ip6h again */\n+\tip6h = ipv6_hdr(skb);\n+\told_da = ip6h-\u003edaddr;\n+\n+\tcsum = l4_hlen ? seg6_mobile_l4_csum(skb, l4_off, nexthdr) : NULL;\n+\tif (csum) {\n+\t\tinet_proto_csum_replace16(csum, skb, old_da.s6_addr32,\n+\t\t\t\t\t nh-\u003es6_addr32, true);\n+\t\tif (nexthdr == IPPROTO_UDP \u0026\u0026 !*csum)\n+\t\t\t*csum = CSUM_MANGLED_0;\n+\t} else if (skb-\u003eip_summed == CHECKSUM_COMPLETE) {\n+\t\t/* no L4 patch, so skb-\u003ecsum must track the DA change itself */\n+\t\tupdate_csum_diff16(skb, old_da.s6_addr32, (__be32 *)nh);\n+\t}\n+\n+\tip6h-\u003edaddr = *nh;\n+\tskb_clear_hash(skb);\n+\n+\treturn 0;\n+}\n+\n+static int seg6_mobile_forward(struct sk_buff *skb)\n+{\n+\tseg6_lookup_nexthop(skb, NULL, 0);\n+\treturn dst_input(skb);\n+}\n+\n+/* replace DA with the mapped SID and forward, leaving the SRH untouched */\n+static int input_action_end_map(struct sk_buff *skb,\n+\t\t\t\tstruct seg6_mobile_lwt *slwt)\n+{\n+\tenum seg6_mobile_srh_state srh_state;\n+\tstruct ipv6_sr_hdr *srh;\n+\n+\tsrh = seg6_mobile_get_and_validate_srh(skb, \u0026srh_state);\n+\tif (srh_state == SEG6_MOBILE_SRH_MALFORMED)\n+\t\tgoto drop;\n+\n+\t/* SL == 0: SRH spent, L4 is verified against the new DA */\n+\tif (seg6_mobile_advance_da(skb, \u0026slwt-\u003emapped_sid,\n+\t\t\t\t srh \u0026\u0026 srh-\u003esegments_left))\n+\t\tgoto drop;\n+\n+\treturn seg6_mobile_forward(skb);\n+\n+drop:\n+\tkfree_skb(skb);\n+\treturn -EINVAL;\n+}\n+\n+static int parse_nla_mapped_sid(struct nlattr **attrs,\n+\t\t\t\tstruct seg6_mobile_lwt *slwt,\n+\t\t\t\tstruct netlink_ext_ack *extack)\n+{\n+\tmemcpy(\u0026slwt-\u003emapped_sid, nla_data(attrs[SEG6_MOBILE_MAPPED_SID]),\n+\t sizeof(struct in6_addr));\n+\n+\treturn 0;\n+}\n+\n+static int put_nla_mapped_sid(struct sk_buff *skb, struct seg6_mobile_lwt *slwt)\n+{\n+\tif (nla_put_in6_addr(skb, SEG6_MOBILE_MAPPED_SID, \u0026slwt-\u003emapped_sid))\n+\t\treturn -EMSGSIZE;\n+\n+\treturn 0;\n+}\n+\n+static int cmp_nla_mapped_sid(struct seg6_mobile_lwt *a, struct seg6_mobile_lwt *b)\n+{\n+\treturn memcmp(\u0026a-\u003emapped_sid, \u0026b-\u003emapped_sid, sizeof(struct in6_addr));\n+}\n+\n+static const struct\n+nla_policy seg6_mobile_counters_policy[SEG6_MOBILE_CNT_MAX + 1] = {\n+\t[SEG6_MOBILE_CNT_PACKETS]\t= { .type = NLA_U64 },\n+\t[SEG6_MOBILE_CNT_BYTES]\t\t= { .type = NLA_U64 },\n+\t[SEG6_MOBILE_CNT_ERRORS]\t= { .type = NLA_U64 },\n+};\n+\n+static int parse_nla_counters(struct nlattr **attrs,\n+\t\t\t struct seg6_mobile_lwt *slwt,\n+\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tstruct pcpu_seg6_mobile_counters __percpu *pcounters;\n+\tstruct nlattr *tb[SEG6_MOBILE_CNT_MAX + 1];\n+\tint ret;\n+\n+\tret = nla_parse_nested(tb, SEG6_MOBILE_CNT_MAX,\n+\t\t\t attrs[SEG6_MOBILE_COUNTERS],\n+\t\t\t seg6_mobile_counters_policy, extack);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\t/* basic support for SRv6 Behavior counters requires at least:\n+\t * packets, bytes and errors.\n+\t */\n+\tif (!tb[SEG6_MOBILE_CNT_PACKETS] || !tb[SEG6_MOBILE_CNT_BYTES] ||\n+\t !tb[SEG6_MOBILE_CNT_ERRORS])\n+\t\treturn -EINVAL;\n+\n+\t/* counters are always zero initialized */\n+\tpcounters = seg6_mobile_alloc_pcpu_counters(GFP_KERNEL);\n+\tif (!pcounters)\n+\t\treturn -ENOMEM;\n+\n+\tslwt-\u003epcpu_counters = pcounters;\n+\n+\treturn 0;\n+}\n+\n+static int seg6_mobile_fill_nla_counters(struct sk_buff *skb,\n+\t\t\t\t\t struct seg6_mobile_counters *counters)\n+{\n+\tif (nla_put_u64_64bit(skb, SEG6_MOBILE_CNT_PACKETS, counters-\u003epackets,\n+\t\t\t SEG6_MOBILE_CNT_PAD))\n+\t\treturn -EMSGSIZE;\n+\n+\tif (nla_put_u64_64bit(skb, SEG6_MOBILE_CNT_BYTES, counters-\u003ebytes,\n+\t\t\t SEG6_MOBILE_CNT_PAD))\n+\t\treturn -EMSGSIZE;\n+\n+\tif (nla_put_u64_64bit(skb, SEG6_MOBILE_CNT_ERRORS, counters-\u003eerrors,\n+\t\t\t SEG6_MOBILE_CNT_PAD))\n+\t\treturn -EMSGSIZE;\n+\n+\treturn 0;\n+}\n+\n+static int put_nla_counters(struct sk_buff *skb, struct seg6_mobile_lwt *slwt)\n+{\n+\tstruct seg6_mobile_counters counters = { 0, 0, 0 };\n+\tstruct nlattr *nest;\n+\tint rc, i;\n+\n+\tnest = nla_nest_start(skb, SEG6_MOBILE_COUNTERS);\n+\tif (!nest)\n+\t\treturn -EMSGSIZE;\n+\n+\tfor_each_possible_cpu(i) {\n+\t\tstruct pcpu_seg6_mobile_counters *pcounters;\n+\t\tu64 packets, bytes, errors;\n+\t\tunsigned int start;\n+\n+\t\tpcounters = per_cpu_ptr(slwt-\u003epcpu_counters, i);\n+\t\tdo {\n+\t\t\tstart = u64_stats_fetch_begin(\u0026pcounters-\u003esyncp);\n+\n+\t\t\tpackets = u64_stats_read(\u0026pcounters-\u003epackets);\n+\t\t\tbytes = u64_stats_read(\u0026pcounters-\u003ebytes);\n+\t\t\terrors = u64_stats_read(\u0026pcounters-\u003eerrors);\n+\n+\t\t} while (u64_stats_fetch_retry(\u0026pcounters-\u003esyncp, start));\n+\n+\t\tcounters.packets += packets;\n+\t\tcounters.bytes += bytes;\n+\t\tcounters.errors += errors;\n+\t}\n+\n+\trc = seg6_mobile_fill_nla_counters(skb, \u0026counters);\n+\tif (rc \u003c 0) {\n+\t\tnla_nest_cancel(skb, nest);\n+\t\treturn rc;\n+\t}\n+\n+\treturn nla_nest_end(skb, nest);\n+}\n+\n+static int cmp_nla_counters(struct seg6_mobile_lwt *a,\n+\t\t\t struct seg6_mobile_lwt *b)\n+{\n+\t/* tunnels with counters enabled and disabled are different. */\n+\treturn (!!((unsigned long)a-\u003epcpu_counters)) ^\n+\t\t(!!((unsigned long)b-\u003epcpu_counters));\n+}\n+\n+static void destroy_attr_counters(struct seg6_mobile_lwt *slwt)\n+{\n+\tfree_percpu(slwt-\u003epcpu_counters);\n+}\n+\n+static const struct seg6_mobile_action_desc seg6_mobile_action_table[] = {\n+\t{\n+\t\t.action\t\t= SEG6_MOBILE_ACTION_END_MAP,\n+\t\t.attrs\t\t= SEG6_MOBILE_F_ATTR(SEG6_MOBILE_MAPPED_SID),\n+\t\t.optattrs\t= SEG6_F_MOBILE_COUNTERS,\n+\t\t.input\t\t= input_action_end_map,\n+\t},\n+};\n+\n+static const struct seg6_mobile_action_param\n+seg6_mobile_action_params[SEG6_MOBILE_MAX + 1] = {\n+\t[SEG6_MOBILE_MAPPED_SID] = {\n+\t\t.parse\t= parse_nla_mapped_sid,\n+\t\t.put\t= put_nla_mapped_sid,\n+\t\t.cmp\t= cmp_nla_mapped_sid,\n+\t},\n+\t[SEG6_MOBILE_COUNTERS] = {\n+\t\t.parse\t\t= parse_nla_counters,\n+\t\t.put\t\t= put_nla_counters,\n+\t\t.cmp\t\t= cmp_nla_counters,\n+\t\t.destroy\t= destroy_attr_counters,\n+\t},\n+};\n+\n+static const struct nla_policy\n+seg6_mobile_policy[SEG6_MOBILE_MAX + 1] = {\n+\t[SEG6_MOBILE_ACTION]\t= { .type = NLA_U32 },\n+\t[SEG6_MOBILE_MAPPED_SID]\t= NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)),\n+\t[SEG6_MOBILE_COUNTERS]\t= { .type = NLA_NESTED },\n+};\n+\n+static const struct seg6_mobile_action_desc *\n+seg6_mobile_get_action_desc(int action)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i \u003c ARRAY_SIZE(seg6_mobile_action_table); i++) {\n+\t\tif (seg6_mobile_action_table[i].action == action)\n+\t\t\treturn \u0026seg6_mobile_action_table[i];\n+\t}\n+\n+\treturn NULL;\n+}\n+\n+/* call the destroy() callback (if available) for each set attribute in\n+ * @parsed_attrs, starting from the first attribute up to the @max_parsed\n+ * (excluded) attribute.\n+ */\n+static void __destroy_attrs(unsigned long parsed_attrs, int max_parsed,\n+\t\t\t struct seg6_mobile_lwt *slwt)\n+{\n+\tconst struct seg6_mobile_action_param *param;\n+\tint i;\n+\n+\tfor (i = SEG6_MOBILE_ACTION + 1; i \u003c max_parsed; i++) {\n+\t\tif (!(parsed_attrs \u0026 SEG6_MOBILE_F_ATTR(i)))\n+\t\t\tcontinue;\n+\n+\t\tparam = \u0026seg6_mobile_action_params[i];\n+\t\tif (param-\u003edestroy)\n+\t\t\tparam-\u003edestroy(slwt);\n+\t}\n+}\n+\n+static void destroy_attrs(struct seg6_mobile_lwt *slwt)\n+{\n+\tunsigned long attrs = slwt-\u003edesc-\u003eattrs | slwt-\u003eparsed_optattrs;\n+\n+\t__destroy_attrs(attrs, SEG6_MOBILE_MAX + 1, slwt);\n+}\n+\n+static int seg6_mobile_parse_attrs(struct nlattr **attrs,\n+\t\t\t\t struct seg6_mobile_lwt *slwt,\n+\t\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tconst struct seg6_mobile_action_param *param;\n+\tconst struct seg6_mobile_action_desc *desc;\n+\tunsigned long parsed_optattrs = 0;\n+\tint i, err;\n+\n+\tdesc = slwt-\u003edesc;\n+\n+\tif (WARN_ON_ONCE(desc-\u003eattrs \u0026 desc-\u003eoptattrs))\n+\t\treturn -EINVAL;\n+\n+\tfor (i = SEG6_MOBILE_ACTION + 1; i \u003c= SEG6_MOBILE_MAX; i++) {\n+\t\tbool required = desc-\u003eattrs \u0026 SEG6_MOBILE_F_ATTR(i);\n+\t\tbool optional = desc-\u003eoptattrs \u0026 SEG6_MOBILE_F_ATTR(i);\n+\n+\t\tif (!required \u0026\u0026 !optional)\n+\t\t\tcontinue;\n+\n+\t\tif (required \u0026\u0026 !attrs[i]) {\n+\t\t\tNL_SET_ERR_MSG_MOD(extack,\n+\t\t\t\t\t \"missing required attribute\");\n+\t\t\terr = -EINVAL;\n+\t\t\tgoto err;\n+\t\t}\n+\n+\t\tif (!attrs[i])\n+\t\t\tcontinue;\n+\n+\t\tparam = \u0026seg6_mobile_action_params[i];\n+\t\terr = param-\u003eparse(attrs, slwt, extack);\n+\t\tif (err \u003c 0)\n+\t\t\tgoto err;\n+\n+\t\tif (optional)\n+\t\t\tparsed_optattrs |= SEG6_MOBILE_F_ATTR(i);\n+\t}\n+\n+\tslwt-\u003eparsed_optattrs = parsed_optattrs;\n+\n+\treturn 0;\n+\n+err:\n+\t__destroy_attrs(desc-\u003eattrs | parsed_optattrs, i, slwt);\n+\treturn err;\n+}\n+\n+static bool seg6_mobile_counters_enabled(struct seg6_mobile_lwt *slwt)\n+{\n+\treturn slwt-\u003eparsed_optattrs \u0026 SEG6_F_MOBILE_COUNTERS;\n+}\n+\n+static void seg6_mobile_update_counters(struct seg6_mobile_lwt *slwt,\n+\t\t\t\t\tunsigned int len, int err)\n+{\n+\tstruct pcpu_seg6_mobile_counters *pcounters;\n+\n+\tpcounters = this_cpu_ptr(slwt-\u003epcpu_counters);\n+\tu64_stats_update_begin(\u0026pcounters-\u003esyncp);\n+\n+\tif (likely(!err)) {\n+\t\tu64_stats_inc(\u0026pcounters-\u003epackets);\n+\t\tu64_stats_add(\u0026pcounters-\u003ebytes, len);\n+\t} else {\n+\t\tu64_stats_inc(\u0026pcounters-\u003eerrors);\n+\t}\n+\n+\tu64_stats_update_end(\u0026pcounters-\u003esyncp);\n+}\n+\n+static int seg6_mobile_input(struct sk_buff *skb)\n+{\n+\tstruct dst_entry *orig_dst = skb_dst(skb);\n+\tstruct seg6_mobile_lwt *slwt;\n+\tunsigned int len = skb-\u003elen;\n+\tint rc;\n+\n+\tif (skb-\u003eprotocol != htons(ETH_P_IPV6)) {\n+\t\tkfree_skb(skb);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tslwt = seg6_mobile_lwtunnel(orig_dst-\u003elwtstate);\n+\n+\trc = slwt-\u003edesc-\u003einput(skb, slwt);\n+\n+\tif (seg6_mobile_counters_enabled(slwt))\n+\t\tseg6_mobile_update_counters(slwt, len, rc);\n+\n+\treturn rc;\n+}\n+\n+static int seg6_mobile_build_state(struct net *net, struct nlattr *nla,\n+\t\t\t\t unsigned int family, const void *cfg,\n+\t\t\t\t struct lwtunnel_state **ts,\n+\t\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tconst struct seg6_mobile_action_desc *desc;\n+\tstruct nlattr *tb[SEG6_MOBILE_MAX + 1];\n+\tstruct lwtunnel_state *newts;\n+\tstruct seg6_mobile_lwt *slwt;\n+\tint err;\n+\n+\tif (family != AF_INET6)\n+\t\treturn -EINVAL;\n+\n+\terr = nla_parse_nested(tb, SEG6_MOBILE_MAX, nla,\n+\t\t\t seg6_mobile_policy, extack);\n+\tif (err \u003c 0)\n+\t\treturn err;\n+\n+\tif (!tb[SEG6_MOBILE_ACTION]) {\n+\t\tNL_SET_ERR_MSG_MOD(extack, \"missing SEG6_MOBILE_ACTION\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tdesc = seg6_mobile_get_action_desc(nla_get_u32(tb[SEG6_MOBILE_ACTION]));\n+\tif (!desc) {\n+\t\tNL_SET_ERR_MSG_MOD(extack, \"unknown SRv6 Mobile action\");\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n+\tnewts = lwtunnel_state_alloc(sizeof(*slwt));\n+\tif (!newts)\n+\t\treturn -ENOMEM;\n+\n+\tslwt = seg6_mobile_lwtunnel(newts);\n+\tslwt-\u003eaction = desc-\u003eaction;\n+\tslwt-\u003edesc = desc;\n+\n+\terr = seg6_mobile_parse_attrs(tb, slwt, extack);\n+\tif (err \u003c 0) {\n+\t\tkfree(newts);\n+\t\treturn err;\n+\t}\n+\n+\tnewts-\u003etype = LWTUNNEL_ENCAP_SEG6_MOBILE;\n+\tnewts-\u003eflags = LWTUNNEL_STATE_INPUT_REDIRECT;\n+\n+\t*ts = newts;\n+\n+\treturn 0;\n+}\n+\n+static void seg6_mobile_destroy_state(struct lwtunnel_state *lwt)\n+{\n+\tdestroy_attrs(seg6_mobile_lwtunnel(lwt));\n+}\n+\n+static int seg6_mobile_fill_encap(struct sk_buff *skb,\n+\t\t\t\t struct lwtunnel_state *lwt)\n+{\n+\tstruct seg6_mobile_lwt *slwt = seg6_mobile_lwtunnel(lwt);\n+\tconst struct seg6_mobile_action_param *param;\n+\tunsigned long attrs;\n+\tint i, err;\n+\n+\tif (nla_put_u32(skb, SEG6_MOBILE_ACTION, slwt-\u003eaction))\n+\t\treturn -EMSGSIZE;\n+\n+\tattrs = slwt-\u003edesc-\u003eattrs | slwt-\u003eparsed_optattrs;\n+\tfor (i = SEG6_MOBILE_ACTION + 1; i \u003c= SEG6_MOBILE_MAX; i++) {\n+\t\tif (!(attrs \u0026 SEG6_MOBILE_F_ATTR(i)))\n+\t\t\tcontinue;\n+\n+\t\tparam = \u0026seg6_mobile_action_params[i];\n+\t\terr = param-\u003eput(skb, slwt);\n+\t\tif (err \u003c 0)\n+\t\t\treturn err;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int seg6_mobile_get_encap_size(struct lwtunnel_state *lwt)\n+{\n+\tstruct seg6_mobile_lwt *slwt = seg6_mobile_lwtunnel(lwt);\n+\tunsigned long attrs;\n+\tint nlsize;\n+\n+\tnlsize = nla_total_size(sizeof(u32)); /* SEG6_MOBILE_ACTION */\n+\n+\tattrs = slwt-\u003edesc-\u003eattrs | slwt-\u003eparsed_optattrs;\n+\tif (attrs \u0026 SEG6_MOBILE_F_ATTR(SEG6_MOBILE_MAPPED_SID))\n+\t\tnlsize += nla_total_size(sizeof(struct in6_addr));\n+\n+\tif (attrs \u0026 SEG6_F_MOBILE_COUNTERS)\n+\t\tnlsize += nla_total_size(0) + /* nest SEG6_MOBILE_COUNTERS */\n+\t\t\t /* SEG6_MOBILE_CNT_PACKETS */\n+\t\t\t nla_total_size_64bit(sizeof(__u64)) +\n+\t\t\t /* SEG6_MOBILE_CNT_BYTES */\n+\t\t\t nla_total_size_64bit(sizeof(__u64)) +\n+\t\t\t /* SEG6_MOBILE_CNT_ERRORS */\n+\t\t\t nla_total_size_64bit(sizeof(__u64));\n+\n+\treturn nlsize;\n+}\n+\n+static int seg6_mobile_cmp_encap(struct lwtunnel_state *a,\n+\t\t\t\t struct lwtunnel_state *b)\n+{\n+\tstruct seg6_mobile_lwt *slwt_a = seg6_mobile_lwtunnel(a);\n+\tstruct seg6_mobile_lwt *slwt_b = seg6_mobile_lwtunnel(b);\n+\tconst struct seg6_mobile_action_param *param;\n+\tunsigned long attrs_a, attrs_b;\n+\tint i;\n+\n+\tif (slwt_a-\u003eaction != slwt_b-\u003eaction)\n+\t\treturn 1;\n+\n+\tattrs_a = slwt_a-\u003edesc-\u003eattrs | slwt_a-\u003eparsed_optattrs;\n+\tattrs_b = slwt_b-\u003edesc-\u003eattrs | slwt_b-\u003eparsed_optattrs;\n+\n+\tif (attrs_a != attrs_b)\n+\t\treturn 1;\n+\n+\tfor (i = SEG6_MOBILE_ACTION + 1; i \u003c= SEG6_MOBILE_MAX; i++) {\n+\t\tif (!(attrs_a \u0026 SEG6_MOBILE_F_ATTR(i)))\n+\t\t\tcontinue;\n+\n+\t\tparam = \u0026seg6_mobile_action_params[i];\n+\t\tif (param-\u003ecmp(slwt_a, slwt_b))\n+\t\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static const struct lwtunnel_encap_ops seg6_mobile_ops = {\n+\t.build_state\t= seg6_mobile_build_state,\n+\t.destroy_state\t= seg6_mobile_destroy_state,\n+\t.input\t\t= seg6_mobile_input,\n+\t.fill_encap\t= seg6_mobile_fill_encap,\n+\t.get_encap_size\t= seg6_mobile_get_encap_size,\n+\t.cmp_encap\t= seg6_mobile_cmp_encap,\n+\t.owner\t\t= THIS_MODULE,\n+};\n+\n+int __init seg6_mobile_init(void)\n+{\n+\tBUILD_BUG_ON(SEG6_MOBILE_MAX + 1 \u003e BITS_PER_TYPE(unsigned long));\n+\n+\treturn lwtunnel_encap_add_ops(\u0026seg6_mobile_ops,\n+\t\t\t\t LWTUNNEL_ENCAP_SEG6_MOBILE);\n+}\n+\n+void seg6_mobile_exit(void)\n+{\n+\tlwtunnel_encap_del_ops(\u0026seg6_mobile_ops, LWTUNNEL_ENCAP_SEG6_MOBILE);\n+}\ndiff --git a/tools/testing/selftests/net/.gitignore b/tools/testing/selftests/net/.gitignore\nindex c9f46031ac73b..9afff0d83ddec 100644\n--- a/tools/testing/selftests/net/.gitignore\n+++ b/tools/testing/selftests/net/.gitignore\n@@ -42,6 +42,7 @@ socket\n so_incoming_cpu\n so_netns_cookie\n so_rcv_listener\n+srv6_mobile_send\n stress_reuseport_listen\n tap\n tcp_fastopen_backup_key\ndiff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile\nindex 3ee3378f8b26e..105e0f5077918 100644\n--- a/tools/testing/selftests/net/Makefile\n+++ b/tools/testing/selftests/net/Makefile\n@@ -96,6 +96,7 @@ TEST_PROGS := \\\n \tsrv6_end_dx4_netfilter_test.sh \\\n \tsrv6_end_dx6_netfilter_test.sh \\\n \tsrv6_end_flavors_test.sh \\\n+\tsrv6_end_map_test.sh \\\n \tsrv6_end_next_csid_l3vpn_test.sh \\\n \tsrv6_end_x_next_csid_l3vpn_test.sh \\\n \tsrv6_hencap_red_l3vpn_test.sh \\\n@@ -165,6 +166,7 @@ TEST_GEN_FILES := \\\n \tso_netns_cookie \\\n \tso_rcv_listener \\\n \tsocket \\\n+\tsrv6_mobile_send \\\n \tstress_reuseport_listen \\\n \ttcp_fastopen_backup_key \\\n \ttcp_inq \\\ndiff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config\nindex 30d5fcb09a831..afe9a51631679 100644\n--- a/tools/testing/selftests/net/config\n+++ b/tools/testing/selftests/net/config\n@@ -49,6 +49,7 @@ CONFIG_IPV6_ROUTE_INFO=y\n CONFIG_IPV6_ROUTER_PREF=y\n CONFIG_IPV6_RPL_LWTUNNEL=y\n CONFIG_IPV6_SEG6_LWTUNNEL=y\n+CONFIG_IPV6_SEG6_MOBILE=y\n CONFIG_IPV6_SIT=y\n CONFIG_IPV6_VTI=y\n CONFIG_IPVLAN=m\ndiff --git a/tools/testing/selftests/net/srv6_end_map_test.sh b/tools/testing/selftests/net/srv6_end_map_test.sh\nnew file mode 100755\nindex 0000000000000..a4efcb58a1e4d\n--- /dev/null\n+++ b/tools/testing/selftests/net/srv6_end_map_test.sh\n@@ -0,0 +1,400 @@\n+#!/bin/bash\n+# SPDX-License-Identifier: GPL-2.0\n+#\n+# author: Yuya Kusakabe \u003cyuya.kusakabe@gmail.com\u003e\n+\n+# Selftest for the SRv6 End.MAP behavior (RFC 9433).\n+#\n+# +------+ 2001:db8:1::/64 +------+ 2001:db8:2::/64 +------+\n+# | rt-1 | --------------------- | rt-2 | --------------------- | rt-3 |\n+# +------+ veth1 +------+ veth2 +------+\n+# (End.MAP)\n+#\n+# rt-2 holds the End.MAP route for 2001:db8:f::/64 that replaces the\n+# IPv6 destination with 2001:db8:3::3 (an address on rt-3's loopback,\n+# also used as the final SRv6 segment in the H.Encaps scenario).\n+#\n+# The original destination 2001:db8:f::1 and the replacement\n+# 2001:db8:3::3 have different 16-bit word sums, so any regression in\n+# the transport-checksum diff update would corrupt the ICMPv6\n+# checksum and bump Icmp6InCsumErrors -- the forwarding cases assert\n+# that the counter does not move.\n+#\n+# Eight cases are exercised:\n+#\n+# 1. SRH absent -- plain ICMPv6 echo to the End.MAP SID.\n+# 2. SRH present -- the destination is reached through an\n+# H.Encaps wrapper that carries an SRH with\n+# two segments; End.MAP must leave the SRH\n+# structurally intact.\n+# 3. SRH inline -- the destination is reached through an\n+# H.Insert wrapper that inserts an SRH whose\n+# first hop is the End.MAP SID; End.MAP must\n+# NOT patch the L4 checksum, because the\n+# receiver's SRv6 processing restores the\n+# destination from segments[0] before the\n+# ICMPv6 handler verifies it.\n+# 4. RH not an SRH -- a C helper sends a packet whose Routing\n+# Header is type 0 rather than 4; End.MAP must\n+# drop it. The behavior's own errors counter\n+# binds the assertion to the drop.\n+# 5. SRH malformed -- the helper sends an SRH whose Last Entry\n+# exceeds its length; End.MAP must drop it.\n+# 6. SRH exhausted -- the helper sends a valid SRH with Segments\n+# Left 0, so the IPv6 DA already is the final\n+# destination; End.MAP must patch the L4\n+# checksum as in the SRH-absent case and the\n+# receiver must accept the echo.\n+# 7. Fragmented -- an oversized echo without an SRH is fragmented\n+# by rt-1; only the first fragment carries the\n+# ICMPv6 header, and End.MAP must patch its\n+# checksum so the reassembled echo verifies at\n+# rt-3.\n+# 8. Hop Limit -- an echo whose Hop Limit is 1 on arrival at\n+# the End.MAP node must yield an ICMPv6 Time\n+# Exceeded from that node, confirming Hop Limit\n+# handling is delegated to the ip6_forward path.\n+\n+source lib.sh\n+\n+readonly PING_TIMEOUT_SEC=4\n+readonly COUNTER_TIMEOUT_SEC=2\n+readonly END_MAP_PREFIX=\"2001:db8:f::/64\"\n+readonly END_MAP_SID=\"2001:db8:f::1\"\n+readonly RT3_SID=\"2001:db8:3::3\"\n+HELPER_DIR=$(cd -- \"$(dirname -- \"${BASH_SOURCE[0]}\")\" \u0026\u0026 pwd)\n+readonly HELPER_DIR\n+readonly HELPER=\"${HELPER_DIR}/srv6_mobile_send\"\n+\n+ret=0\n+nsuccess=0\n+nfail=0\n+\n+PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}\n+\n+log_test()\n+{\n+\tlocal rc=$1\n+\tlocal expected=$2\n+\tlocal msg=\"$3\"\n+\n+\tif [ \"${rc}\" -eq \"${expected}\" ]; then\n+\t\tnsuccess=$((nsuccess + 1))\n+\t\tprintf \"\\n TEST: %-60s [ OK ]\\n\" \"${msg}\"\n+\telse\n+\t\tret=1\n+\t\tnfail=$((nfail + 1))\n+\t\tprintf \"\\n TEST: %-60s [FAIL]\\n\" \"${msg}\"\n+\t\tif [ \"${PAUSE_ON_FAIL}\" = \"yes\" ]; then\n+\t\t\techo\n+\t\t\techo \"hit enter to continue, 'q' to quit\"\n+\t\t\tread -r a\n+\t\t\t[ \"$a\" = \"q\" ] \u0026\u0026 exit 1\n+\t\tfi\n+\tfi\n+}\n+\n+print_log_test_results()\n+{\n+\tprintf \"\\nTests passed: %3d\\n\" \"${nsuccess}\"\n+\tprintf \"Tests failed: %3d\\n\" \"${nfail}\"\n+}\n+\n+cleanup()\n+{\n+\tcleanup_all_ns\n+}\n+\n+trap cleanup EXIT\n+\n+check_dependencies()\n+{\n+\tif [ \"$(id -u)\" -ne 0 ]; then\n+\t\techo \"SKIP: need root privileges\"\n+\t\texit \"${ksft_skip}\"\n+\tfi\n+\n+\tfor cmd in ip ping nstat sysctl ethtool; do\n+\t\tif ! command -v \"$cmd\" \u003e/dev/null; then\n+\t\t\techo \"SKIP: ${cmd} is required\"\n+\t\t\texit \"${ksft_skip}\"\n+\t\tfi\n+\tdone\n+\n+\tif [ ! -x \"${HELPER}\" ]; then\n+\t\techo \"SKIP: ${HELPER} not built\"\n+\t\texit \"${ksft_skip}\"\n+\tfi\n+\n+\tif ! ip route help 2\u003e\u00261 | grep -qF \"seg6mobile\"; then\n+\t\techo \"SKIP: iproute2 lacks seg6mobile support\"\n+\t\texit \"${ksft_skip}\"\n+\tfi\n+\n+\tif ! ip route help 2\u003e\u00261 | grep -qF \"End.MAP\"; then\n+\t\techo \"SKIP: iproute2 lacks End.MAP action\"\n+\t\texit \"${ksft_skip}\"\n+\tfi\n+}\n+\n+setup()\n+{\n+\tsetup_ns rt1 rt2 rt3\n+\n+\t# shellcheck disable=SC2154 # variables assigned by setup_ns\n+\tfor ns in \"$rt1\" \"$rt2\" \"$rt3\"; do\n+\t\tip -n \"$ns\" link set lo up\n+\tdone\n+\n+\tip link add veth1 netns \"$rt1\" \\\n+\t\ttype veth peer name veth1-rt2 netns \"$rt2\"\n+\tip link add veth2 netns \"$rt2\" \\\n+\t\ttype veth peer name veth2-rt3 netns \"$rt3\"\n+\n+\tip -n \"$rt1\" addr add 2001:db8:1::1/64 dev veth1 nodad\n+\tip -n \"$rt2\" addr add 2001:db8:1::2/64 dev veth1-rt2 nodad\n+\tip -n \"$rt2\" addr add 2001:db8:2::1/64 dev veth2 nodad\n+\tip -n \"$rt3\" addr add 2001:db8:2::2/64 dev veth2-rt3 nodad\n+\t# rt-3 also owns the End.MAP replacement SID / SRH endpoint.\n+\tip -n \"$rt3\" addr add \"$RT3_SID/128\" dev lo nodad\n+\n+\tip -n \"$rt1\" link set veth1 up\n+\tip -n \"$rt2\" link set veth1-rt2 up\n+\tip -n \"$rt2\" link set veth2 up\n+\tip -n \"$rt3\" link set veth2-rt3 up\n+\n+\tip netns exec \"$rt2\" sysctl -wq net.ipv6.conf.all.forwarding=1\n+\n+\t# rt-3 must accept SRv6 packets so ipv6_srh_rcv lets the\n+\t# extension header chain through to local delivery.\n+\tip netns exec \"$rt3\" sysctl -wq net.ipv6.conf.all.seg6_enabled=1\n+\tip netns exec \"$rt3\" \\\n+\t\tsysctl -wq net.ipv6.conf.veth2-rt3.seg6_enabled=1\n+\tip netns exec \"$rt3\" sysctl -wq net.ipv6.conf.lo.seg6_enabled=1\n+\n+\t# Disable HW checksum offload so the kernel software checksum\n+\t# path runs unconditionally and any csum bug surfaces.\n+\tip netns exec \"$rt1\" ethtool -K veth1 tx off rx off\n+\tip netns exec \"$rt2\" ethtool -K veth1-rt2 tx off rx off\n+\tip netns exec \"$rt2\" ethtool -K veth2 tx off rx off\n+\tip netns exec \"$rt3\" ethtool -K veth2-rt3 tx off rx off\n+\n+\t# rt-1: route the End.MAP locator into rt-2.\n+\tip -n \"$rt1\" -6 route add \"$END_MAP_PREFIX\" via 2001:db8:1::2\n+\n+\t# rt-1: a separate H.Encaps route for the SRH-present scenario,\n+\t# wrapping the inner ICMPv6 echo in an outer IPv6+SRH carrying\n+\t# [End.MAP_SID, RT3_SID].\n+\tip -n \"$rt1\" -6 route add \"$RT3_SID/128\" via 2001:db8:1::2 \\\n+\t\tencap seg6 mode encap \\\n+\t\tsegs \"$END_MAP_SID\",\"$RT3_SID\" \\\n+\t\tdev veth1\n+\n+\t# rt-2: End.MAP -- swap DA from the End.MAP SID to RT3_SID\n+\t# (an address on rt-3) and forward via the IPv6 FIB. \"count\"\n+\t# enables the per-behavior counters the drop tests read.\n+\tip -n \"$rt2\" -6 route add \"$END_MAP_PREFIX\" \\\n+\t\tencap seg6mobile action End.MAP mapped_sid \"$RT3_SID\" count \\\n+\t\tdev veth2\n+\n+\t# rt-2: reach RT3_SID (on rt-3's loopback) through the\n+\t# directly connected neighbour 2001:db8:2::2.\n+\tip -n \"$rt2\" -6 route add \"$RT3_SID/128\" via 2001:db8:2::2\n+\n+\t# rt-3: return route for the ICMPv6 echo reply.\n+\tip -n \"$rt3\" -6 route add 2001:db8:1::/64 via 2001:db8:2::1\n+}\n+\n+read_nstat_counter()\n+{\n+\tlocal ns=$1\n+\tlocal name=$2\n+\n+\t# nstat -az reports a counter that has never incremented as 0,\n+\t# which is what we rely on for a clean before/after delta.\n+\tip netns exec \"$ns\" nstat -az \"$name\" \\\n+\t\t| awk -v n=\"$name\" '$1 == n {print $2}'\n+}\n+\n+read_route_errors()\n+{\n+\t# The End.MAP route carries \"count\", so its errors counter\n+\t# increments once for every packet the behavior drops. Reading it\n+\t# binds the negative test to the drop itself rather than to any\n+\t# unrelated loss on the path to rt-3.\n+\tip -n \"$rt2\" -j -s -6 route show \"$END_MAP_PREFIX\" \\\n+\t\t| grep -oE '\"errors\":[0-9]+' | grep -oE '[0-9]+'\n+}\n+\n+test_srh_absent()\n+{\n+\tlocal before after rc=0\n+\n+\tbefore=$(read_nstat_counter \"$rt3\" Icmp6InCsumErrors)\n+\n+\tif ! ip netns exec \"$rt1\" \\\n+\t\t\tping -6 -c 1 -W \"$PING_TIMEOUT_SEC\" \"$END_MAP_SID\" \\\n+\t\t\t\u003e/dev/null 2\u003e\u00261; then\n+\t\trc=1\n+\tfi\n+\n+\tif [ \"$rc\" -eq 0 ]; then\n+\t\tafter=$(read_nstat_counter \"$rt3\" Icmp6InCsumErrors)\n+\t\t[ \"$before\" != \"$after\" ] \u0026\u0026 rc=1\n+\tfi\n+\n+\tlog_test \"$rc\" 0 \"End.MAP forwards an ICMPv6 echo without an SRH\"\n+}\n+\n+test_srh_present()\n+{\n+\tlocal before after rc=0\n+\n+\tbefore=$(read_nstat_counter \"$rt3\" Icmp6InCsumErrors)\n+\n+\tif ! ip netns exec \"$rt1\" \\\n+\t\t\tping -6 -c 1 -W \"$PING_TIMEOUT_SEC\" \"$RT3_SID\" \\\n+\t\t\t\u003e/dev/null 2\u003e\u00261; then\n+\t\trc=1\n+\tfi\n+\n+\tif [ \"$rc\" -eq 0 ]; then\n+\t\tafter=$(read_nstat_counter \"$rt3\" Icmp6InCsumErrors)\n+\t\t[ \"$before\" != \"$after\" ] \u0026\u0026 rc=1\n+\tfi\n+\n+\tlog_test \"$rc\" 0 \"End.MAP preserves an SRH carried by H.Encaps\"\n+}\n+\n+test_srh_inline()\n+{\n+\tlocal before after rc=0\n+\n+\tip -n \"$rt1\" -6 route add 2001:db8:2::2/128 via 2001:db8:1::2 \\\n+\t\tencap seg6 mode inline segs \"$END_MAP_SID\" \\\n+\t\tdev veth1\n+\n+\tbefore=$(read_nstat_counter \"$rt3\" Icmp6InCsumErrors)\n+\n+\tif ! ip netns exec \"$rt1\" \\\n+\t\t\tping -6 -c 1 -W \"$PING_TIMEOUT_SEC\" 2001:db8:2::2 \\\n+\t\t\t\u003e/dev/null 2\u003e\u00261; then\n+\t\trc=1\n+\tfi\n+\n+\tif [ \"$rc\" -eq 0 ]; then\n+\t\tafter=$(read_nstat_counter \"$rt3\" Icmp6InCsumErrors)\n+\t\t[ \"$before\" != \"$after\" ] \u0026\u0026 rc=1\n+\tfi\n+\n+\tlog_test \"$rc\" 0 \"End.MAP preserves L4 csum across mode inline SRH\"\n+}\n+\n+# The drop happens on rt-2's receive path, asynchronously to the\n+# sender, so wait for the errors counter instead of sampling it.\n+expect_end_map_drop()\n+{\n+\tlocal before rc=0\n+\n+\tbefore=$(read_route_errors)\n+\n+\tip netns exec \"$rt1\" \"$HELPER\" -m end-map \"$@\" \\\n+\t\t-s 2001:db8:1::1 -d \"$END_MAP_SID\" \u003e/dev/null 2\u003e\u00261\n+\n+\tslowwait \"$COUNTER_TIMEOUT_SEC\" until_counter_is \"\u003e= $((before + 1))\" \\\n+\t\tread_route_errors \u003e/dev/null || rc=1\n+\t[ \"$(read_route_errors)\" -eq \"$((before + 1))\" ] || rc=1\n+\n+\techo \"$rc\"\n+}\n+\n+test_rh_not_srh()\n+{\n+\tlocal rc\n+\n+\trc=$(expect_end_map_drop --rh-type 0)\n+\tlog_test \"$rc\" 0 \"End.MAP drops a routing header that is not an SRH\"\n+}\n+\n+test_srh_malformed()\n+{\n+\tlocal rc\n+\n+\trc=$(expect_end_map_drop --bad-srh)\n+\tlog_test \"$rc\" 0 \"End.MAP drops a malformed SRH\"\n+}\n+\n+test_srh_exhausted()\n+{\n+\tlocal before_echos before_csum rc=0\n+\n+\tbefore_echos=$(read_nstat_counter \"$rt3\" Icmp6InEchos)\n+\tbefore_csum=$(read_nstat_counter \"$rt3\" Icmp6InCsumErrors)\n+\n+\tip netns exec \"$rt1\" \"$HELPER\" -m end-map \\\n+\t\t-s 2001:db8:1::1 -d \"$END_MAP_SID\" \u003e/dev/null 2\u003e\u00261\n+\n+\tslowwait \"$COUNTER_TIMEOUT_SEC\" \\\n+\t\tuntil_counter_is \"\u003e= $((before_echos + 1))\" \\\n+\t\tread_nstat_counter \"$rt3\" Icmp6InEchos \u003e/dev/null || rc=1\n+\t[ \"$(read_nstat_counter \"$rt3\" Icmp6InCsumErrors)\" -eq \\\n+\t\t\"$before_csum\" ] || rc=1\n+\n+\tlog_test \"$rc\" 0 \"End.MAP patches the L4 csum of an exhausted SRH\"\n+}\n+\n+test_fragmented()\n+{\n+\tlocal before after rc=0\n+\n+\tbefore=$(read_nstat_counter \"$rt3\" Icmp6InCsumErrors)\n+\n+\tif ! ip netns exec \"$rt1\" \\\n+\t\t\tping -6 -c 1 -s 2000 -M dont -W \"$PING_TIMEOUT_SEC\" \\\n+\t\t\t\"$END_MAP_SID\" \u003e/dev/null 2\u003e\u00261; then\n+\t\trc=1\n+\tfi\n+\n+\tif [ \"$rc\" -eq 0 ]; then\n+\t\tafter=$(read_nstat_counter \"$rt3\" Icmp6InCsumErrors)\n+\t\t[ \"$before\" != \"$after\" ] \u0026\u0026 rc=1\n+\tfi\n+\n+\tlog_test \"$rc\" 0 \"End.MAP patches the L4 csum of a fragmented echo\"\n+}\n+\n+test_hoplimit_expiry()\n+{\n+\tlocal before after rc=0\n+\n+\tbefore=$(read_nstat_counter \"$rt2\" Icmp6OutTimeExcds)\n+\n+\tip netns exec \"$rt1\" \\\n+\t\tping -6 -c 1 -t 1 -W \"$PING_TIMEOUT_SEC\" \"$END_MAP_SID\" \\\n+\t\t\u003e/dev/null 2\u003e\u00261\n+\n+\tafter=$(read_nstat_counter \"$rt2\" Icmp6OutTimeExcds)\n+\t[ \"$((after - before))\" -eq 1 ] || rc=1\n+\n+\tlog_test \"$rc\" 0 \"End.MAP delegates Hop Limit expiry to ip6_forward\"\n+}\n+\n+main()\n+{\n+\tcheck_dependencies\n+\tsetup\n+\n+\ttest_srh_absent\n+\ttest_srh_present\n+\ttest_srh_inline\n+\ttest_rh_not_srh\n+\ttest_srh_malformed\n+\ttest_srh_exhausted\n+\ttest_fragmented\n+\ttest_hoplimit_expiry\n+\n+\tprint_log_test_results\n+\texit \"${ret}\"\n+}\n+\n+main \"$@\"\ndiff --git a/tools/testing/selftests/net/srv6_mobile_send.c b/tools/testing/selftests/net/srv6_mobile_send.c\nnew file mode 100644\nindex 0000000000000..01329ec616761\n--- /dev/null\n+++ b/tools/testing/selftests/net/srv6_mobile_send.c\n@@ -0,0 +1,262 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * Author: Yuya Kusakabe (yuya.kusakabe@gmail.com)\n+ *\n+ * Helper for SRv6 Mobile (RFC 9433) selftests.\n+ *\n+ * Usage:\n+ * srv6_mobile_send -m end-map -s \u003csrc\u003e -d \u003cdst\u003e [--rh-type N] [--bad-srh]\n+ */\n+\n+#include \u003carpa/inet.h\u003e\n+#include \u003cassert.h\u003e\n+#include \u003cerrno.h\u003e\n+#include \u003cgetopt.h\u003e\n+#include \u003cnetinet/in.h\u003e\n+#include \u003cnetinet/ip6.h\u003e\n+#include \u003cnetinet/icmp6.h\u003e\n+#include \u003cstdbool.h\u003e\n+#include \u003cstdint.h\u003e\n+#include \u003cstdio.h\u003e\n+#include \u003cstdlib.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003csys/socket.h\u003e\n+#include \u003csys/types.h\u003e\n+#include \u003cunistd.h\u003e\n+\n+/* RFC 8200 Routing header common fields are 4 bytes; an additional\n+ * 4 bytes of type-specific data follow (the Reserved field for the\n+ * deprecated type 0, or first_segment/flags/tag for SRH type 4). The\n+ * segment list then runs in 16-byte units, giving a total of 24 bytes\n+ * for one segment -- which is what ip6r_len = 2 advertises.\n+ */\n+struct srh_one_seg {\n+\tstruct ip6_rthdr rthdr;\n+\tuint32_t type_data;\n+\tstruct in6_addr segment;\n+};\n+\n+/* Built as a struct so the headers are written through typed, aligned\n+ * members rather than casts of a byte array; static_assert keeps it\n+ * exactly the on-wire layout.\n+ */\n+struct end_map_frame {\n+\tstruct ip6_hdr\t\tip6;\n+\tstruct srh_one_seg\tsrh;\n+\tstruct icmp6_hdr\ticmp6;\n+};\n+\n+static_assert(sizeof(struct end_map_frame) ==\n+\t sizeof(struct ip6_hdr) + sizeof(struct srh_one_seg) +\n+\t sizeof(struct icmp6_hdr),\n+\t \"end_map_frame must not contain padding\");\n+\n+enum mode {\n+\tMODE_NONE,\n+\tMODE_END_MAP,\n+};\n+\n+struct cfg {\n+\tenum mode\tmode;\n+\tstruct in6_addr\tsrc6;\n+\tstruct in6_addr\tdst6;\n+\tuint8_t\t\trh_type;\n+\tbool\t\tbad_srh;\n+};\n+\n+static void usage(const char *bin)\n+{\n+\tfprintf(stderr,\n+\t\t\"Usage: %s -m \u003cmode\u003e -s \u003csrc\u003e -d \u003cdst\u003e [opts]\\n\"\n+\t\t\"\\n\"\n+\t\t\"Modes:\\n\"\n+\t\t\" end-map Send IPv6 + SRH + ICMPv6 echo for End.MAP testing\\n\"\n+\t\t\"\\n\"\n+\t\t\"Mode end-map options:\\n\"\n+\t\t\" --rh-type \u003cn\u003e Routing Header type (default 4, the SRH)\\n\"\n+\t\t\" --bad-srh emit an SRH whose Last Entry exceeds its length (drop test)\\n\"\n+\t\t\"\\n\"\n+\t\t\"Exit: 0 sent, 1 failure, 3 invalid arguments.\\n\",\n+\t\tbin);\n+}\n+\n+static int parse_u32(const char *s, uint32_t *out)\n+{\n+\tunsigned long v;\n+\tchar *end;\n+\n+\terrno = 0;\n+\tv = strtoul(s, \u0026end, 0);\n+\tif (errno || !*s || *end || v \u003e 0xffffffffUL)\n+\t\treturn -1;\n+\t*out = (uint32_t)v;\n+\treturn 0;\n+}\n+\n+static int parse_u8(const char *s, uint8_t *out)\n+{\n+\tuint32_t v;\n+\n+\tif (parse_u32(s, \u0026v) || v \u003e 0xff)\n+\t\treturn -1;\n+\t*out = (uint8_t)v;\n+\treturn 0;\n+}\n+\n+static enum mode parse_mode(const char *s)\n+{\n+\tif (!strcmp(s, \"end-map\"))\n+\t\treturn MODE_END_MAP;\n+\treturn MODE_NONE;\n+}\n+\n+static int parse_args(int argc, char **argv, struct cfg *cfg)\n+{\n+\tenum { OPT_RH_TYPE = 256, OPT_BAD_SRH };\n+\tstatic const struct option longopts[] = {\n+\t\t{ \"rh-type\", required_argument, NULL, OPT_RH_TYPE },\n+\t\t{ \"bad-srh\", no_argument, NULL, OPT_BAD_SRH },\n+\t\t{ NULL, 0, NULL, 0 },\n+\t};\n+\tint c;\n+\n+\tcfg-\u003erh_type = 4;\t/* RFC 8754: the SRH is Routing Header type 4 */\n+\twhile ((c = getopt_long(argc, argv, \"m:s:d:\", longopts, NULL))\n+\t != -1) {\n+\t\tswitch (c) {\n+\t\tcase 'm':\n+\t\t\tcfg-\u003emode = parse_mode(optarg);\n+\t\t\tbreak;\n+\t\tcase 's':\n+\t\t\tif (inet_pton(AF_INET6, optarg, \u0026cfg-\u003esrc6) != 1)\n+\t\t\t\treturn -1;\n+\t\t\tbreak;\n+\t\tcase 'd':\n+\t\t\tif (inet_pton(AF_INET6, optarg, \u0026cfg-\u003edst6) != 1)\n+\t\t\t\treturn -1;\n+\t\t\tbreak;\n+\t\tcase OPT_RH_TYPE:\n+\t\t\tif (parse_u8(optarg, \u0026cfg-\u003erh_type))\n+\t\t\t\treturn -1;\n+\t\t\tbreak;\n+\t\tcase OPT_BAD_SRH:\n+\t\t\tcfg-\u003ebad_srh = true;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\treturn -1;\n+\t\t}\n+\t}\n+\tif (cfg-\u003emode == MODE_NONE)\n+\t\treturn -1;\n+\treturn 0;\n+}\n+\n+static uint16_t csum_fold(uint32_t sum)\n+{\n+\twhile (sum \u003e\u003e 16)\n+\t\tsum = (sum \u0026 0xffff) + (sum \u003e\u003e 16);\n+\treturn ~sum;\n+}\n+\n+static uint32_t csum_partial(const void *buf, size_t len, uint32_t sum)\n+{\n+\tconst uint8_t *p = buf;\n+\tuint16_t word;\n+\n+\twhile (len \u003e 1) {\n+\t\tmemcpy(\u0026word, p, sizeof(word));\n+\t\tsum += word;\n+\t\tp += sizeof(word);\n+\t\tlen -= sizeof(word);\n+\t}\n+\tif (len)\n+\t\tsum += *p;\n+\treturn sum;\n+}\n+\n+static uint16_t pseudo_csum(const struct in6_addr *src,\n+\t\t\t const struct in6_addr *dst,\n+\t\t\t uint32_t plen, uint8_t nexthdr,\n+\t\t\t const void *payload, size_t len)\n+{\n+\tuint32_t nh = htonl(nexthdr);\n+\tuint32_t pl = htonl(plen);\n+\tuint32_t sum;\n+\n+\tsum = csum_partial(src, sizeof(*src), 0);\n+\tsum = csum_partial(dst, sizeof(*dst), sum);\n+\tsum = csum_partial(\u0026pl, sizeof(pl), sum);\n+\tsum = csum_partial(\u0026nh, sizeof(nh), sum);\n+\tsum = csum_partial(payload, len, sum);\n+\treturn csum_fold(sum);\n+}\n+\n+static int send_end_map(const struct cfg *cfg)\n+{\n+\tstruct sockaddr_in6 dst_addr = { .sin6_family = AF_INET6 };\n+\tstruct end_map_frame frame = {};\n+\tssize_t res;\n+\tint fd;\n+\n+\tframe.ip6.ip6_flow = htonl(6u \u003c\u003c 28);\n+\tframe.ip6.ip6_plen = htons(sizeof(frame.srh) + sizeof(frame.icmp6));\n+\tframe.ip6.ip6_nxt = IPPROTO_ROUTING;\n+\tframe.ip6.ip6_hops = 64;\n+\tframe.ip6.ip6_src = cfg-\u003esrc6;\n+\tframe.ip6.ip6_dst = cfg-\u003edst6;\n+\n+\tframe.srh.rthdr.ip6r_nxt = IPPROTO_ICMPV6;\n+\tframe.srh.rthdr.ip6r_len = 2;\t\t/* (1 + ip6r_len) * 8 = 24 */\n+\tframe.srh.rthdr.ip6r_type = cfg-\u003erh_type;\n+\tframe.srh.rthdr.ip6r_segleft = 0;\n+\t/* SRH Last Entry is the high byte of the type-specific word. A\n+\t * single segment makes it 0; --bad-srh claims a second segment the\n+\t * header has no room for, which seg6_validate_srh() rejects.\n+\t */\n+\tframe.srh.type_data = htonl((uint32_t)(cfg-\u003ebad_srh ? 1 : 0) \u003c\u003c 24);\n+\tframe.srh.segment = frame.ip6.ip6_dst;\n+\n+\tframe.icmp6.icmp6_type = ICMP6_ECHO_REQUEST;\n+\tframe.icmp6.icmp6_code = 0;\n+\tframe.icmp6.icmp6_dataun.icmp6_un_data16[0] = htons(0x1234);\n+\tframe.icmp6.icmp6_dataun.icmp6_un_data16[1] = htons(1);\n+\tframe.icmp6.icmp6_cksum = pseudo_csum(\u0026frame.ip6.ip6_src,\n+\t\t\t\t\t \u0026frame.ip6.ip6_dst,\n+\t\t\t\t\t sizeof(frame.icmp6),\n+\t\t\t\t\t IPPROTO_ICMPV6, \u0026frame.icmp6,\n+\t\t\t\t\t sizeof(frame.icmp6));\n+\n+\tfd = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW);\n+\tif (fd \u003c 0) {\n+\t\tperror(\"socket\");\n+\t\treturn 1;\n+\t}\n+\tdst_addr.sin6_addr = frame.ip6.ip6_dst;\n+\n+\tres = sendto(fd, \u0026frame, sizeof(frame), 0,\n+\t\t (struct sockaddr *)\u0026dst_addr, sizeof(dst_addr));\n+\tclose(fd);\n+\tif (res != (ssize_t)sizeof(frame)) {\n+\t\tperror(\"sendto\");\n+\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+int main(int argc, char **argv)\n+{\n+\tstruct cfg cfg = {};\n+\n+\tif (parse_args(argc, argv, \u0026cfg)) {\n+\t\tusage(argv[0]);\n+\t\treturn 3;\n+\t}\n+\n+\tswitch (cfg.mode) {\n+\tcase MODE_END_MAP:\n+\t\treturn send_end_map(\u0026cfg);\n+\tdefault:\n+\t\tusage(argv[0]);\n+\t\treturn 3;\n+\t}\n+}\n"
} |
| 2/1 | 2026/09/18 14:50 | agent | patch-evaluator |
3mModel:core Args: null Results: {
"EnableConfigs": [
"IPV6",
"IPV6_SEG6_LWTUNNEL",
"IPV6_SEG6_MOBILE"
],
"FocusSymbols": [
"seg6_mobile_build_state",
"seg6_mobile_input",
"input_action_end_map",
"seg6_mobile_advance_da",
"seg6_mobile_fill_encap",
"seg6_mobile_parse_attrs"
],
"Reasoning": "The patch implements the SRv6 Mobile User Plane (RFC 9433) behaviors under the lightweight tunnel infrastructure (LWTUNNEL_ENCAP_SEG6_MOBILE). It adds new netlink configuration parsing and route encapsulation building (seg6_mobile_build_state, seg6_mobile_parse_attrs), per-CPU counter handling, and packet processing hooks (seg6_mobile_input, input_action_end_map, seg6_mobile_advance_da) that mutate IPv6 destination addresses and recalculate L4 checksums (TCP, UDP, ICMPv6). This reachable core networking code is fully testable in virtual environments via netlink and virtual interfaces (veth/tun/tap/loopback) and warrants fuzzing.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.
IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.
================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.
- REACHABILITY IS THE PRIMARY GATE:
Fuzzing can only discover bugs in code that can actually execute in standard virtualized
environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
regardless of whether it adds assertions or complex logic.
- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
Patch authors routinely label changes as "cleanups", "refactorings", or state
"No functional change intended". Do NOT take these claims at face value.
Code refactorings that rearrange logic, introduce helper functions, or alter state management
in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
If reachable executable code is modified or refactored, it MUST be fuzzed.
- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
Even if the author believes the invariant always holds, fuzzing is essential to verify whether
an unusual sequence of operations can violate it.
================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:
- Non-kernel and non-executable changes:
* Modifications to Documentation/, comments, or spelling fixes.
* User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
that do not affect the compiled kernel image (vmlinux) or kernel modules.
* Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
that do not alter control flow or data structures.
* Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
* Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
* Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
physical PCI hot-unplug or manual sysfs driver unbinding.
* Code paths exclusive to architectures other than the target architecture.
================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
* Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
* Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
* Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
* Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).
================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:
- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
Do NOT list generic, ubiquitous functions called by almost every program in the corpus
(including, but not limited to: general memory allocators and deallocators, page fault
and trap handlers, or core synchronization primitives; this is not an exhaustive list).
Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
which severely dilutes fuzzing effort away from the actual changes.
- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
List functions that specifically implement the logic being added or altered, or direct API entrypoints
for the subsystem feature under review.
- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).
================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 226895d2aaa75cc1d5087f3e7576b94879980fb8
Author: syz-cluster <triage@syzkaller.com>
Date: Fri Sep 18 14:50:22 2026 +0000
syz-cluster: applied patch under review
diff --git a/include/net/seg6.h b/include/net/seg6.h
index 82b3fbbcbb935..789e9bcc47738 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
+#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 reduced);
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 229655ef792f6..6e48f79c548e2 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,
};
diff --git a/include/uapi/linux/seg6_mobile.h b/include/uapi/linux/seg6_mobile.h
new file mode 100644
index 0000000000000..068ab91b2873e
--- /dev/null
+++ b/include/uapi/linux/seg6_mobile.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * SRv6 Mobile User Plane implementation
+ *
+ * Author:
+ * Yuya Kusakabe <yuya.kusakabe@gmail.com>
+ */
+#ifndef _UAPI_LINUX_SEG6_MOBILE_H
+#define _UAPI_LINUX_SEG6_MOBILE_H
+
+enum {
+ SEG6_MOBILE_UNSPEC,
+ SEG6_MOBILE_ACTION,
+ SEG6_MOBILE_MAPPED_SID,
+ SEG6_MOBILE_COUNTERS,
+ __SEG6_MOBILE_MAX,
+};
+
+#define SEG6_MOBILE_MAX (__SEG6_MOBILE_MAX - 1)
+
+enum {
+ SEG6_MOBILE_ACTION_UNSPEC = 0,
+ /* swap IPv6 DA with the mapped SID, leave SRH untouched */
+ SEG6_MOBILE_ACTION_END_MAP = 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 b01a395d9a966..4476293ccb37f 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 c3806c6ac96f9..e094b835a1186 100644
--- a/net/ipv6/Kconfig
+++ b/net/ipv6/Kconfig
@@ -314,6 +314,16 @@ config IPV6_SEG6_BPF
depends on IPV6_SEG6_LWTUNNEL
depends on IPV6 = y
+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, 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 cf5e01f83ce3d..95961f1804390 100644
--- a/net/ipv6/Makefile
+++ b/net/ipv6/Makefile
@@ -24,6 +24,7 @@ ipv6-$(CONFIG_SYN_COOKIES) += syncookies.o
ipv6-$(CONFIG_NETLABEL) += calipso.o
ipv6-$(CONFIG_IPV6_SEG6_LWTUNNEL) += seg6_iptunnel.o seg6_local.o
ipv6-$(CONFIG_IPV6_SEG6_HMAC) += seg6_hmac.o
+ipv6-$(CONFIG_IPV6_SEG6_MOBILE) += seg6_mobile.o
ipv6-$(CONFIG_IPV6_RPL_LWTUNNEL) += rpl_iptunnel.o
ipv6-$(CONFIG_IPV6_IOAM6_LWTUNNEL) += ioam6_iptunnel.o
diff --git a/net/ipv6/seg6.c b/net/ipv6/seg6.c
index 62a7eb7792026..14626b15abd5c 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;
+ err = seg6_mobile_init();
+ if (err)
+ goto out_unregister_local;
+
pr_info("Segment Routing with IPv6\n");
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)
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 0000000000000..f3757ab458489
--- /dev/null
+++ b/net/ipv6/seg6_mobile.c
@@ -0,0 +1,701 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SRv6 Mobile User Plane implementation
+ *
+ * Author:
+ * Yuya Kusakabe <yuya.kusakabe@gmail.com>
+ */
+
+#include <linux/icmpv6.h>
+#include <linux/in6.h>
+#include <linux/ipv6.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/tcp.h>
+#include <linux/types.h>
+#include <linux/udp.h>
+#include <net/checksum.h>
+#include <net/ipv6.h>
+#include <net/lwtunnel.h>
+#include <net/seg6.h>
+#ifdef CONFIG_IPV6_SEG6_HMAC
+#include <net/seg6_hmac.h>
+#endif
+#include <uapi/linux/seg6_mobile.h>
+
+#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 mapped_sid;
+ 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;
+}
+
+/* ABSENT is told apart from MALFORMED so that a behavior which accepts
+ * SRH-less packets still drops a malformed SRH
+ */
+enum seg6_mobile_srh_state {
+ SEG6_MOBILE_SRH_ABSENT,
+ SEG6_MOBILE_SRH_PRESENT,
+ SEG6_MOBILE_SRH_MALFORMED,
+};
+
+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 = 0;
+ int hdr_proto;
+ int flags = 0;
+
+ srh = seg6_get_srh(skb, 0);
+ if (srh) {
+#ifdef CONFIG_IPV6_SEG6_HMAC
+ if (!seg6_hmac_validate_skb(skb)) {
+ *state = SEG6_MOBILE_SRH_MALFORMED;
+ return NULL;
+ }
+#endif
+ *state = SEG6_MOBILE_SRH_PRESENT;
+ return srh;
+ }
+
+ hdr_proto = ipv6_find_hdr(skb, &srhoff, IPPROTO_ROUTING, NULL, &flags);
+ *state = hdr_proto == -ENOENT ? SEG6_MOBILE_SRH_ABSENT
+ : SEG6_MOBILE_SRH_MALFORMED;
+ return NULL;
+}
+
+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;
+}
+
+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 = (struct udphdr *)(skb->data + l4_off);
+
+ /* zero UDPv6 checksum on a non-offloaded skb means "no checksum" */
+ if (!uh->check && skb->ip_summed != CHECKSUM_PARTIAL)
+ return NULL;
+ return &uh->check;
+ }
+ case IPPROTO_ICMPV6:
+ return &((struct icmp6hdr *)(skb->data + l4_off))->icmp6_cksum;
+ }
+ return NULL;
+}
+
+/* rewrite DA; the L4 checksum follows only when the receiver will verify
+ * it against the new DA, i.e. no SRH is left for it to process
+ */
+static int seg6_mobile_advance_da(struct sk_buff *skb,
+ const struct in6_addr *nh, bool srh_present)
+{
+ int l4_off = 0, l4_hlen = 0;
+ struct in6_addr old_da;
+ struct ipv6hdr *ip6h;
+ __be16 frag_off;
+ u8 nexthdr = 0;
+ __sum16 *csum;
+ int write_len;
+
+ if (!pskb_may_pull(skb, sizeof(*ip6h)))
+ return -EINVAL;
+
+ ip6h = ipv6_hdr(skb);
+ write_len = sizeof(*ip6h);
+
+ if (!srh_present) {
+ nexthdr = ip6h->nexthdr;
+ l4_off = ipv6_skip_exthdr(skb, sizeof(*ip6h), &nexthdr,
+ &frag_off);
+ if (l4_off < 0)
+ return -EINVAL;
+
+ /* only the first fragment carries the L4 header; frag_off is the
+ * raw field, M flag included
+ */
+ if (!(frag_off & htons(IP6_OFFSET)))
+ l4_hlen = seg6_mobile_l4_csum_hlen(nexthdr);
+ if (l4_hlen)
+ write_len = l4_off + l4_hlen;
+ }
+
+ if (skb_ensure_writable(skb, write_len))
+ return -ENOMEM;
+
+ /* skb_ensure_writable() may change skb pointers; evaluate ip6h again */
+ ip6h = ipv6_hdr(skb);
+ old_da = ip6h->daddr;
+
+ csum = 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);
+ if (nexthdr == IPPROTO_UDP && !*csum)
+ *csum = CSUM_MANGLED_0;
+ } else if (skb->ip_summed == CHECKSUM_COMPLETE) {
+ /* no L4 patch, so skb->csum must track the DA change itself */
+ update_csum_diff16(skb, old_da.s6_addr32, (__be32 *)nh);
+ }
+
+ ip6h->daddr = *nh;
+ skb_clear_hash(skb);
+
+ return 0;
+}
+
+static int seg6_mobile_forward(struct sk_buff *skb)
+{
+ seg6_lookup_nexthop(skb, NULL, 0);
+ return dst_input(skb);
+}
+
+/* replace DA with the mapped SID and forward, leaving the SRH untouched */
+static int input_action_end_map(struct sk_buff *skb,
+ struct seg6_mobile_lwt *slwt)
+{
+ enum seg6_mobile_srh_state srh_state;
+ struct ipv6_sr_hdr *srh;
+
+ srh = seg6_mobile_get_and_validate_srh(skb, &srh_state);
+ if (srh_state == SEG6_MOBILE_SRH_MALFORMED)
+ goto drop;
+
+ /* SL == 0: SRH spent, L4 is verified against the new DA */
+ if (seg6_mobile_advance_da(skb, &slwt->mapped_sid,
+ srh && srh->segments_left))
+ goto drop;
+
+ return seg6_mobile_forward(skb);
+
+drop:
+ kfree_skb(skb);
+ return -EINVAL;
+}
+
+static int parse_nla_mapped_sid(struct nlattr **attrs,
+ struct seg6_mobile_lwt *slwt,
+ struct netlink_ext_ack *extack)
+{
+ memcpy(&slwt->mapped_sid, nla_data(attrs[SEG6_MOBILE_MAPPED_SID]),
+ sizeof(struct in6_addr));
+
+ return 0;
+}
+
+static int put_nla_mapped_sid(struct sk_buff *skb, struct seg6_mobile_lwt *slwt)
+{
+ if (nla_put_in6_addr(skb, SEG6_MOBILE_MAPPED_SID, &slwt->mapped_sid))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int cmp_nla_mapped_sid(struct seg6_mobile_lwt *a, struct seg6_mobile_lwt *b)
+{
+ return memcmp(&a->mapped_sid, &b->mapped_sid, sizeof(struct in6_addr));
+}
+
+static const struct
+nla_policy seg6_mobile_counters_policy[SEG6_MOBILE_CNT_MAX + 1] = {
+ [SEG6_MOBILE_CNT_PACKETS] = { .type = NLA_U64 },
+ [SEG6_MOBILE_CNT_BYTES] = { .type = NLA_U64 },
+ [SEG6_MOBILE_CNT_ERRORS] = { .type = 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 = nla_parse_nested(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 = seg6_mobile_alloc_pcpu_counters(GFP_KERNEL);
+ if (!pcounters)
+ return -ENOMEM;
+
+ slwt->pcpu_counters = 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 *slwt)
+{
+ struct seg6_mobile_counters counters = { 0, 0, 0 };
+ struct nlattr *nest;
+ int rc, i;
+
+ nest = 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 = per_cpu_ptr(slwt->pcpu_counters, i);
+ do {
+ start = u64_stats_fetch_begin(&pcounters->syncp);
+
+ packets = u64_stats_read(&pcounters->packets);
+ bytes = u64_stats_read(&pcounters->bytes);
+ errors = u64_stats_read(&pcounters->errors);
+
+ } while (u64_stats_fetch_retry(&pcounters->syncp, start));
+
+ counters.packets += packets;
+ counters.bytes += bytes;
+ counters.errors += errors;
+ }
+
+ rc = 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[] = {
+ {
+ .action = SEG6_MOBILE_ACTION_END_MAP,
+ .attrs = SEG6_MOBILE_F_ATTR(SEG6_MOBILE_MAPPED_SID),
+ .optattrs = SEG6_F_MOBILE_COUNTERS,
+ .input = input_action_end_map,
+ },
+};
+
+static const struct seg6_mobile_action_param
+seg6_mobile_action_params[SEG6_MOBILE_MAX + 1] = {
+ [SEG6_MOBILE_MAPPED_SID] = {
+ .parse = parse_nla_mapped_sid,
+ .put = put_nla_mapped_sid,
+ .cmp = cmp_nla_mapped_sid,
+ },
+ [SEG6_MOBILE_COUNTERS] = {
+ .parse = parse_nla_counters,
+ .put = put_nla_counters,
+ .cmp = cmp_nla_counters,
+ .destroy = destroy_attr_counters,
+ },
+};
+
+static const struct nla_policy
+seg6_mobile_policy[SEG6_MOBILE_MAX + 1] = {
+ [SEG6_MOBILE_ACTION] = { .type = NLA_U32 },
+ [SEG6_MOBILE_MAPPED_SID] = NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)),
+ [SEG6_MOBILE_COUNTERS] = { .type = NLA_NESTED },
+};
+
+static const struct seg6_mobile_action_desc *
+seg6_mobile_get_action_desc(int action)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(seg6_mobile_action_table); i++) {
+ if (seg6_mobile_action_table[i].action == 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 = SEG6_MOBILE_ACTION + 1; i < max_parsed; i++) {
+ if (!(parsed_attrs & SEG6_MOBILE_F_ATTR(i)))
+ continue;
+
+ param = &seg6_mobile_action_params[i];
+ if (param->destroy)
+ param->destroy(slwt);
+ }
+}
+
+static void destroy_attrs(struct seg6_mobile_lwt *slwt)
+{
+ unsigned long attrs = 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 = 0;
+ int i, err;
+
+ desc = slwt->desc;
+
+ if (WARN_ON_ONCE(desc->attrs & desc->optattrs))
+ return -EINVAL;
+
+ for (i = SEG6_MOBILE_ACTION + 1; i <= SEG6_MOBILE_MAX; i++) {
+ bool required = desc->attrs & SEG6_MOBILE_F_ATTR(i);
+ bool optional = 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 = -EINVAL;
+ goto err;
+ }
+
+ if (!attrs[i])
+ continue;
+
+ param = &seg6_mobile_action_params[i];
+ err = param->parse(attrs, slwt, extack);
+ if (err < 0)
+ goto err;
+
+ if (optional)
+ parsed_optattrs |= SEG6_MOBILE_F_ATTR(i);
+ }
+
+ slwt->parsed_optattrs = 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 = 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 = skb_dst(skb);
+ struct seg6_mobile_lwt *slwt;
+ unsigned int len = skb->len;
+ int rc;
+
+ if (skb->protocol != htons(ETH_P_IPV6)) {
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ slwt = seg6_mobile_lwtunnel(orig_dst->lwtstate);
+
+ rc = 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 != AF_INET6)
+ return -EINVAL;
+
+ err = nla_parse_nested(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 = 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 = lwtunnel_state_alloc(sizeof(*slwt));
+ if (!newts)
+ return -ENOMEM;
+
+ slwt = seg6_mobile_lwtunnel(newts);
+ slwt->action = desc->action;
+ slwt->desc = desc;
+
+ err = seg6_mobile_parse_attrs(tb, slwt, extack);
+ if (err < 0) {
+ kfree(newts);
+ return err;
+ }
+
+ newts->type = LWTUNNEL_ENCAP_SEG6_MOBILE;
+ newts->flags = LWTUNNEL_STATE_INPUT_REDIRECT;
+
+ *ts = 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 = 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 = slwt->desc->attrs | slwt->parsed_optattrs;
+ for (i = SEG6_MOBILE_ACTION + 1; i <= SEG6_MOBILE_MAX; i++) {
+ if (!(attrs & SEG6_MOBILE_F_ATTR(i)))
+ continue;
+
+ param = &seg6_mobile_action_params[i];
+ err = 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 = seg6_mobile_lwtunnel(lwt);
+ unsigned long attrs;
+ int nlsize;
+
+ nlsize = nla_total_size(sizeof(u32)); /* SEG6_MOBILE_ACTION */
+
+ attrs = slwt->desc->attrs | slwt->parsed_optattrs;
+ if (attrs & SEG6_MOBILE_F_ATTR(SEG6_MOBILE_MAPPED_SID))
+ nlsize += nla_total_size(sizeof(struct in6_addr));
+
+ if (attrs & SEG6_F_MOBILE_COUNTERS)
+ nlsize += 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 = seg6_mobile_lwtunnel(a);
+ struct seg6_mobile_lwt *slwt_b = seg6_mobile_lwtunnel(b);
+ const struct seg6_mobile_action_param *param;
+ unsigned long attrs_a, attrs_b;
+ int i;
+
+ if (slwt_a->action != slwt_b->action)
+ return 1;
+
+ attrs_a = slwt_a->desc->attrs | slwt_a->parsed_optattrs;
+ attrs_b = slwt_b->desc->attrs | slwt_b->parsed_optattrs;
+
+ if (attrs_a != attrs_b)
+ return 1;
+
+ for (i = SEG6_MOBILE_ACTION + 1; i <= SEG6_MOBILE_MAX; i++) {
+ if (!(attrs_a & SEG6_MOBILE_F_ATTR(i)))
+ continue;
+
+ param = &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 = {
+ .build_state = seg6_mobile_build_state,
+ .destroy_state = seg6_mobile_destroy_state,
+ .input = seg6_mobile_input,
+ .fill_encap = seg6_mobile_fill_encap,
+ .get_encap_size = seg6_mobile_get_encap_size,
+ .cmp_encap = seg6_mobile_cmp_encap,
+ .owner = 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);
+}
diff --git a/tools/testing/selftests/net/.gitignore b/tools/testing/selftests/net/.gitignore
index c9f46031ac73b..9afff0d83ddec 100644
--- a/tools/testing/selftests/net/.gitignore
+++ b/tools/testing/selftests/net/.gitignore
@@ -42,6 +42,7 @@ socket
so_incoming_cpu
so_netns_cookie
so_rcv_listener
+srv6_mobile_send
stress_reuseport_listen
tap
tcp_fastopen_backup_key
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 3ee3378f8b26e..105e0f5077918 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -96,6 +96,7 @@ TEST_PROGS := \
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 \
@@ -165,6 +166,7 @@ TEST_GEN_FILES := \
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/config b/tools/testing/selftests/net/config
index 30d5fcb09a831..afe9a51631679 100644
--- a/tools/testing/selftests/net/config
+++ b/tools/testing/selftests/net/config
@@ -49,6 +49,7 @@ CONFIG_IPV6_ROUTE_INFO=y
CONFIG_IPV6_ROUTER_PREF=y
CONFIG_IPV6_RPL_LWTUNNEL=y
CONFIG_IPV6_SEG6_LWTUNNEL=y
+CONFIG_IPV6_SEG6_MOBILE=y
CONFIG_IPV6_SIT=y
CONFIG_IPV6_VTI=y
CONFIG_IPVLAN=m
diff --git a/tools/testing/selftests/net/srv6_end_map_test.sh b/tools/testing/selftests/net/srv6_end_map_test.sh
new file mode 100755
index 0000000000000..a4efcb58a1e4d
--- /dev/null
+++ b/tools/testing/selftests/net/srv6_end_map_test.sh
@@ -0,0 +1,400 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# author: Yuya Kusakabe <yuya.kusakabe@gmail.com>
+
+# 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 H.Encaps scenario).
+#
+# 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 -- the forwarding cases assert
+# that the counter does not move.
+#
+# Eight 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. RH not an SRH -- a C helper sends a packet whose Routing
+# Header is type 0 rather than 4; End.MAP must
+# drop it. The behavior's own errors counter
+# binds the assertion to the drop.
+# 5. SRH malformed -- the helper sends an SRH whose Last Entry
+# exceeds its length; End.MAP must drop it.
+# 6. SRH exhausted -- the helper sends a valid SRH with Segments
+# Left 0, so the IPv6 DA already is the final
+# destination; End.MAP must patch the L4
+# checksum as in the SRH-absent case and the
+# receiver must accept the echo.
+# 7. Fragmented -- an oversized echo without an SRH is fragmented
+# by rt-1; only the first fragment carries the
+# ICMPv6 header, and End.MAP must patch its
+# checksum so the reassembled echo verifies at
+# rt-3.
+# 8. 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=4
+readonly COUNTER_TIMEOUT_SEC=2
+readonly END_MAP_PREFIX="2001:db8:f::/64"
+readonly END_MAP_SID="2001:db8:f::1"
+readonly RT3_SID="2001:db8:3::3"
+HELPER_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
+readonly HELPER_DIR
+readonly HELPER="${HELPER_DIR}/srv6_mobile_send"
+
+ret=0
+nsuccess=0
+nfail=0
+
+PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}
+
+log_test()
+{
+ local rc=$1
+ local expected=$2
+ local msg="$3"
+
+ if [ "${rc}" -eq "${expected}" ]; then
+ nsuccess=$((nsuccess + 1))
+ printf "\n TEST: %-60s [ OK ]\n" "${msg}"
+ else
+ ret=1
+ nfail=$((nfail + 1))
+ printf "\n TEST: %-60s [FAIL]\n" "${msg}"
+ if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
+ echo
+ echo "hit enter to continue, 'q' to quit"
+ read -r a
+ [ "$a" = "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 nstat sysctl ethtool; 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
+
+ # shellcheck disable=SC2154 # variables assigned by setup_ns
+ for ns in "$rt1" "$rt2" "$rt3"; do
+ ip -n "$ns" link set lo up
+ done
+
+ 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=1
+
+ # 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=1
+ ip netns exec "$rt3" \
+ sysctl -wq net.ipv6.conf.veth2-rt3.seg6_enabled=1
+ ip netns exec "$rt3" sysctl -wq net.ipv6.conf.lo.seg6_enabled=1
+
+ # 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
+ ip netns exec "$rt2" ethtool -K veth1-rt2 tx off rx off
+ ip netns exec "$rt2" ethtool -K veth2 tx off rx off
+ ip netns exec "$rt3" ethtool -K veth2-rt3 tx off rx off
+
+ # 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 drop tests read.
+ ip -n "$rt2" -6 route add "$END_MAP_PREFIX" \
+ encap seg6mobile action End.MAP mapped_sid "$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=$1
+ local name=$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="$name" '$1 == 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_srh_absent()
+{
+ local before after rc=0
+
+ before=$(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=1
+ fi
+
+ if [ "$rc" -eq 0 ]; then
+ after=$(read_nstat_counter "$rt3" Icmp6InCsumErrors)
+ [ "$before" != "$after" ] && rc=1
+ fi
+
+ log_test "$rc" 0 "End.MAP forwards an ICMPv6 echo without an SRH"
+}
+
+test_srh_present()
+{
+ local before after rc=0
+
+ before=$(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=1
+ fi
+
+ if [ "$rc" -eq 0 ]; then
+ after=$(read_nstat_counter "$rt3" Icmp6InCsumErrors)
+ [ "$before" != "$after" ] && rc=1
+ fi
+
+ log_test "$rc" 0 "End.MAP preserves an SRH carried by H.Encaps"
+}
+
+test_srh_inline()
+{
+ local before after rc=0
+
+ 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=$(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=1
+ fi
+
+ if [ "$rc" -eq 0 ]; then
+ after=$(read_nstat_counter "$rt3" Icmp6InCsumErrors)
+ [ "$before" != "$after" ] && rc=1
+ fi
+
+ log_test "$rc" 0 "End.MAP preserves L4 csum across mode inline SRH"
+}
+
+# The drop happens on rt-2's receive path, asynchronously to the
+# sender, so wait for the errors counter instead of sampling it.
+expect_end_map_drop()
+{
+ local before rc=0
+
+ before=$(read_route_errors)
+
+ ip netns exec "$rt1" "$HELPER" -m end-map "$@" \
+ -s 2001:db8:1::1 -d "$END_MAP_SID" >/dev/null 2>&1
+
+ slowwait "$COUNTER_TIMEOUT_SEC" until_counter_is ">= $((before + 1))" \
+ read_route_errors >/dev/null || rc=1
+ [ "$(read_route_errors)" -eq "$((before + 1))" ] || rc=1
+
+ echo "$rc"
+}
+
+test_rh_not_srh()
+{
+ local rc
+
+ rc=$(expect_end_map_drop --rh-type 0)
+ log_test "$rc" 0 "End.MAP drops a routing header that is not an SRH"
+}
+
+test_srh_malformed()
+{
+ local rc
+
+ rc=$(expect_end_map_drop --bad-srh)
+ log_test "$rc" 0 "End.MAP drops a malformed SRH"
+}
+
+test_srh_exhausted()
+{
+ local before_echos before_csum rc=0
+
+ before_echos=$(read_nstat_counter "$rt3" Icmp6InEchos)
+ before_csum=$(read_nstat_counter "$rt3" Icmp6InCsumErrors)
+
+ ip netns exec "$rt1" "$HELPER" -m end-map \
+ -s 2001:db8:1::1 -d "$END_MAP_SID" >/dev/null 2>&1
+
+ slowwait "$COUNTER_TIMEOUT_SEC" \
+ until_counter_is ">= $((before_echos + 1))" \
+ read_nstat_counter "$rt3" Icmp6InEchos >/dev/null || rc=1
+ [ "$(read_nstat_counter "$rt3" Icmp6InCsumErrors)" -eq \
+ "$before_csum" ] || rc=1
+
+ log_test "$rc" 0 "End.MAP patches the L4 csum of an exhausted SRH"
+}
+
+test_fragmented()
+{
+ local before after rc=0
+
+ before=$(read_nstat_counter "$rt3" Icmp6InCsumErrors)
+
+ if ! ip netns exec "$rt1" \
+ ping -6 -c 1 -s 2000 -M dont -W "$PING_TIMEOUT_SEC" \
+ "$END_MAP_SID" >/dev/null 2>&1; then
+ rc=1
+ fi
+
+ if [ "$rc" -eq 0 ]; then
+ after=$(read_nstat_counter "$rt3" Icmp6InCsumErrors)
+ [ "$before" != "$after" ] && rc=1
+ fi
+
+ log_test "$rc" 0 "End.MAP patches the L4 csum of a fragmented echo"
+}
+
+test_hoplimit_expiry()
+{
+ local before after rc=0
+
+ before=$(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=$(read_nstat_counter "$rt2" Icmp6OutTimeExcds)
+ [ "$((after - before))" -eq 1 ] || rc=1
+
+ 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_rh_not_srh
+ test_srh_malformed
+ test_srh_exhausted
+ test_fragmented
+ 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 0000000000000..01329ec616761
--- /dev/null
+++ b/tools/testing/selftests/net/srv6_mobile_send.c
@@ -0,0 +1,262 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Author: Yuya Kusakabe (yuya.kusakabe@gmail.com)
+ *
+ * Helper for SRv6 Mobile (RFC 9433) selftests.
+ *
+ * Usage:
+ * srv6_mobile_send -m end-map -s <src> -d <dst> [--rh-type N] [--bad-srh]
+ */
+
+#include <arpa/inet.h>
+#include <assert.h>
+#include <errno.h>
+#include <getopt.h>
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+/* 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 = 2 advertises.
+ */
+struct srh_one_seg {
+ struct ip6_rthdr rthdr;
+ uint32_t type_data;
+ struct in6_addr segment;
+};
+
+/* Built as a struct so the headers are written through typed, aligned
+ * members rather than casts of a byte array; static_assert keeps it
+ * exactly the on-wire layout.
+ */
+struct end_map_frame {
+ struct ip6_hdr ip6;
+ struct srh_one_seg srh;
+ struct icmp6_hdr icmp6;
+};
+
+static_assert(sizeof(struct end_map_frame) ==
+ sizeof(struct ip6_hdr) + sizeof(struct srh_one_seg) +
+ sizeof(struct icmp6_hdr),
+ "end_map_frame must not contain padding");
+
+enum mode {
+ MODE_NONE,
+ MODE_END_MAP,
+};
+
+struct cfg {
+ enum mode mode;
+ struct in6_addr src6;
+ struct in6_addr dst6;
+ uint8_t rh_type;
+ bool bad_srh;
+};
+
+static void usage(const char *bin)
+{
+ fprintf(stderr,
+ "Usage: %s -m <mode> -s <src> -d <dst> [opts]\n"
+ "\n"
+ "Modes:\n"
+ " end-map Send IPv6 + SRH + ICMPv6 echo for End.MAP testing\n"
+ "\n"
+ "Mode end-map options:\n"
+ " --rh-type <n> Routing Header type (default 4, the SRH)\n"
+ " --bad-srh emit an SRH whose Last Entry exceeds its length (drop test)\n"
+ "\n"
+ "Exit: 0 sent, 1 failure, 3 invalid arguments.\n",
+ bin);
+}
+
+static int parse_u32(const char *s, uint32_t *out)
+{
+ unsigned long v;
+ char *end;
+
+ errno = 0;
+ v = strtoul(s, &end, 0);
+ if (errno || !*s || *end || v > 0xffffffffUL)
+ return -1;
+ *out = (uint32_t)v;
+ return 0;
+}
+
+static int parse_u8(const char *s, uint8_t *out)
+{
+ uint32_t v;
+
+ if (parse_u32(s, &v) || v > 0xff)
+ return -1;
+ *out = (uint8_t)v;
+ return 0;
+}
+
+static enum mode parse_mode(const char *s)
+{
+ if (!strcmp(s, "end-map"))
+ return MODE_END_MAP;
+ return MODE_NONE;
+}
+
+static int parse_args(int argc, char **argv, struct cfg *cfg)
+{
+ enum { OPT_RH_TYPE = 256, OPT_BAD_SRH };
+ static const struct option longopts[] = {
+ { "rh-type", required_argument, NULL, OPT_RH_TYPE },
+ { "bad-srh", no_argument, NULL, OPT_BAD_SRH },
+ { NULL, 0, NULL, 0 },
+ };
+ int c;
+
+ cfg->rh_type = 4; /* RFC 8754: the SRH is Routing Header type 4 */
+ while ((c = getopt_long(argc, argv, "m:s:d:", longopts, NULL))
+ != -1) {
+ switch (c) {
+ case 'm':
+ cfg->mode = parse_mode(optarg);
+ break;
+ case 's':
+ if (inet_pton(AF_INET6, optarg, &cfg->src6) != 1)
+ return -1;
+ break;
+ case 'd':
+ if (inet_pton(AF_INET6, optarg, &cfg->dst6) != 1)
+ return -1;
+ break;
+ case OPT_RH_TYPE:
+ if (parse_u8(optarg, &cfg->rh_type))
+ return -1;
+ break;
+ case OPT_BAD_SRH:
+ cfg->bad_srh = true;
+ break;
+ default:
+ return -1;
+ }
+ }
+ if (cfg->mode == MODE_NONE)
+ return -1;
+ return 0;
+}
+
+static uint16_t csum_fold(uint32_t sum)
+{
+ while (sum >> 16)
+ sum = (sum & 0xffff) + (sum >> 16);
+ return ~sum;
+}
+
+static uint32_t csum_partial(const void *buf, size_t len, uint32_t sum)
+{
+ const uint8_t *p = buf;
+ uint16_t word;
+
+ while (len > 1) {
+ memcpy(&word, p, sizeof(word));
+ sum += word;
+ p += sizeof(word);
+ len -= sizeof(word);
+ }
+ if (len)
+ sum += *p;
+ return sum;
+}
+
+static uint16_t pseudo_csum(const struct in6_addr *src,
+ const struct in6_addr *dst,
+ uint32_t plen, uint8_t nexthdr,
+ const void *payload, size_t len)
+{
+ uint32_t nh = htonl(nexthdr);
+ uint32_t pl = htonl(plen);
+ uint32_t sum;
+
+ sum = csum_partial(src, sizeof(*src), 0);
+ sum = csum_partial(dst, sizeof(*dst), sum);
+ sum = csum_partial(&pl, sizeof(pl), sum);
+ sum = csum_partial(&nh, sizeof(nh), sum);
+ sum = csum_partial(payload, len, sum);
+ return csum_fold(sum);
+}
+
+static int send_end_map(const struct cfg *cfg)
+{
+ struct sockaddr_in6 dst_addr = { .sin6_family = AF_INET6 };
+ struct end_map_frame frame = {};
+ ssize_t res;
+ int fd;
+
+ frame.ip6.ip6_flow = htonl(6u << 28);
+ frame.ip6.ip6_plen = htons(sizeof(frame.srh) + sizeof(frame.icmp6));
+ frame.ip6.ip6_nxt = IPPROTO_ROUTING;
+ frame.ip6.ip6_hops = 64;
+ frame.ip6.ip6_src = cfg->src6;
+ frame.ip6.ip6_dst = cfg->dst6;
+
+ frame.srh.rthdr.ip6r_nxt = IPPROTO_ICMPV6;
+ frame.srh.rthdr.ip6r_len = 2; /* (1 + ip6r_len) * 8 = 24 */
+ frame.srh.rthdr.ip6r_type = cfg->rh_type;
+ frame.srh.rthdr.ip6r_segleft = 0;
+ /* SRH Last Entry is the high byte of the type-specific word. A
+ * single segment makes it 0; --bad-srh claims a second segment the
+ * header has no room for, which seg6_validate_srh() rejects.
+ */
+ frame.srh.type_data = htonl((uint32_t)(cfg->bad_srh ? 1 : 0) << 24);
+ frame.srh.segment = frame.ip6.ip6_dst;
+
+ frame.icmp6.icmp6_type = ICMP6_ECHO_REQUEST;
+ frame.icmp6.icmp6_code = 0;
+ frame.icmp6.icmp6_dataun.icmp6_un_data16[0] = htons(0x1234);
+ frame.icmp6.icmp6_dataun.icmp6_un_data16[1] = htons(1);
+ frame.icmp6.icmp6_cksum = pseudo_csum(&frame.ip6.ip6_src,
+ &frame.ip6.ip6_dst,
+ sizeof(frame.icmp6),
+ IPPROTO_ICMPV6, &frame.icmp6,
+ sizeof(frame.icmp6));
+
+ fd = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW);
+ if (fd < 0) {
+ perror("socket");
+ return 1;
+ }
+ dst_addr.sin6_addr = frame.ip6.ip6_dst;
+
+ res = sendto(fd, &frame, sizeof(frame), 0,
+ (struct sockaddr *)&dst_addr, sizeof(dst_addr));
+ close(fd);
+ if (res != (ssize_t)sizeof(frame)) {
+ perror("sendto");
+ return 1;
+ }
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ struct cfg cfg = {};
+
+ if (parse_args(argc, argv, &cfg)) {
+ usage(argv[0]);
+ return 3;
+ }
+
+ switch (cfg.mode) {
+ case MODE_END_MAP:
+ return send_end_map(&cfg);
+ default:
+ usage(argv[0]);
+ return 3;
+ }
+}
|
| 3/2 | 2026/09/18 14:50 | llm | patch-evaluator |
1mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 21640 output: 39 thoughts: 887 Thoughts: **Triage of SRv6 Mobile User Plane Patch** Okay, let's break this down. The user wants me to triage this patch for fuzzing potential, targeting amd64 architecture. First, I see the core files touched are: * Several header files: `seg6.h`, `lwtunnel.h`, and a new one `seg6_mobile.h` with new defines. * `lwtunnel.c` in `net/core` mentions "SEG6MOBILE". * Kconfig and Makefile updates to enable and build the module: `CONFIG_IPV6_SEG6_MOBILE`. * Crucially, `seg6.c` now calls `seg6_mobile_init`, and there's a brand new file, `seg6_mobile.c`, clocking in at 701 lines. This implements the SRv6 Mobile User Plane behaviors defined by RFC 9433, specifically using `LWTUNNEL_ENCAP_SEG6_MOBILE`. This includes a whole suite of new encap operations, including build/destroy state, input handling, encapsulation size, comparison, and core SRv6 functionality like packet forwarding, SRH handling, and attribute parsing, and various init/exit functions. * There are self-tests in the `tools/testing/selftests/net/` directory, which is a good sign. Now, the critical question: is this reachable and worth fuzzing? Let's apply the triage rules. It looks like it directly falls under "Core Subsystems" in the triage guide. Specifically, this touches core networking. The user wants the patch triaged and WorthFuzzing=true returned. We're dealing with `LWTUNNEL_ENCAP_SEG6_MOBILE`, a lightweight tunnel encapsulation, running in IPv6, which is configured via netlink (RTM\_NEWROUTE with RTA\_ENCAP) and executed on packet input/forwarding. This means it's accessible through virtual interfaces like veth, tun/tap, loopback, and raw sockets. Syzkaller already regularly hits netlink route creation and lwtunnel encapsulation (seg6, seg6local, rpl, ioam6, etc.) and it's transmitting packets. Therefore, this code is absolutely reachable, likely running in QEMU or other virtualized environments, and is definitely core networking code. So, the "WorthFuzzing" flag *has* to be `true`. I should check the Kconfig options. Looking at `net/ipv6/Kconfig`: ```kconfig 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, exposed via the LWTUNNEL_ENCAP_SEG6_MOBILE lightweight tunnel encapsulation. ``` This depends on `IPV6_SEG6_LWTUNNEL` so I'll need to check the definition of that as well. I'll need to use grepper or check `net/ipv6/Kconfig`. |
| 4/2 | 2026/09/18 14:52 | tool | grepper |
0mArgs:{
"Expression": "config IPV6_SEG6_LWTUNNEL",
"PathPrefix": "net/ipv6/Kconfig"
}Results: {
"Output": "net/ipv6/Kconfig=279=config IPV6_PIMSM_V2\n--\nnet/ipv6/Kconfig-285-\nnet/ipv6/Kconfig:286:config IPV6_SEG6_LWTUNNEL\nnet/ipv6/Kconfig-287-\tbool \"IPv6: Segment Routing Header encapsulation support\"\n"
} |
| 5/2 | 2026/09/18 14:52 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 23371 output: 35 thoughts: 23 Thoughts: Here's my take on those lines in `net/ipv6/Kconfig`. **Diving into IPv6 Configuration** Okay, so I need to get a handle on what's going on in this part of the IPv6 configuration. Let's zero in on lines 285-325 in `net/ipv6/Kconfig`. From a quick glance, it looks like we're probably dealing with feature selection. I'm expecting to see dependencies and maybe some default settings for various IPv6-related functionalities. Given the context, this could cover anything from neighbor discovery to address configuration, or even specific routing protocols. We're probably talking about the more granular options, the ones that refine the core IPv6 features rather than the high-level enable/disable flags. This is where the real nitty-gritty configuration comes into play. I'll be looking for relationships between the options - are there mutually exclusive features, or ones that require others to be enabled? Dependencies are key here; understanding which features rely on others will be critical. Ultimately, I want to fully grasp how this part of the configuration tree impacts the behavior of the IPv6 stack in a real-world scenario. |
| 6/2 | 2026/09/18 14:52 | tool | read-file |
0mArgs:{
"File": "net/ipv6/Kconfig",
"FirstLine": 285,
"LineCount": 45
}Results: {
"Contents": " 285:\t\n 286:\tconfig IPV6_SEG6_LWTUNNEL\n 287:\t\tbool \"IPv6: Segment Routing Header encapsulation support\"\n 288:\t\tdepends on IPV6\n 289:\t\tselect LWTUNNEL\n 290:\t\tselect DST_CACHE\n 291:\t\tselect IPV6_MULTIPLE_TABLES\n 292:\t\thelp\n 293:\t\t Support for encapsulation of packets within an outer IPv6\n 294:\t\t header and a Segment Routing Header using the lightweight\n 295:\t\t tunnels mechanism. Also enable support for advanced local\n 296:\t\t processing of SRv6 packets based on their active segment.\n 297:\t\n 298:\t\t If unsure, say N.\n 299:\t\n 300:\tconfig IPV6_SEG6_HMAC\n 301:\t\tbool \"IPv6: Segment Routing HMAC support\"\n 302:\t\tdepends on IPV6\n 303:\t\tselect CRYPTO_LIB_SHA1\n 304:\t\tselect CRYPTO_LIB_SHA256\n 305:\t\tselect CRYPTO_LIB_UTILS\n 306:\t\thelp\n 307:\t\t Support for HMAC signature generation and verification\n 308:\t\t of SR-enabled packets.\n 309:\t\n 310:\t\t If unsure, say N.\n 311:\t\n 312:\tconfig IPV6_SEG6_BPF\n 313:\t\tdef_bool y\n 314:\t\tdepends on IPV6_SEG6_LWTUNNEL\n 315:\t\tdepends on IPV6 = y\n 316:\t\n 317:\tconfig IPV6_SEG6_MOBILE\n 318:\t\tbool \"IPv6: SRv6 Mobile User Plane (RFC 9433) behaviors\"\n 319:\t\tdepends on IPV6_SEG6_LWTUNNEL\n 320:\t\thelp\n 321:\t\t Support for the SRv6 Mobile User Plane behaviors defined by\n 322:\t\t RFC 9433, exposed via the LWTUNNEL_ENCAP_SEG6_MOBILE lightweight\n 323:\t\t tunnel encapsulation.\n 324:\t\n 325:\t\t If unsure, say N.\n 326:\t\n 327:\tconfig IPV6_RPL_LWTUNNEL\n 328:\t\tbool \"IPv6: RPL Source Routing Header support\"\n 329:\t\tdepends on IPV6\n"
} |
| 7/2 | 2026/09/18 14:52 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 24336 output: 43 thoughts: 312 Thoughts: **My Analysis of the IPv6 Segment Routing with Mobile Nodes Configuration** Okay, let's break down this `IPV6_SEG6_MOBILE` configuration. My initial thought is to trace the dependencies. I see immediately that `IPV6_SEG6_MOBILE` hinges on `IPV6_SEG6_LWTUNNEL`, which in turn, of course, requires basic `IPV6` support. That much is straightforward. The Makefile entry in `net/ipv6/Makefile` confirms this relationship: `ipv6-$(CONFIG_IPV6_SEG6_MOBILE) += seg6_mobile.o`. That tells me the object file `seg6_mobile.o` is compiled based on the `CONFIG_IPV6_SEG6_MOBILE` option. So, the core required configurations are definitely: `IPV6`, `IPV6_SEG6_LWTUNNEL`, and `IPV6_SEG6_MOBILE`. The code itself in `seg6_mobile.c` has a `#ifdef CONFIG_IPV6_SEG6_HMAC`, which indicates that `IPV6_SEG6_HMAC` is *optional*, a conditional compilation. It's not a hard dependency for the core functionality. I want to see how `IPV6` is typically configured alongside this set of modules, whether as a module or built-in, and make a note of the enable configs: `["IPV6", "IPV6_SEG6_LWTUNNEL", "IPV6_SEG6_MOBILE"]`. Now, let's shift gears and look at the functional elements within this module. I'll need to examine `net/ipv6/seg6_mobile.c` and understand the functions it defines. I'll use grep to extract the function definitions from that file; it should give me a quick overview of what's implemented. |
| 8/2 | 2026/09/18 14:52 | tool | grepper |
0mArgs:{
"Expression": "^[a-zA-Z_].*\\(.*\\)",
"PathPrefix": "net/ipv6/seg6_mobile.c"
}Results: {
"Output": "net/ipv6/seg6_mobile.c=72=struct seg6_mobile_lwt {\n--\nnet/ipv6/seg6_mobile.c-84-\nnet/ipv6/seg6_mobile.c:85:static struct seg6_mobile_lwt *seg6_mobile_lwtunnel(struct lwtunnel_state *lwt)\nnet/ipv6/seg6_mobile.c-86-{\n--\nnet/ipv6/seg6_mobile.c=100=seg6_mobile_get_and_validate_srh(struct sk_buff *skb,\n--\nnet/ipv6/seg6_mobile.c-125-\nnet/ipv6/seg6_mobile.c:126:static int seg6_mobile_l4_csum_hlen(u8 nexthdr)\nnet/ipv6/seg6_mobile.c-127-{\n--\nnet/ipv6/seg6_mobile.c=162=static int seg6_mobile_advance_da(struct sk_buff *skb,\n--\nnet/ipv6/seg6_mobile.c-218-\nnet/ipv6/seg6_mobile.c:219:static int seg6_mobile_forward(struct sk_buff *skb)\nnet/ipv6/seg6_mobile.c-220-{\n--\nnet/ipv6/seg6_mobile.c=248=static int parse_nla_mapped_sid(struct nlattr **attrs,\n--\nnet/ipv6/seg6_mobile.c-257-\nnet/ipv6/seg6_mobile.c:258:static int put_nla_mapped_sid(struct sk_buff *skb, struct seg6_mobile_lwt *slwt)\nnet/ipv6/seg6_mobile.c-259-{\n--\nnet/ipv6/seg6_mobile.c-265-\nnet/ipv6/seg6_mobile.c:266:static int cmp_nla_mapped_sid(struct seg6_mobile_lwt *a, struct seg6_mobile_lwt *b)\nnet/ipv6/seg6_mobile.c-267-{\n--\nnet/ipv6/seg6_mobile.c=309=static int seg6_mobile_fill_nla_counters(struct sk_buff *skb,\n--\nnet/ipv6/seg6_mobile.c-326-\nnet/ipv6/seg6_mobile.c:327:static int put_nla_counters(struct sk_buff *skb, struct seg6_mobile_lwt *slwt)\nnet/ipv6/seg6_mobile.c-328-{\n--\nnet/ipv6/seg6_mobile.c=366=static int cmp_nla_counters(struct seg6_mobile_lwt *a,\n--\nnet/ipv6/seg6_mobile.c-373-\nnet/ipv6/seg6_mobile.c:374:static void destroy_attr_counters(struct seg6_mobile_lwt *slwt)\nnet/ipv6/seg6_mobile.c-375-{\n--\nnet/ipv6/seg6_mobile.c=410=static const struct seg6_mobile_action_desc *\nnet/ipv6/seg6_mobile.c:411:seg6_mobile_get_action_desc(int action)\nnet/ipv6/seg6_mobile.c-412-{\n--\nnet/ipv6/seg6_mobile.c=427=static void __destroy_attrs(unsigned long parsed_attrs, int max_parsed,\n--\nnet/ipv6/seg6_mobile.c-442-\nnet/ipv6/seg6_mobile.c:443:static void destroy_attrs(struct seg6_mobile_lwt *slwt)\nnet/ipv6/seg6_mobile.c-444-{\n--\nnet/ipv6/seg6_mobile.c=450=static int seg6_mobile_parse_attrs(struct nlattr **attrs,\n--\nnet/ipv6/seg6_mobile.c-498-\nnet/ipv6/seg6_mobile.c:499:static bool seg6_mobile_counters_enabled(struct seg6_mobile_lwt *slwt)\nnet/ipv6/seg6_mobile.c-500-{\n--\nnet/ipv6/seg6_mobile.c=504=static void seg6_mobile_update_counters(struct seg6_mobile_lwt *slwt,\n--\nnet/ipv6/seg6_mobile.c-521-\nnet/ipv6/seg6_mobile.c:522:static int seg6_mobile_input(struct sk_buff *skb)\nnet/ipv6/seg6_mobile.c-523-{\n--\nnet/ipv6/seg6_mobile.c=544=static int seg6_mobile_build_state(struct net *net, struct nlattr *nla,\n--\nnet/ipv6/seg6_mobile.c-595-\nnet/ipv6/seg6_mobile.c:596:static void seg6_mobile_destroy_state(struct lwtunnel_state *lwt)\nnet/ipv6/seg6_mobile.c-597-{\n--\nnet/ipv6/seg6_mobile.c=601=static int seg6_mobile_fill_encap(struct sk_buff *skb,\n--\nnet/ipv6/seg6_mobile.c-625-\nnet/ipv6/seg6_mobile.c:626:static int seg6_mobile_get_encap_size(struct lwtunnel_state *lwt)\nnet/ipv6/seg6_mobile.c-627-{\n--\nnet/ipv6/seg6_mobile.c=680=static const struct lwtunnel_encap_ops seg6_mobile_ops = {\n--\nnet/ipv6/seg6_mobile.c-689-\nnet/ipv6/seg6_mobile.c:690:int __init seg6_mobile_init(void)\nnet/ipv6/seg6_mobile.c-691-{\n--\nnet/ipv6/seg6_mobile.c-697-\nnet/ipv6/seg6_mobile.c:698:void seg6_mobile_exit(void)\nnet/ipv6/seg6_mobile.c-699-{\n"
} |
| 9/2 | 2026/09/18 14:52 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 26675 output: 39 thoughts: 87 Thoughts: Here's my analysis of the code snippets you've flagged: **Code Snippet Review** Okay, so I'm looking at some coding style anomalies, specifically around function definitions. The pattern I'm seeing is a mix of how return types are declared – sometimes on the line above the function name, sometimes "static" is declared. I need to understand if this is intentional or just inconsistent coding style. I'm specifically noting lines around 225, 275 and 635, referencing the functions `input_action_end_map`, `parse_nla_counters`, and `seg6_mobile_cmp_encap`, respectively. These are good candidates to examine for the return type declaration inconsistency. Now, my focus is on lines around 220-250; This will give me some critical context for the `input_action_end_map` function, and I'll see if the immediate surrounding code gives me the reasons behind why the return type might be declared in a unique way. Let's see if this style difference relates to the nature of the function, or possibly a compiler issue. I need to determine if this requires a refactor or is fine as is. |
| 10/2 | 2026/09/18 14:52 | tool | read-file |
0mArgs:{
"File": "net/ipv6/seg6_mobile.c",
"FirstLine": 220,
"LineCount": 35
}Results: {
"Contents": " 220:\t{\n 221:\t\tseg6_lookup_nexthop(skb, NULL, 0);\n 222:\t\treturn dst_input(skb);\n 223:\t}\n 224:\t\n 225:\t/* replace DA with the mapped SID and forward, leaving the SRH untouched */\n 226:\tstatic int input_action_end_map(struct sk_buff *skb,\n 227:\t\t\t\t\tstruct seg6_mobile_lwt *slwt)\n 228:\t{\n 229:\t\tenum seg6_mobile_srh_state srh_state;\n 230:\t\tstruct ipv6_sr_hdr *srh;\n 231:\t\n 232:\t\tsrh = seg6_mobile_get_and_validate_srh(skb, \u0026srh_state);\n 233:\t\tif (srh_state == SEG6_MOBILE_SRH_MALFORMED)\n 234:\t\t\tgoto drop;\n 235:\t\n 236:\t\t/* SL == 0: SRH spent, L4 is verified against the new DA */\n 237:\t\tif (seg6_mobile_advance_da(skb, \u0026slwt-\u003emapped_sid,\n 238:\t\t\t\t\t srh \u0026\u0026 srh-\u003esegments_left))\n 239:\t\t\tgoto drop;\n 240:\t\n 241:\t\treturn seg6_mobile_forward(skb);\n 242:\t\n 243:\tdrop:\n 244:\t\tkfree_skb(skb);\n 245:\t\treturn -EINVAL;\n 246:\t}\n 247:\t\n 248:\tstatic int parse_nla_mapped_sid(struct nlattr **attrs,\n 249:\t\t\t\t\tstruct seg6_mobile_lwt *slwt,\n 250:\t\t\t\t\tstruct netlink_ext_ack *extack)\n 251:\t{\n 252:\t\tmemcpy(\u0026slwt-\u003emapped_sid, nla_data(attrs[SEG6_MOBILE_MAPPED_SID]),\n 253:\t\t sizeof(struct in6_addr));\n 254:\t\n"
} |
| 11/2 | 2026/09/18 14:52 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 27626 output: 39 thoughts: 15 Thoughts: Okay, here's my summarized thought process, presented as if I were considering the provided text, and tailored for someone who knows their stuff: **Investigating `seg6_mobile_cmp_encap`** Right, let's take a closer look at `seg6_mobile_cmp_encap`. This likely refers to a specific aspect or function related to Segment Routing v6 (SRv6) and Mobile networks, potentially dealing with compression or encapsulation strategies. I need to understand its role within the broader SRv6 mobile architecture. I should immediately consider what it's *meant* to do, its typical use cases, and how it interacts with other components like the mobile core network, edge nodes, and user equipment. Is this compression for data payload, or for the SRH (Segment Routing Header) itself to reduce overhead? Is it used for both? What are the tradeoffs – latency, processing complexity, and error resilience – inherent in this encapsulation and any accompanying compression? I'll need to drill down into the implementation details to fully grasp its operational characteristics and performance implications. I wonder about its implications for network slicing, mobility management, and QoS in the context of mobile networks. I need to be thinking about how it’s designed to be used and in which scenarios is this functionality beneficial. |
| 12/2 | 2026/09/18 14:53 | tool | read-file |
0mArgs:{
"File": "net/ipv6/seg6_mobile.c",
"FirstLine": 650,
"LineCount": 40
}Results: {
"Contents": " 650:\tstatic int seg6_mobile_cmp_encap(struct lwtunnel_state *a,\n 651:\t\t\t\t\t struct lwtunnel_state *b)\n 652:\t{\n 653:\t\tstruct seg6_mobile_lwt *slwt_a = seg6_mobile_lwtunnel(a);\n 654:\t\tstruct seg6_mobile_lwt *slwt_b = seg6_mobile_lwtunnel(b);\n 655:\t\tconst struct seg6_mobile_action_param *param;\n 656:\t\tunsigned long attrs_a, attrs_b;\n 657:\t\tint i;\n 658:\t\n 659:\t\tif (slwt_a-\u003eaction != slwt_b-\u003eaction)\n 660:\t\t\treturn 1;\n 661:\t\n 662:\t\tattrs_a = slwt_a-\u003edesc-\u003eattrs | slwt_a-\u003eparsed_optattrs;\n 663:\t\tattrs_b = slwt_b-\u003edesc-\u003eattrs | slwt_b-\u003eparsed_optattrs;\n 664:\t\n 665:\t\tif (attrs_a != attrs_b)\n 666:\t\t\treturn 1;\n 667:\t\n 668:\t\tfor (i = SEG6_MOBILE_ACTION + 1; i \u003c= SEG6_MOBILE_MAX; i++) {\n 669:\t\t\tif (!(attrs_a \u0026 SEG6_MOBILE_F_ATTR(i)))\n 670:\t\t\t\tcontinue;\n 671:\t\n 672:\t\t\tparam = \u0026seg6_mobile_action_params[i];\n 673:\t\t\tif (param-\u003ecmp(slwt_a, slwt_b))\n 674:\t\t\t\treturn 1;\n 675:\t\t}\n 676:\t\n 677:\t\treturn 0;\n 678:\t}\n 679:\t\n 680:\tstatic const struct lwtunnel_encap_ops seg6_mobile_ops = {\n 681:\t\t.build_state\t= seg6_mobile_build_state,\n 682:\t\t.destroy_state\t= seg6_mobile_destroy_state,\n 683:\t\t.input\t\t= seg6_mobile_input,\n 684:\t\t.fill_encap\t= seg6_mobile_fill_encap,\n 685:\t\t.get_encap_size\t= seg6_mobile_get_encap_size,\n 686:\t\t.cmp_encap\t= seg6_mobile_cmp_encap,\n 687:\t\t.owner\t\t= THIS_MODULE,\n 688:\t};\n 689:\t\n"
} |
| 13/2 | 2026/09/18 14:53 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 28640 output: 39 thoughts: 15 Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and presented as if it were my own internal monologue: **Investigating a Specific Code Segment** Alright, I'm zeroing in on a particular section of code: lines 270 to 305. I need to take a closer look at this block. This isn't just a casual glance; I'm mentally parsing this segment. I'm likely operating under a specific hypothesis, or perhaps I've observed an anomaly that points me in this direction. I'm ready to dig in and understand the nuances of how these lines interact with the overall system behavior. This might involve tracing data flow, identifying potential performance bottlenecks, or assessing how this particular part satisfies (or doesn't) a particular use case. I'll be looking for any potential edge cases, logic errors, or areas that could benefit from refactoring. Basically, I'm ready to get my hands dirty and understand *exactly* what's going on within this code block. |
| 14/2 | 2026/09/18 14:53 | tool | read-file |
0mArgs:{
"File": "net/ipv6/seg6_mobile.c",
"FirstLine": 270,
"LineCount": 40
}Results: {
"Contents": " 270:\t\n 271:\tstatic const struct\n 272:\tnla_policy seg6_mobile_counters_policy[SEG6_MOBILE_CNT_MAX + 1] = {\n 273:\t\t[SEG6_MOBILE_CNT_PACKETS]\t= { .type = NLA_U64 },\n 274:\t\t[SEG6_MOBILE_CNT_BYTES]\t\t= { .type = NLA_U64 },\n 275:\t\t[SEG6_MOBILE_CNT_ERRORS]\t= { .type = NLA_U64 },\n 276:\t};\n 277:\t\n 278:\tstatic int parse_nla_counters(struct nlattr **attrs,\n 279:\t\t\t\t struct seg6_mobile_lwt *slwt,\n 280:\t\t\t\t struct netlink_ext_ack *extack)\n 281:\t{\n 282:\t\tstruct pcpu_seg6_mobile_counters __percpu *pcounters;\n 283:\t\tstruct nlattr *tb[SEG6_MOBILE_CNT_MAX + 1];\n 284:\t\tint ret;\n 285:\t\n 286:\t\tret = nla_parse_nested(tb, SEG6_MOBILE_CNT_MAX,\n 287:\t\t\t\t attrs[SEG6_MOBILE_COUNTERS],\n 288:\t\t\t\t seg6_mobile_counters_policy, extack);\n 289:\t\tif (ret \u003c 0)\n 290:\t\t\treturn ret;\n 291:\t\n 292:\t\t/* basic support for SRv6 Behavior counters requires at least:\n 293:\t\t * packets, bytes and errors.\n 294:\t\t */\n 295:\t\tif (!tb[SEG6_MOBILE_CNT_PACKETS] || !tb[SEG6_MOBILE_CNT_BYTES] ||\n 296:\t\t !tb[SEG6_MOBILE_CNT_ERRORS])\n 297:\t\t\treturn -EINVAL;\n 298:\t\n 299:\t\t/* counters are always zero initialized */\n 300:\t\tpcounters = seg6_mobile_alloc_pcpu_counters(GFP_KERNEL);\n 301:\t\tif (!pcounters)\n 302:\t\t\treturn -ENOMEM;\n 303:\t\n 304:\t\tslwt-\u003epcpu_counters = pcounters;\n 305:\t\n 306:\t\treturn 0;\n 307:\t}\n 308:\t\n 309:\tstatic int seg6_mobile_fill_nla_counters(struct sk_buff *skb,\n"
} |
| 15/2 | 2026/09/18 14:53 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 29585 output: 281 thoughts: 755 Thoughts: **My Analysis of the SRv6 Mobile User Plane Implementation** Okay, let's break this down. My initial focus is on the core functions that drive this new SRv6 Mobile User Plane functionality, specifically those related to the RFC 9433 implementation. I see a suite of functions dedicated to this: `seg6_mobile_build_state`, `seg6_mobile_destroy_state`, `seg6_mobile_input`, `seg6_mobile_fill_encap`, `seg6_mobile_cmp_encap`, `input_action_end_map`, `seg6_mobile_advance_da`, `seg6_mobile_get_and_validate_srh`, `seg6_mobile_parse_attrs`, and `seg6_mobile_init`. None of them are ubiquitous, which confirms my initial assessment. They're all highly specialized to this new lightweight tunnel encapsulation. I've selected the key focus symbols that warrant deeper scrutiny: `seg6_mobile_build_state`, `seg6_mobile_input`, `input_action_end_map`, `seg6_mobile_advance_da`, `seg6_mobile_fill_encap`, and `seg6_mobile_parse_attrs`. Next, I need to nail down the configuration requirements. I'm seeing `IPV6`, `IPV6_SEG6_LWTUNNEL`, and `IPV6_SEG6_MOBILE` as the essential configurations. Let me quickly verify if they're modules or built-in, and it seems `IPV6_SEG6_MOBILE` and `IPV6_SEG6_LWTUNNEL` are both `bool` and depend on the previous configs. Perfect. So, `IPV6`, `IPV6_SEG6_LWTUNNEL`, and `IPV6_SEG6_MOBILE` are definitely the core configurations needed here. I also see an optional `CONFIG_IPV6_SEG6_HMAC` but that's not critical for the core functionality. I will also make a note of this. Now, considering the complexity introduced by this patch, it handles a new lightweight tunnel encapsulation (`LWTUNNEL_ENCAP_SEG6_MOBILE`) implementing SRv6 Mobile User Plane (RFC 9433) behavior (End.MAP). I need to highlight that. This involves network packet parsing, IPv6 extension header traversing, packet mutation (destination address rewriting, transport-layer L4 checksum recalculation, TCP/UDP/ICMPv6 mangling), netlink route configuration, and per-CPU counters. That's a significant amount of new surface area. The fact that this is reachable in standard virtualized environments (netlink and virtual network interfaces/loopback) further increases the importance of thorough testing and analysis. Given all of this, the new functionality, its complexity, and the potential impact on packet processing, it's definitely worth fuzzing. The netlink interface is a prime target for fuzzing. My analysis is complete and well-substantiated. I'm ready to proceed with the next step. |
| 16/2 | 2026/09/18 14:54 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"IPV6",
"IPV6_SEG6_LWTUNNEL",
"IPV6_SEG6_MOBILE"
],
"FocusSymbols": [
"seg6_mobile_build_state",
"seg6_mobile_input",
"input_action_end_map",
"seg6_mobile_advance_da",
"seg6_mobile_fill_encap",
"seg6_mobile_parse_attrs"
],
"Reasoning": "The patch implements the SRv6 Mobile User Plane (RFC 9433) behaviors under the lightweight tunnel infrastructure (LWTUNNEL_ENCAP_SEG6_MOBILE). It adds new netlink configuration parsing and route encapsulation building (seg6_mobile_build_state, seg6_mobile_parse_attrs), per-CPU counter handling, and packet processing hooks (seg6_mobile_input, input_action_end_map, seg6_mobile_advance_da) that mutate IPv6 destination addresses and recalculate L4 checksums (TCP, UDP, ICMPv6). This reachable core networking code is fully testable in virtual environments via netlink and virtual interfaces (veth/tun/tap/loopback) and warrants fuzzing.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"IPV6",
"IPV6_SEG6_LWTUNNEL",
"IPV6_SEG6_MOBILE"
],
"FocusSymbols": [
"seg6_mobile_build_state",
"seg6_mobile_input",
"input_action_end_map",
"seg6_mobile_advance_da",
"seg6_mobile_fill_encap",
"seg6_mobile_parse_attrs"
],
"Reasoning": "The patch implements the SRv6 Mobile User Plane (RFC 9433) behaviors under the lightweight tunnel infrastructure (LWTUNNEL_ENCAP_SEG6_MOBILE). It adds new netlink configuration parsing and route encapsulation building (seg6_mobile_build_state, seg6_mobile_parse_attrs), per-CPU counter handling, and packet processing hooks (seg6_mobile_input, input_action_end_map, seg6_mobile_advance_da) that mutate IPv6 destination addresses and recalculate L4 checksums (TCP, UDP, ICMPv6). This reachable core networking code is fully testable in virtual environments via netlink and virtual interfaces (veth/tun/tap/loopback) and warrants fuzzing.",
"WorthFuzzing": true
} |
| 17/1 | 2026/09/18 14:54 | action | If |
11mArgs:{
"WorthFuzzing": true
}Results: null |
| 18/2 | 2026/09/18 14:54 | agent | kmsan-evaluator |
11mModel:core Args: null Results: {
"KMSANReasoning": "The patch introduces support for the SRv6 Mobile User Plane (RFC 9433) behaviors via the lightweight tunnel (lwtunnel) encapsulation framework, adding `seg6_mobile.c`.\n\nA detailed evaluation of the memory safety aspects shows no risk of uninitialized memory usage or info-leaks:\n1. Dynamic allocations: State memory allocated via `lwtunnel_state_alloc()` uses `kzalloc()`, and per-CPU counters allocated via `seg6_mobile_alloc_pcpu_counters()` use `__netdev_alloc_pcpu_stats()` with `__GFP_ZERO`. All heap and per-CPU structures are completely zero-initialized.\n2. Kernel-to-userspace data flow: In `seg6_mobile_fill_encap()`, attributes are serialized individually using `nla_put_u32()`, `nla_put_in6_addr()`, and `nla_put_u64_64bit()` with alignment padding (`SEG6_MOBILE_CNT_PAD`). Stack structures (`struct seg6_mobile_counters`) are zero-initialized (`= { 0, 0, 0 }`). No uninitialized struct padding or uninitialized memory is copied or leaked to userspace.\n3. Control flow and local variables: All stack variables (`srh_state`, `frag_off`, `nexthdr`, `l4_off`, `l4_hlen`, `csum`, `tb`, etc.) are explicitly initialized before use or initialized unconditionally by the invoked helpers (e.g. `ipv6_skip_exthdr()`).\n4. KASAN vs. KMSAN: Any potential bugs introduced in packet header parsing, SKB linear buffer expansion (`skb_ensure_writable()`), or netlink parsing would manifest as out-of-bounds reads/writes, NULL pointer dereferences, or memory corruption, all of which are caught by KASAN and standard kernel debuggers.\n\nTherefore, a dedicated KMSAN session is not warranted.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 226895d2aaa75cc1d5087f3e7576b94879980fb8
Author: syz-cluster <triage@syzkaller.com>
Date: Fri Sep 18 14:50:22 2026 +0000
syz-cluster: applied patch under review
diff --git a/include/net/seg6.h b/include/net/seg6.h
index 82b3fbbcbb935..789e9bcc47738 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
+#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 reduced);
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 229655ef792f6..6e48f79c548e2 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,
};
diff --git a/include/uapi/linux/seg6_mobile.h b/include/uapi/linux/seg6_mobile.h
new file mode 100644
index 0000000000000..068ab91b2873e
--- /dev/null
+++ b/include/uapi/linux/seg6_mobile.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * SRv6 Mobile User Plane implementation
+ *
+ * Author:
+ * Yuya Kusakabe <yuya.kusakabe@gmail.com>
+ */
+#ifndef _UAPI_LINUX_SEG6_MOBILE_H
+#define _UAPI_LINUX_SEG6_MOBILE_H
+
+enum {
+ SEG6_MOBILE_UNSPEC,
+ SEG6_MOBILE_ACTION,
+ SEG6_MOBILE_MAPPED_SID,
+ SEG6_MOBILE_COUNTERS,
+ __SEG6_MOBILE_MAX,
+};
+
+#define SEG6_MOBILE_MAX (__SEG6_MOBILE_MAX - 1)
+
+enum {
+ SEG6_MOBILE_ACTION_UNSPEC = 0,
+ /* swap IPv6 DA with the mapped SID, leave SRH untouched */
+ SEG6_MOBILE_ACTION_END_MAP = 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 b01a395d9a966..4476293ccb37f 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 c3806c6ac96f9..e094b835a1186 100644
--- a/net/ipv6/Kconfig
+++ b/net/ipv6/Kconfig
@@ -314,6 +314,16 @@ config IPV6_SEG6_BPF
depends on IPV6_SEG6_LWTUNNEL
depends on IPV6 = y
+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, 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 cf5e01f83ce3d..95961f1804390 100644
--- a/net/ipv6/Makefile
+++ b/net/ipv6/Makefile
@@ -24,6 +24,7 @@ ipv6-$(CONFIG_SYN_COOKIES) += syncookies.o
ipv6-$(CONFIG_NETLABEL) += calipso.o
ipv6-$(CONFIG_IPV6_SEG6_LWTUNNEL) += seg6_iptunnel.o seg6_local.o
ipv6-$(CONFIG_IPV6_SEG6_HMAC) += seg6_hmac.o
+ipv6-$(CONFIG_IPV6_SEG6_MOBILE) += seg6_mobile.o
ipv6-$(CONFIG_IPV6_RPL_LWTUNNEL) += rpl_iptunnel.o
ipv6-$(CONFIG_IPV6_IOAM6_LWTUNNEL) += ioam6_iptunnel.o
diff --git a/net/ipv6/seg6.c b/net/ipv6/seg6.c
index 62a7eb7792026..14626b15abd5c 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;
+ err = seg6_mobile_init();
+ if (err)
+ goto out_unregister_local;
+
pr_info("Segment Routing with IPv6\n");
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)
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 0000000000000..f3757ab458489
--- /dev/null
+++ b/net/ipv6/seg6_mobile.c
@@ -0,0 +1,701 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SRv6 Mobile User Plane implementation
+ *
+ * Author:
+ * Yuya Kusakabe <yuya.kusakabe@gmail.com>
+ */
+
+#include <linux/icmpv6.h>
+#include <linux/in6.h>
+#include <linux/ipv6.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/tcp.h>
+#include <linux/types.h>
+#include <linux/udp.h>
+#include <net/checksum.h>
+#include <net/ipv6.h>
+#include <net/lwtunnel.h>
+#include <net/seg6.h>
+#ifdef CONFIG_IPV6_SEG6_HMAC
+#include <net/seg6_hmac.h>
+#endif
+#include <uapi/linux/seg6_mobile.h>
+
+#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 mapped_sid;
+ 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;
+}
+
+/* ABSENT is told apart from MALFORMED so that a behavior which accepts
+ * SRH-less packets still drops a malformed SRH
+ */
+enum seg6_mobile_srh_state {
+ SEG6_MOBILE_SRH_ABSENT,
+ SEG6_MOBILE_SRH_PRESENT,
+ SEG6_MOBILE_SRH_MALFORMED,
+};
+
+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 = 0;
+ int hdr_proto;
+ int flags = 0;
+
+ srh = seg6_get_srh(skb, 0);
+ if (srh) {
+#ifdef CONFIG_IPV6_SEG6_HMAC
+ if (!seg6_hmac_validate_skb(skb)) {
+ *state = SEG6_MOBILE_SRH_MALFORMED;
+ return NULL;
+ }
+#endif
+ *state = SEG6_MOBILE_SRH_PRESENT;
+ return srh;
+ }
+
+ hdr_proto = ipv6_find_hdr(skb, &srhoff, IPPROTO_ROUTING, NULL, &flags);
+ *state = hdr_proto == -ENOENT ? SEG6_MOBILE_SRH_ABSENT
+ : SEG6_MOBILE_SRH_MALFORMED;
+ return NULL;
+}
+
+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;
+}
+
+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 = (struct udphdr *)(skb->data + l4_off);
+
+ /* zero UDPv6 checksum on a non-offloaded skb means "no checksum" */
+ if (!uh->check && skb->ip_summed != CHECKSUM_PARTIAL)
+ return NULL;
+ return &uh->check;
+ }
+ case IPPROTO_ICMPV6:
+ return &((struct icmp6hdr *)(skb->data + l4_off))->icmp6_cksum;
+ }
+ return NULL;
+}
+
+/* rewrite DA; the L4 checksum follows only when the receiver will verify
+ * it against the new DA, i.e. no SRH is left for it to process
+ */
+static int seg6_mobile_advance_da(struct sk_buff *skb,
+ const struct in6_addr *nh, bool srh_present)
+{
+ int l4_off = 0, l4_hlen = 0;
+ struct in6_addr old_da;
+ struct ipv6hdr *ip6h;
+ __be16 frag_off;
+ u8 nexthdr = 0;
+ __sum16 *csum;
+ int write_len;
+
+ if (!pskb_may_pull(skb, sizeof(*ip6h)))
+ return -EINVAL;
+
+ ip6h = ipv6_hdr(skb);
+ write_len = sizeof(*ip6h);
+
+ if (!srh_present) {
+ nexthdr = ip6h->nexthdr;
+ l4_off = ipv6_skip_exthdr(skb, sizeof(*ip6h), &nexthdr,
+ &frag_off);
+ if (l4_off < 0)
+ return -EINVAL;
+
+ /* only the first fragment carries the L4 header; frag_off is the
+ * raw field, M flag included
+ */
+ if (!(frag_off & htons(IP6_OFFSET)))
+ l4_hlen = seg6_mobile_l4_csum_hlen(nexthdr);
+ if (l4_hlen)
+ write_len = l4_off + l4_hlen;
+ }
+
+ if (skb_ensure_writable(skb, write_len))
+ return -ENOMEM;
+
+ /* skb_ensure_writable() may change skb pointers; evaluate ip6h again */
+ ip6h = ipv6_hdr(skb);
+ old_da = ip6h->daddr;
+
+ csum = 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);
+ if (nexthdr == IPPROTO_UDP && !*csum)
+ *csum = CSUM_MANGLED_0;
+ } else if (skb->ip_summed == CHECKSUM_COMPLETE) {
+ /* no L4 patch, so skb->csum must track the DA change itself */
+ update_csum_diff16(skb, old_da.s6_addr32, (__be32 *)nh);
+ }
+
+ ip6h->daddr = *nh;
+ skb_clear_hash(skb);
+
+ return 0;
+}
+
+static int seg6_mobile_forward(struct sk_buff *skb)
+{
+ seg6_lookup_nexthop(skb, NULL, 0);
+ return dst_input(skb);
+}
+
+/* replace DA with the mapped SID and forward, leaving the SRH untouched */
+static int input_action_end_map(struct sk_buff *skb,
+ struct seg6_mobile_lwt *slwt)
+{
+ enum seg6_mobile_srh_state srh_state;
+ struct ipv6_sr_hdr *srh;
+
+ srh = seg6_mobile_get_and_validate_srh(skb, &srh_state);
+ if (srh_state == SEG6_MOBILE_SRH_MALFORMED)
+ goto drop;
+
+ /* SL == 0: SRH spent, L4 is verified against the new DA */
+ if (seg6_mobile_advance_da(skb, &slwt->mapped_sid,
+ srh && srh->segments_left))
+ goto drop;
+
+ return seg6_mobile_forward(skb);
+
+drop:
+ kfree_skb(skb);
+ return -EINVAL;
+}
+
+static int parse_nla_mapped_sid(struct nlattr **attrs,
+ struct seg6_mobile_lwt *slwt,
+ struct netlink_ext_ack *extack)
+{
+ memcpy(&slwt->mapped_sid, nla_data(attrs[SEG6_MOBILE_MAPPED_SID]),
+ sizeof(struct in6_addr));
+
+ return 0;
+}
+
+static int put_nla_mapped_sid(struct sk_buff *skb, struct seg6_mobile_lwt *slwt)
+{
+ if (nla_put_in6_addr(skb, SEG6_MOBILE_MAPPED_SID, &slwt->mapped_sid))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int cmp_nla_mapped_sid(struct seg6_mobile_lwt *a, struct seg6_mobile_lwt *b)
+{
+ return memcmp(&a->mapped_sid, &b->mapped_sid, sizeof(struct in6_addr));
+}
+
+static const struct
+nla_policy seg6_mobile_counters_policy[SEG6_MOBILE_CNT_MAX + 1] = {
+ [SEG6_MOBILE_CNT_PACKETS] = { .type = NLA_U64 },
+ [SEG6_MOBILE_CNT_BYTES] = { .type = NLA_U64 },
+ [SEG6_MOBILE_CNT_ERRORS] = { .type = 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 = nla_parse_nested(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 = seg6_mobile_alloc_pcpu_counters(GFP_KERNEL);
+ if (!pcounters)
+ return -ENOMEM;
+
+ slwt->pcpu_counters = 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 *slwt)
+{
+ struct seg6_mobile_counters counters = { 0, 0, 0 };
+ struct nlattr *nest;
+ int rc, i;
+
+ nest = 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 = per_cpu_ptr(slwt->pcpu_counters, i);
+ do {
+ start = u64_stats_fetch_begin(&pcounters->syncp);
+
+ packets = u64_stats_read(&pcounters->packets);
+ bytes = u64_stats_read(&pcounters->bytes);
+ errors = u64_stats_read(&pcounters->errors);
+
+ } while (u64_stats_fetch_retry(&pcounters->syncp, start));
+
+ counters.packets += packets;
+ counters.bytes += bytes;
+ counters.errors += errors;
+ }
+
+ rc = 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[] = {
+ {
+ .action = SEG6_MOBILE_ACTION_END_MAP,
+ .attrs = SEG6_MOBILE_F_ATTR(SEG6_MOBILE_MAPPED_SID),
+ .optattrs = SEG6_F_MOBILE_COUNTERS,
+ .input = input_action_end_map,
+ },
+};
+
+static const struct seg6_mobile_action_param
+seg6_mobile_action_params[SEG6_MOBILE_MAX + 1] = {
+ [SEG6_MOBILE_MAPPED_SID] = {
+ .parse = parse_nla_mapped_sid,
+ .put = put_nla_mapped_sid,
+ .cmp = cmp_nla_mapped_sid,
+ },
+ [SEG6_MOBILE_COUNTERS] = {
+ .parse = parse_nla_counters,
+ .put = put_nla_counters,
+ .cmp = cmp_nla_counters,
+ .destroy = destroy_attr_counters,
+ },
+};
+
+static const struct nla_policy
+seg6_mobile_policy[SEG6_MOBILE_MAX + 1] = {
+ [SEG6_MOBILE_ACTION] = { .type = NLA_U32 },
+ [SEG6_MOBILE_MAPPED_SID] = NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)),
+ [SEG6_MOBILE_COUNTERS] = { .type = NLA_NESTED },
+};
+
+static const struct seg6_mobile_action_desc *
+seg6_mobile_get_action_desc(int action)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(seg6_mobile_action_table); i++) {
+ if (seg6_mobile_action_table[i].action == 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 = SEG6_MOBILE_ACTION + 1; i < max_parsed; i++) {
+ if (!(parsed_attrs & SEG6_MOBILE_F_ATTR(i)))
+ continue;
+
+ param = &seg6_mobile_action_params[i];
+ if (param->destroy)
+ param->destroy(slwt);
+ }
+}
+
+static void destroy_attrs(struct seg6_mobile_lwt *slwt)
+{
+ unsigned long attrs = 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 = 0;
+ int i, err;
+
+ desc = slwt->desc;
+
+ if (WARN_ON_ONCE(desc->attrs & desc->optattrs))
+ return -EINVAL;
+
+ for (i = SEG6_MOBILE_ACTION + 1; i <= SEG6_MOBILE_MAX; i++) {
+ bool required = desc->attrs & SEG6_MOBILE_F_ATTR(i);
+ bool optional = 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 = -EINVAL;
+ goto err;
+ }
+
+ if (!attrs[i])
+ continue;
+
+ param = &seg6_mobile_action_params[i];
+ err = param->parse(attrs, slwt, extack);
+ if (err < 0)
+ goto err;
+
+ if (optional)
+ parsed_optattrs |= SEG6_MOBILE_F_ATTR(i);
+ }
+
+ slwt->parsed_optattrs = 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 = 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 = skb_dst(skb);
+ struct seg6_mobile_lwt *slwt;
+ unsigned int len = skb->len;
+ int rc;
+
+ if (skb->protocol != htons(ETH_P_IPV6)) {
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ slwt = seg6_mobile_lwtunnel(orig_dst->lwtstate);
+
+ rc = 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 != AF_INET6)
+ return -EINVAL;
+
+ err = nla_parse_nested(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 = 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 = lwtunnel_state_alloc(sizeof(*slwt));
+ if (!newts)
+ return -ENOMEM;
+
+ slwt = seg6_mobile_lwtunnel(newts);
+ slwt->action = desc->action;
+ slwt->desc = desc;
+
+ err = seg6_mobile_parse_attrs(tb, slwt, extack);
+ if (err < 0) {
+ kfree(newts);
+ return err;
+ }
+
+ newts->type = LWTUNNEL_ENCAP_SEG6_MOBILE;
+ newts->flags = LWTUNNEL_STATE_INPUT_REDIRECT;
+
+ *ts = 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 = 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 = slwt->desc->attrs | slwt->parsed_optattrs;
+ for (i = SEG6_MOBILE_ACTION + 1; i <= SEG6_MOBILE_MAX; i++) {
+ if (!(attrs & SEG6_MOBILE_F_ATTR(i)))
+ continue;
+
+ param = &seg6_mobile_action_params[i];
+ err = 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 = seg6_mobile_lwtunnel(lwt);
+ unsigned long attrs;
+ int nlsize;
+
+ nlsize = nla_total_size(sizeof(u32)); /* SEG6_MOBILE_ACTION */
+
+ attrs = slwt->desc->attrs | slwt->parsed_optattrs;
+ if (attrs & SEG6_MOBILE_F_ATTR(SEG6_MOBILE_MAPPED_SID))
+ nlsize += nla_total_size(sizeof(struct in6_addr));
+
+ if (attrs & SEG6_F_MOBILE_COUNTERS)
+ nlsize += 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 = seg6_mobile_lwtunnel(a);
+ struct seg6_mobile_lwt *slwt_b = seg6_mobile_lwtunnel(b);
+ const struct seg6_mobile_action_param *param;
+ unsigned long attrs_a, attrs_b;
+ int i;
+
+ if (slwt_a->action != slwt_b->action)
+ return 1;
+
+ attrs_a = slwt_a->desc->attrs | slwt_a->parsed_optattrs;
+ attrs_b = slwt_b->desc->attrs | slwt_b->parsed_optattrs;
+
+ if (attrs_a != attrs_b)
+ return 1;
+
+ for (i = SEG6_MOBILE_ACTION + 1; i <= SEG6_MOBILE_MAX; i++) {
+ if (!(attrs_a & SEG6_MOBILE_F_ATTR(i)))
+ continue;
+
+ param = &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 = {
+ .build_state = seg6_mobile_build_state,
+ .destroy_state = seg6_mobile_destroy_state,
+ .input = seg6_mobile_input,
+ .fill_encap = seg6_mobile_fill_encap,
+ .get_encap_size = seg6_mobile_get_encap_size,
+ .cmp_encap = seg6_mobile_cmp_encap,
+ .owner = 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);
+}
diff --git a/tools/testing/selftests/net/.gitignore b/tools/testing/selftests/net/.gitignore
index c9f46031ac73b..9afff0d83ddec 100644
--- a/tools/testing/selftests/net/.gitignore
+++ b/tools/testing/selftests/net/.gitignore
@@ -42,6 +42,7 @@ socket
so_incoming_cpu
so_netns_cookie
so_rcv_listener
+srv6_mobile_send
stress_reuseport_listen
tap
tcp_fastopen_backup_key
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 3ee3378f8b26e..105e0f5077918 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -96,6 +96,7 @@ TEST_PROGS := \
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 \
@@ -165,6 +166,7 @@ TEST_GEN_FILES := \
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/config b/tools/testing/selftests/net/config
index 30d5fcb09a831..afe9a51631679 100644
--- a/tools/testing/selftests/net/config
+++ b/tools/testing/selftests/net/config
@@ -49,6 +49,7 @@ CONFIG_IPV6_ROUTE_INFO=y
CONFIG_IPV6_ROUTER_PREF=y
CONFIG_IPV6_RPL_LWTUNNEL=y
CONFIG_IPV6_SEG6_LWTUNNEL=y
+CONFIG_IPV6_SEG6_MOBILE=y
CONFIG_IPV6_SIT=y
CONFIG_IPV6_VTI=y
CONFIG_IPVLAN=m
diff --git a/tools/testing/selftests/net/srv6_end_map_test.sh b/tools/testing/selftests/net/srv6_end_map_test.sh
new file mode 100755
index 0000000000000..a4efcb58a1e4d
--- /dev/null
+++ b/tools/testing/selftests/net/srv6_end_map_test.sh
@@ -0,0 +1,400 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# author: Yuya Kusakabe <yuya.kusakabe@gmail.com>
+
+# 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 H.Encaps scenario).
+#
+# 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 -- the forwarding cases assert
+# that the counter does not move.
+#
+# Eight 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. RH not an SRH -- a C helper sends a packet whose Routing
+# Header is type 0 rather than 4; End.MAP must
+# drop it. The behavior's own errors counter
+# binds the assertion to the drop.
+# 5. SRH malformed -- the helper sends an SRH whose Last Entry
+# exceeds its length; End.MAP must drop it.
+# 6. SRH exhausted -- the helper sends a valid SRH with Segments
+# Left 0, so the IPv6 DA already is the final
+# destination; End.MAP must patch the L4
+# checksum as in the SRH-absent case and the
+# receiver must accept the echo.
+# 7. Fragmented -- an oversized echo without an SRH is fragmented
+# by rt-1; only the first fragment carries the
+# ICMPv6 header, and End.MAP must patch its
+# checksum so the reassembled echo verifies at
+# rt-3.
+# 8. 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=4
+readonly COUNTER_TIMEOUT_SEC=2
+readonly END_MAP_PREFIX="2001:db8:f::/64"
+readonly END_MAP_SID="2001:db8:f::1"
+readonly RT3_SID="2001:db8:3::3"
+HELPER_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
+readonly HELPER_DIR
+readonly HELPER="${HELPER_DIR}/srv6_mobile_send"
+
+ret=0
+nsuccess=0
+nfail=0
+
+PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}
+
+log_test()
+{
+ local rc=$1
+ local expected=$2
+ local msg="$3"
+
+ if [ "${rc}" -eq "${expected}" ]; then
+ nsuccess=$((nsuccess + 1))
+ printf "\n TEST: %-60s [ OK ]\n" "${msg}"
+ else
+ ret=1
+ nfail=$((nfail + 1))
+ printf "\n TEST: %-60s [FAIL]\n" "${msg}"
+ if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
+ echo
+ echo "hit enter to continue, 'q' to quit"
+ read -r a
+ [ "$a" = "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 nstat sysctl ethtool; 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
+
+ # shellcheck disable=SC2154 # variables assigned by setup_ns
+ for ns in "$rt1" "$rt2" "$rt3"; do
+ ip -n "$ns" link set lo up
+ done
+
+ 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=1
+
+ # 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=1
+ ip netns exec "$rt3" \
+ sysctl -wq net.ipv6.conf.veth2-rt3.seg6_enabled=1
+ ip netns exec "$rt3" sysctl -wq net.ipv6.conf.lo.seg6_enabled=1
+
+ # 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
+ ip netns exec "$rt2" ethtool -K veth1-rt2 tx off rx off
+ ip netns exec "$rt2" ethtool -K veth2 tx off rx off
+ ip netns exec "$rt3" ethtool -K veth2-rt3 tx off rx off
+
+ # 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 drop tests read.
+ ip -n "$rt2" -6 route add "$END_MAP_PREFIX" \
+ encap seg6mobile action End.MAP mapped_sid "$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=$1
+ local name=$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="$name" '$1 == 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_srh_absent()
+{
+ local before after rc=0
+
+ before=$(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=1
+ fi
+
+ if [ "$rc" -eq 0 ]; then
+ after=$(read_nstat_counter "$rt3" Icmp6InCsumErrors)
+ [ "$before" != "$after" ] && rc=1
+ fi
+
+ log_test "$rc" 0 "End.MAP forwards an ICMPv6 echo without an SRH"
+}
+
+test_srh_present()
+{
+ local before after rc=0
+
+ before=$(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=1
+ fi
+
+ if [ "$rc" -eq 0 ]; then
+ after=$(read_nstat_counter "$rt3" Icmp6InCsumErrors)
+ [ "$before" != "$after" ] && rc=1
+ fi
+
+ log_test "$rc" 0 "End.MAP preserves an SRH carried by H.Encaps"
+}
+
+test_srh_inline()
+{
+ local before after rc=0
+
+ 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=$(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=1
+ fi
+
+ if [ "$rc" -eq 0 ]; then
+ after=$(read_nstat_counter "$rt3" Icmp6InCsumErrors)
+ [ "$before" != "$after" ] && rc=1
+ fi
+
+ log_test "$rc" 0 "End.MAP preserves L4 csum across mode inline SRH"
+}
+
+# The drop happens on rt-2's receive path, asynchronously to the
+# sender, so wait for the errors counter instead of sampling it.
+expect_end_map_drop()
+{
+ local before rc=0
+
+ before=$(read_route_errors)
+
+ ip netns exec "$rt1" "$HELPER" -m end-map "$@" \
+ -s 2001:db8:1::1 -d "$END_MAP_SID" >/dev/null 2>&1
+
+ slowwait "$COUNTER_TIMEOUT_SEC" until_counter_is ">= $((before + 1))" \
+ read_route_errors >/dev/null || rc=1
+ [ "$(read_route_errors)" -eq "$((before + 1))" ] || rc=1
+
+ echo "$rc"
+}
+
+test_rh_not_srh()
+{
+ local rc
+
+ rc=$(expect_end_map_drop --rh-type 0)
+ log_test "$rc" 0 "End.MAP drops a routing header that is not an SRH"
+}
+
+test_srh_malformed()
+{
+ local rc
+
+ rc=$(expect_end_map_drop --bad-srh)
+ log_test "$rc" 0 "End.MAP drops a malformed SRH"
+}
+
+test_srh_exhausted()
+{
+ local before_echos before_csum rc=0
+
+ before_echos=$(read_nstat_counter "$rt3" Icmp6InEchos)
+ before_csum=$(read_nstat_counter "$rt3" Icmp6InCsumErrors)
+
+ ip netns exec "$rt1" "$HELPER" -m end-map \
+ -s 2001:db8:1::1 -d "$END_MAP_SID" >/dev/null 2>&1
+
+ slowwait "$COUNTER_TIMEOUT_SEC" \
+ until_counter_is ">= $((before_echos + 1))" \
+ read_nstat_counter "$rt3" Icmp6InEchos >/dev/null || rc=1
+ [ "$(read_nstat_counter "$rt3" Icmp6InCsumErrors)" -eq \
+ "$before_csum" ] || rc=1
+
+ log_test "$rc" 0 "End.MAP patches the L4 csum of an exhausted SRH"
+}
+
+test_fragmented()
+{
+ local before after rc=0
+
+ before=$(read_nstat_counter "$rt3" Icmp6InCsumErrors)
+
+ if ! ip netns exec "$rt1" \
+ ping -6 -c 1 -s 2000 -M dont -W "$PING_TIMEOUT_SEC" \
+ "$END_MAP_SID" >/dev/null 2>&1; then
+ rc=1
+ fi
+
+ if [ "$rc" -eq 0 ]; then
+ after=$(read_nstat_counter "$rt3" Icmp6InCsumErrors)
+ [ "$before" != "$after" ] && rc=1
+ fi
+
+ log_test "$rc" 0 "End.MAP patches the L4 csum of a fragmented echo"
+}
+
+test_hoplimit_expiry()
+{
+ local before after rc=0
+
+ before=$(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=$(read_nstat_counter "$rt2" Icmp6OutTimeExcds)
+ [ "$((after - before))" -eq 1 ] || rc=1
+
+ 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_rh_not_srh
+ test_srh_malformed
+ test_srh_exhausted
+ test_fragmented
+ 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 0000000000000..01329ec616761
--- /dev/null
+++ b/tools/testing/selftests/net/srv6_mobile_send.c
@@ -0,0 +1,262 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Author: Yuya Kusakabe (yuya.kusakabe@gmail.com)
+ *
+ * Helper for SRv6 Mobile (RFC 9433) selftests.
+ *
+ * Usage:
+ * srv6_mobile_send -m end-map -s <src> -d <dst> [--rh-type N] [--bad-srh]
+ */
+
+#include <arpa/inet.h>
+#include <assert.h>
+#include <errno.h>
+#include <getopt.h>
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+/* 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 = 2 advertises.
+ */
+struct srh_one_seg {
+ struct ip6_rthdr rthdr;
+ uint32_t type_data;
+ struct in6_addr segment;
+};
+
+/* Built as a struct so the headers are written through typed, aligned
+ * members rather than casts of a byte array; static_assert keeps it
+ * exactly the on-wire layout.
+ */
+struct end_map_frame {
+ struct ip6_hdr ip6;
+ struct srh_one_seg srh;
+ struct icmp6_hdr icmp6;
+};
+
+static_assert(sizeof(struct end_map_frame) ==
+ sizeof(struct ip6_hdr) + sizeof(struct srh_one_seg) +
+ sizeof(struct icmp6_hdr),
+ "end_map_frame must not contain padding");
+
+enum mode {
+ MODE_NONE,
+ MODE_END_MAP,
+};
+
+struct cfg {
+ enum mode mode;
+ struct in6_addr src6;
+ struct in6_addr dst6;
+ uint8_t rh_type;
+ bool bad_srh;
+};
+
+static void usage(const char *bin)
+{
+ fprintf(stderr,
+ "Usage: %s -m <mode> -s <src> -d <dst> [opts]\n"
+ "\n"
+ "Modes:\n"
+ " end-map Send IPv6 + SRH + ICMPv6 echo for End.MAP testing\n"
+ "\n"
+ "Mode end-map options:\n"
+ " --rh-type <n> Routing Header type (default 4, the SRH)\n"
+ " --bad-srh emit an SRH whose Last Entry exceeds its length (drop test)\n"
+ "\n"
+ "Exit: 0 sent, 1 failure, 3 invalid arguments.\n",
+ bin);
+}
+
+static int parse_u32(const char *s, uint32_t *out)
+{
+ unsigned long v;
+ char *end;
+
+ errno = 0;
+ v = strtoul(s, &end, 0);
+ if (errno || !*s || *end || v > 0xffffffffUL)
+ return -1;
+ *out = (uint32_t)v;
+ return 0;
+}
+
+static int parse_u8(const char *s, uint8_t *out)
+{
+ uint32_t v;
+
+ if (parse_u32(s, &v) || v > 0xff)
+ return -1;
+ *out = (uint8_t)v;
+ return 0;
+}
+
+static enum mode parse_mode(const char *s)
+{
+ if (!strcmp(s, "end-map"))
+ return MODE_END_MAP;
+ return MODE_NONE;
+}
+
+static int parse_args(int argc, char **argv, struct cfg *cfg)
+{
+ enum { OPT_RH_TYPE = 256, OPT_BAD_SRH };
+ static const struct option longopts[] = {
+ { "rh-type", required_argument, NULL, OPT_RH_TYPE },
+ { "bad-srh", no_argument, NULL, OPT_BAD_SRH },
+ { NULL, 0, NULL, 0 },
+ };
+ int c;
+
+ cfg->rh_type = 4; /* RFC 8754: the SRH is Routing Header type 4 */
+ while ((c = getopt_long(argc, argv, "m:s:d:", longopts, NULL))
+ != -1) {
+ switch (c) {
+ case 'm':
+ cfg->mode = parse_mode(optarg);
+ break;
+ case 's':
+ if (inet_pton(AF_INET6, optarg, &cfg->src6) != 1)
+ return -1;
+ break;
+ case 'd':
+ if (inet_pton(AF_INET6, optarg, &cfg->dst6) != 1)
+ return -1;
+ break;
+ case OPT_RH_TYPE:
+ if (parse_u8(optarg, &cfg->rh_type))
+ return -1;
+ break;
+ case OPT_BAD_SRH:
+ cfg->bad_srh = true;
+ break;
+ default:
+ return -1;
+ }
+ }
+ if (cfg->mode == MODE_NONE)
+ return -1;
+ return 0;
+}
+
+static uint16_t csum_fold(uint32_t sum)
+{
+ while (sum >> 16)
+ sum = (sum & 0xffff) + (sum >> 16);
+ return ~sum;
+}
+
+static uint32_t csum_partial(const void *buf, size_t len, uint32_t sum)
+{
+ const uint8_t *p = buf;
+ uint16_t word;
+
+ while (len > 1) {
+ memcpy(&word, p, sizeof(word));
+ sum += word;
+ p += sizeof(word);
+ len -= sizeof(word);
+ }
+ if (len)
+ sum += *p;
+ return sum;
+}
+
+static uint16_t pseudo_csum(const struct in6_addr *src,
+ const struct in6_addr *dst,
+ uint32_t plen, uint8_t nexthdr,
+ const void *payload, size_t len)
+{
+ uint32_t nh = htonl(nexthdr);
+ uint32_t pl = htonl(plen);
+ uint32_t sum;
+
+ sum = csum_partial(src, sizeof(*src), 0);
+ sum = csum_partial(dst, sizeof(*dst), sum);
+ sum = csum_partial(&pl, sizeof(pl), sum);
+ sum = csum_partial(&nh, sizeof(nh), sum);
+ sum = csum_partial(payload, len, sum);
+ return csum_fold(sum);
+}
+
+static int send_end_map(const struct cfg *cfg)
+{
+ struct sockaddr_in6 dst_addr = { .sin6_family = AF_INET6 };
+ struct end_map_frame frame = {};
+ ssize_t res;
+ int fd;
+
+ frame.ip6.ip6_flow = htonl(6u << 28);
+ frame.ip6.ip6_plen = htons(sizeof(frame.srh) + sizeof(frame.icmp6));
+ frame.ip6.ip6_nxt = IPPROTO_ROUTING;
+ frame.ip6.ip6_hops = 64;
+ frame.ip6.ip6_src = cfg->src6;
+ frame.ip6.ip6_dst = cfg->dst6;
+
+ frame.srh.rthdr.ip6r_nxt = IPPROTO_ICMPV6;
+ frame.srh.rthdr.ip6r_len = 2; /* (1 + ip6r_len) * 8 = 24 */
+ frame.srh.rthdr.ip6r_type = cfg->rh_type;
+ frame.srh.rthdr.ip6r_segleft = 0;
+ /* SRH Last Entry is the high byte of the type-specific word. A
+ * single segment makes it 0; --bad-srh claims a second segment the
+ * header has no room for, which seg6_validate_srh() rejects.
+ */
+ frame.srh.type_data = htonl((uint32_t)(cfg->bad_srh ? 1 : 0) << 24);
+ frame.srh.segment = frame.ip6.ip6_dst;
+
+ frame.icmp6.icmp6_type = ICMP6_ECHO_REQUEST;
+ frame.icmp6.icmp6_code = 0;
+ frame.icmp6.icmp6_dataun.icmp6_un_data16[0] = htons(0x1234);
+ frame.icmp6.icmp6_dataun.icmp6_un_data16[1] = htons(1);
+ frame.icmp6.icmp6_cksum = pseudo_csum(&frame.ip6.ip6_src,
+ &frame.ip6.ip6_dst,
+ sizeof(frame.icmp6),
+ IPPROTO_ICMPV6, &frame.icmp6,
+ sizeof(frame.icmp6));
+
+ fd = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW);
+ if (fd < 0) {
+ perror("socket");
+ return 1;
+ }
+ dst_addr.sin6_addr = frame.ip6.ip6_dst;
+
+ res = sendto(fd, &frame, sizeof(frame), 0,
+ (struct sockaddr *)&dst_addr, sizeof(dst_addr));
+ close(fd);
+ if (res != (ssize_t)sizeof(frame)) {
+ perror("sendto");
+ return 1;
+ }
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ struct cfg cfg = {};
+
+ if (parse_args(argc, argv, &cfg)) {
+ usage(argv[0]);
+ return 3;
+ }
+
+ switch (cfg.mode) {
+ case MODE_END_MAP:
+ return send_end_map(&cfg);
+ default:
+ usage(argv[0]);
+ return 3;
+ }
+}
|
| 19/3 | 2026/09/18 14:54 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 20852 output: 21 thoughts: 919 Thoughts: **Analyzing the SRv6 Mobile User Plane Patch** Okay, so this patch is introducing SRv6 Mobile User Plane (RFC 9433) behaviors, specifically the `LWTUNNEL_ENCAP_SEG6_MOBILE` encapsulation. My immediate concern, as always, is a deep dive into the memory management and the data structures involved. I need to understand how this is handled to prevent potential vulnerabilities. Let's break this down systematically. First, I see the changes span across several areas: includes, core LWTunnel code, Kconfig, and the Makefile, and the new implementation file, `seg6_mobile.c`. A quick glance tells me there's an initialization and exit hook, enum definitions for the new functionality and netlink attributes, and the core LWTunnel configuration. I'm particularly interested in the `seg6_mobile.c` implementation. Now, let's look closely at `seg6_mobile.c`. The core structures are here: `seg6_mobile_lwt`, `seg6_mobile_action_desc`, `seg6_mobile_action_param`, and the per-CPU counters. I need to understand what each structure represents, how they are initialized, and how they are used. I'm seeing allocation of per-CPU counters with `seg6_mobile_alloc_pcpu_counters()`, which looks like it uses `__netdev_alloc_pcpu_stats()`. I need to verify how these are handled and if there's sufficient protection against race conditions, especially given the per-CPU nature. The `struct seg6_mobile_lwt` holds the important context and configuration. I need to check how the parsed attributes are handled and how the optional attributes are tracked. Now, let's zoom in on `lwtunnel_state_alloc(sizeof(*slwt))`. This is where the core allocation of the LWTunnel state happens. I need to find the definition of `lwtunnel_state_alloc` and see how it handles memory initialization, specifically for `struct seg6_mobile_lwt`. Are we zero-initializing, using a specific allocator, or relying on something else? Uninitialized memory is a huge red flag and needs to be addressed carefully. |
| 20/3 | 2026/09/18 14:54 | tool | grepper |
0mArgs:{
"Expression": "lwtunnel_state_alloc"
}Results: {
"Output": "include/net/lwtunnel.h=129=int lwtunnel_get_encap_size(struct lwtunnel_state *lwtstate);\ninclude/net/lwtunnel.h:130:struct lwtunnel_state *lwtunnel_state_alloc(int hdr_len);\ninclude/net/lwtunnel.h-131-int lwtunnel_cmp_encap(struct lwtunnel_state *a, struct lwtunnel_state *b);\n--\ninclude/net/lwtunnel.h=235=static inline int lwtunnel_get_encap_size(struct lwtunnel_state *lwtstate)\n--\ninclude/net/lwtunnel.h-239-\ninclude/net/lwtunnel.h:240:static inline struct lwtunnel_state *lwtunnel_state_alloc(int hdr_len)\ninclude/net/lwtunnel.h-241-{\n--\nnet/core/lwt_bpf.c=383=static int bpf_build_state(struct net *net, struct nlattr *nla,\n--\nnet/core/lwt_bpf.c-403-\nnet/core/lwt_bpf.c:404:\tnewts = lwtunnel_state_alloc(sizeof(*bpf));\nnet/core/lwt_bpf.c-405-\tif (!newts)\n--\nnet/core/lwtunnel.c=33=static const char *lwtunnel_encap_str(enum lwtunnel_encap_types encap_type)\n--\nnet/core/lwtunnel.c-70-\nnet/core/lwtunnel.c:71:struct lwtunnel_state *lwtunnel_state_alloc(int encap_len)\nnet/core/lwtunnel.c-72-{\n--\nnet/core/lwtunnel.c-78-}\nnet/core/lwtunnel.c:79:EXPORT_SYMBOL_GPL(lwtunnel_state_alloc);\nnet/core/lwtunnel.c-80-\n--\nnet/ipv4/ip_tunnel_core.c=688=static int ip_tun_build_state(struct net *net, struct nlattr *attr,\n--\nnet/ipv4/ip_tunnel_core.c-706-\nnet/ipv4/ip_tunnel_core.c:707:\tnew_state = lwtunnel_state_alloc(sizeof(*tun_info) + opt_len);\nnet/ipv4/ip_tunnel_core.c-708-\tif (!new_state)\n--\nnet/ipv4/ip_tunnel_core.c=982=static int ip6_tun_build_state(struct net *net, struct nlattr *attr,\n--\nnet/ipv4/ip_tunnel_core.c-1000-\nnet/ipv4/ip_tunnel_core.c:1001:\tnew_state = lwtunnel_state_alloc(sizeof(*tun_info) + opt_len);\nnet/ipv4/ip_tunnel_core.c-1002-\tif (!new_state)\n--\nnet/ipv6/ila/ila_lwt.c=135=static int ila_build_state(struct net *net, struct nlattr *nla,\n--\nnet/ipv6/ila/ila_lwt.c-225-\nnet/ipv6/ila/ila_lwt.c:226:\tnewts = lwtunnel_state_alloc(sizeof(*ilwt));\nnet/ipv6/ila/ila_lwt.c-227-\tif (!newts)\n--\nnet/ipv6/ioam6_iptunnel.c=99=static int ioam6_build_state(struct net *net, struct nlattr *nla,\n--\nnet/ipv6/ioam6_iptunnel.c-163-\tlen_aligned = ALIGN(trace-\u003eremlen * 4, 8);\nnet/ipv6/ioam6_iptunnel.c:164:\tlwt = lwtunnel_state_alloc(sizeof(*ilwt) + len_aligned);\nnet/ipv6/ioam6_iptunnel.c-165-\tif (!lwt)\n--\nnet/ipv6/rpl_iptunnel.c=67=static int rpl_build_state(struct net *net, struct nlattr *nla,\n--\nnet/ipv6/rpl_iptunnel.c-98-\nnet/ipv6/rpl_iptunnel.c:99:\tnewts = lwtunnel_state_alloc(srh_len + sizeof(*rlwt));\nnet/ipv6/rpl_iptunnel.c-100-\tif (!newts)\n--\nnet/ipv6/seg6_iptunnel.c=747=static int seg6_build_state(struct net *net, struct nlattr *nla,\n--\nnet/ipv6/seg6_iptunnel.c-807-\nnet/ipv6/seg6_iptunnel.c:808:\tnewts = lwtunnel_state_alloc(tuninfo_len + sizeof(*slwt));\nnet/ipv6/seg6_iptunnel.c-809-\tif (!newts)\n--\nnet/ipv6/seg6_local.c=2517=static int seg6_local_build_state(struct net *net, struct nlattr *nla,\n--\nnet/ipv6/seg6_local.c-2538-\nnet/ipv6/seg6_local.c:2539:\tnewts = lwtunnel_state_alloc(sizeof(*slwt));\nnet/ipv6/seg6_local.c-2540-\tif (!newts)\n--\nnet/ipv6/seg6_mobile.c=544=static int seg6_mobile_build_state(struct net *net, struct nlattr *nla,\n--\nnet/ipv6/seg6_mobile.c-573-\nnet/ipv6/seg6_mobile.c:574:\tnewts = lwtunnel_state_alloc(sizeof(*slwt));\nnet/ipv6/seg6_mobile.c-575-\tif (!newts)\n--\nnet/mpls/mpls_iptunnel.c=163=static int mpls_build_state(struct net *net, struct nlattr *nla,\n--\nnet/mpls/mpls_iptunnel.c-188-\nnet/mpls/mpls_iptunnel.c:189:\tnewts = lwtunnel_state_alloc(struct_size(tun_encap_info, label,\nnet/mpls/mpls_iptunnel.c-190-\t\t\t\t\t\t n_labels));\n--\nnet/xfrm/xfrm_interface_core.c=73=static int xfrmi_build_state(struct net *net, struct nlattr *nla,\n--\nnet/xfrm/xfrm_interface_core.c-91-\nnet/xfrm/xfrm_interface_core.c:92:\tnew_state = lwtunnel_state_alloc(sizeof(*info));\nnet/xfrm/xfrm_interface_core.c-93-\tif (!new_state) {\n"
} |
| 21/3 | 2026/09/18 14:54 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 24064 output: 36 thoughts: 28 Thoughts: Here's a summary of my thought process, as requested: **Diving into `lwtunnel_state_alloc`** Okay, so I need to understand what's happening with `lwtunnel_state_alloc`. My initial instinct is to go straight to the source. Let's pull up the definition of that function, specifically in `net/core/lwtunnel.c`. I need a precise and reliable foundation. I want to see the *actual* implementation code. Knowing the context of this allocation is crucial for any deeper analysis. I'm after the low-level details, the memory footprint, and the intended use case. This function is foundational to the lightweight tunneling framework, so understanding its allocation mechanism is paramount. Let's see how this thing functions! |
| 22/3 | 2026/09/18 14:54 | tool | read-file |
0mArgs:{
"File": "net/core/lwtunnel.c",
"FirstLine": 70,
"LineCount": 15
}Results: {
"Contents": " 70:\t\n 71:\tstruct lwtunnel_state *lwtunnel_state_alloc(int encap_len)\n 72:\t{\n 73:\t\tstruct lwtunnel_state *lws;\n 74:\t\n 75:\t\tlws = kzalloc(sizeof(*lws) + encap_len, GFP_ATOMIC);\n 76:\t\n 77:\t\treturn lws;\n 78:\t}\n 79:\tEXPORT_SYMBOL_GPL(lwtunnel_state_alloc);\n 80:\t\n 81:\tstatic const struct lwtunnel_encap_ops __rcu *\n 82:\t\t\tlwtun_encaps[LWTUNNEL_ENCAP_MAX + 1] __read_mostly;\n 83:\t\n 84:\tint lwtunnel_encap_add_ops(const struct lwtunnel_encap_ops *ops,\n"
} |
| 23/3 | 2026/09/18 14:54 | llm | kmsan-evaluator |
3mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 24535 output: 26 thoughts: 1493 Thoughts: **Analyzing Potential Uninitialized Memory Issues in `seg6_mobile.c`** Okay, so I'm looking at this `seg6_mobile.c` code and trying to assess potential vulnerabilities related to uninitialized memory. I'm focusing on scenarios where kernel data structures are sent to userspace, or where uninitialized variables might be used in conditional logic or data structure initialization. First things first, the `lwtunnel_state_alloc` function uses `kzalloc`, which means the allocated state is zeroed out initially. That's a good starting point. Now, I'm specifically examining the `seg6_mobile_fill_encap` function and how it populates netlink skb attributes. The key question is whether any uninitialized data could leak to userspace through these attributes. Let's dig into the details: I see the `seg6_mobile_fill_encap` function iterates through attributes, and I am following the logic. I am going to analyze specific attributes one at a time. First, let's look at `SEG6_MOBILE_MAPPED_SID`. The data for `slwt->mapped_sid` is populated by `parse_nla_mapped_sid`, and it's clear that the data is fully copied from the netlink attribute, which itself is validated by the `NLA_POLICY_EXACT_LEN` policy to be the exact size of an `in6_addr`. The netlink attribute is already checked to contain the expected data. This implies that if it has been received, `slwt->mapped_sid` is either initialized at allocation (by `kzalloc`) or from the incoming netlink attribute. I think we are good here. When put, `nla_put_in6_addr` writes the correct `sizeof(struct in6_addr)` bytes. Now, let's analyze `SEG6_MOBILE_COUNTERS`. This attribute is more interesting. The `put_nla_counters` function creates a local `seg6_mobile_counters` structure, initializes it to zero on the stack, and then populates it by summing per-CPU counters using `for_each_possible_cpu`. The critical detail here is that `counters` is *initialized* before being populated. Then it calls `seg6_mobile_fill_nla_counters`, which uses `nla_put_u64_64bit` to write the counter values. From looking at `nla_put_u64_64bit`, it's writing a 64-bit scalar value, which pads with zero. This is safe, because counters are properly initialized to 0. So it looks like we're good here. The counter values are properly summed and zero-initialized. In summary, all the values are either zeroed, or fully initialized by copying. No direct leaks of uninitialized data seems to occur in the code I've examined. I am feeling confident in that assertion. |
| 24/3 | 2026/09/18 14:58 | tool | grepper |
0mArgs:{
"Expression": "nla_put_u64_64bit"
}Results: {
"Output": "\nFull output is too long, showing 500 out of 1765 lines.\nUse more precise expression if possible.\n\ndrivers/block/drbd/drbd_nl.c=3822=static int nla_put_status_info(struct sk_buff *skb, struct drbd_device *device,\n--\ndrivers/block/drbd/drbd_nl.c-3879-\t\t device-\u003estate.i) ||\ndrivers/block/drbd/drbd_nl.c:3880:\t nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_ED_UUID,\ndrivers/block/drbd/drbd_nl.c-3881-\t\t\t device-\u003eed_uuid, 0) ||\ndrivers/block/drbd/drbd_nl.c:3882:\t nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_CAPACITY,\ndrivers/block/drbd/drbd_nl.c-3883-\t\t\t get_capacity(device-\u003evdisk), 0) ||\ndrivers/block/drbd/drbd_nl.c:3884:\t nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_SEND_CNT,\ndrivers/block/drbd/drbd_nl.c-3885-\t\t\t device-\u003esend_cnt, 0) ||\ndrivers/block/drbd/drbd_nl.c:3886:\t nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_RECV_CNT,\ndrivers/block/drbd/drbd_nl.c-3887-\t\t\t device-\u003erecv_cnt, 0) ||\ndrivers/block/drbd/drbd_nl.c:3888:\t nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_READ_CNT,\ndrivers/block/drbd/drbd_nl.c-3889-\t\t\t device-\u003eread_cnt, 0) ||\ndrivers/block/drbd/drbd_nl.c:3890:\t nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_WRIT_CNT,\ndrivers/block/drbd/drbd_nl.c-3891-\t\t\t device-\u003ewrit_cnt, 0) ||\ndrivers/block/drbd/drbd_nl.c:3892:\t nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_AL_WRIT_CNT,\ndrivers/block/drbd/drbd_nl.c-3893-\t\t\t device-\u003eal_writ_cnt, 0) ||\ndrivers/block/drbd/drbd_nl.c:3894:\t nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_BM_WRIT_CNT,\ndrivers/block/drbd/drbd_nl.c-3895-\t\t\t device-\u003ebm_writ_cnt, 0) ||\n--\ndrivers/block/drbd/drbd_nl.c-3916-\t\tif (nla_put_u32(skb, DRBD_A_STATE_INFO_DISK_FLAGS, device-\u003eldev-\u003emd.flags) ||\ndrivers/block/drbd/drbd_nl.c:3917:\t\t nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_BITS_TOTAL, drbd_bm_bits(device), 0) ||\ndrivers/block/drbd/drbd_nl.c:3918:\t\t nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_BITS_OOS,\ndrivers/block/drbd/drbd_nl.c-3919-\t\t\t\t drbd_bm_total_weight(device), 0))\n--\ndrivers/block/drbd/drbd_nl.c-3922-\t\t C_PAUSED_SYNC_T \u003e= device-\u003estate.conn) {\ndrivers/block/drbd/drbd_nl.c:3923:\t\t\tif (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_BITS_RS_TOTAL,\ndrivers/block/drbd/drbd_nl.c-3924-\t\t\t\t\t device-\u003ers_total, 0) ||\ndrivers/block/drbd/drbd_nl.c:3925:\t\t\t nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_BITS_RS_FAILED,\ndrivers/block/drbd/drbd_nl.c-3926-\t\t\t\t\t device-\u003ers_failed, 0))\n--\ndrivers/block/drbd/drbd_nl_gen.c=1904=int disk_conf_to_skb(struct sk_buff *skb, struct disk_conf *s)\n--\ndrivers/block/drbd/drbd_nl_gen.c-1918-\t\tgoto nla_put_failure;\ndrivers/block/drbd/drbd_nl_gen.c:1919:\tif (nla_put_u64_64bit(skb, DRBD_A_DISK_CONF_DISK_SIZE, s-\u003edisk_size, 0))\ndrivers/block/drbd/drbd_nl_gen.c-1920-\t\tgoto nla_put_failure;\n--\ndrivers/block/drbd/drbd_nl_gen.c=2098=int resize_parms_to_skb(struct sk_buff *skb, struct resize_parms *s)\n--\ndrivers/block/drbd/drbd_nl_gen.c-2104-\ndrivers/block/drbd/drbd_nl_gen.c:2105:\tif (nla_put_u64_64bit(skb, DRBD_A_RESIZE_PARMS_RESIZE_SIZE, s-\u003eresize_size, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2106-\t\tgoto nla_put_failure;\n--\ndrivers/block/drbd/drbd_nl_gen.c=2125=int state_info_to_skb(struct sk_buff *skb, struct state_info *s)\n--\ndrivers/block/drbd/drbd_nl_gen.c-2135-\t\tgoto nla_put_failure;\ndrivers/block/drbd/drbd_nl_gen.c:2136:\tif (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_CAPACITY, s-\u003ecapacity, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2137-\t\tgoto nla_put_failure;\ndrivers/block/drbd/drbd_nl_gen.c:2138:\tif (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_ED_UUID, s-\u003eed_uuid, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2139-\t\tgoto nla_put_failure;\n--\ndrivers/block/drbd/drbd_nl_gen.c-2148-\t\tgoto nla_put_failure;\ndrivers/block/drbd/drbd_nl_gen.c:2149:\tif (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_BITS_TOTAL, s-\u003ebits_total, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2150-\t\tgoto nla_put_failure;\ndrivers/block/drbd/drbd_nl_gen.c:2151:\tif (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_BITS_OOS, s-\u003ebits_oos, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2152-\t\tgoto nla_put_failure;\ndrivers/block/drbd/drbd_nl_gen.c:2153:\tif (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_BITS_RS_TOTAL, s-\u003ebits_rs_total, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2154-\t\tgoto nla_put_failure;\ndrivers/block/drbd/drbd_nl_gen.c:2155:\tif (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_BITS_RS_FAILED, s-\u003ebits_rs_failed, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2156-\t\tgoto nla_put_failure;\n--\ndrivers/block/drbd/drbd_nl_gen.c-2161-\t\tgoto nla_put_failure;\ndrivers/block/drbd/drbd_nl_gen.c:2162:\tif (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_SEND_CNT, s-\u003esend_cnt, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2163-\t\tgoto nla_put_failure;\ndrivers/block/drbd/drbd_nl_gen.c:2164:\tif (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_RECV_CNT, s-\u003erecv_cnt, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2165-\t\tgoto nla_put_failure;\ndrivers/block/drbd/drbd_nl_gen.c:2166:\tif (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_READ_CNT, s-\u003eread_cnt, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2167-\t\tgoto nla_put_failure;\ndrivers/block/drbd/drbd_nl_gen.c:2168:\tif (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_WRIT_CNT, s-\u003ewrit_cnt, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2169-\t\tgoto nla_put_failure;\ndrivers/block/drbd/drbd_nl_gen.c:2170:\tif (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_AL_WRIT_CNT, s-\u003eal_writ_cnt, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2171-\t\tgoto nla_put_failure;\ndrivers/block/drbd/drbd_nl_gen.c:2172:\tif (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_BM_WRIT_CNT, s-\u003ebm_writ_cnt, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2173-\t\tgoto nla_put_failure;\n--\ndrivers/block/drbd/drbd_nl_gen.c=2190=int start_ov_parms_to_skb(struct sk_buff *skb, struct start_ov_parms *s)\n--\ndrivers/block/drbd/drbd_nl_gen.c-2196-\ndrivers/block/drbd/drbd_nl_gen.c:2197:\tif (nla_put_u64_64bit(skb, DRBD_A_START_OV_PARMS_OV_START_SECTOR, s-\u003eov_start_sector, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2198-\t\tgoto nla_put_failure;\ndrivers/block/drbd/drbd_nl_gen.c:2199:\tif (nla_put_u64_64bit(skb, DRBD_A_START_OV_PARMS_OV_STOP_SECTOR, s-\u003eov_stop_sector, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2200-\t\tgoto nla_put_failure;\n--\ndrivers/block/drbd/drbd_nl_gen.c=2398=int device_statistics_to_skb(struct sk_buff *skb, struct device_statistics *s)\n--\ndrivers/block/drbd/drbd_nl_gen.c-2404-\ndrivers/block/drbd/drbd_nl_gen.c:2405:\tif (nla_put_u64_64bit(skb, DRBD_A_DEVICE_STATISTICS_DEV_SIZE, s-\u003edev_size, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2406-\t\tgoto nla_put_failure;\ndrivers/block/drbd/drbd_nl_gen.c:2407:\tif (nla_put_u64_64bit(skb, DRBD_A_DEVICE_STATISTICS_DEV_READ, s-\u003edev_read, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2408-\t\tgoto nla_put_failure;\ndrivers/block/drbd/drbd_nl_gen.c:2409:\tif (nla_put_u64_64bit(skb, DRBD_A_DEVICE_STATISTICS_DEV_WRITE, s-\u003edev_write, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2410-\t\tgoto nla_put_failure;\ndrivers/block/drbd/drbd_nl_gen.c:2411:\tif (nla_put_u64_64bit(skb, DRBD_A_DEVICE_STATISTICS_DEV_AL_WRITES, s-\u003edev_al_writes, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2412-\t\tgoto nla_put_failure;\ndrivers/block/drbd/drbd_nl_gen.c:2413:\tif (nla_put_u64_64bit(skb, DRBD_A_DEVICE_STATISTICS_DEV_BM_WRITES, s-\u003edev_bm_writes, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2414-\t\tgoto nla_put_failure;\n--\ndrivers/block/drbd/drbd_nl_gen.c-2424-\t\tgoto nla_put_failure;\ndrivers/block/drbd/drbd_nl_gen.c:2425:\tif (nla_put_u64_64bit(skb, DRBD_A_DEVICE_STATISTICS_DEV_EXPOSED_DATA_UUID, s-\u003edev_exposed_data_uuid, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2426-\t\tgoto nla_put_failure;\ndrivers/block/drbd/drbd_nl_gen.c:2427:\tif (nla_put_u64_64bit(skb, DRBD_A_DEVICE_STATISTICS_DEV_CURRENT_UUID, s-\u003edev_current_uuid, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2428-\t\tgoto nla_put_failure;\n--\ndrivers/block/drbd/drbd_nl_gen.c=2463=int peer_device_statistics_to_skb(struct sk_buff *skb, struct peer_device_statistics *s)\n--\ndrivers/block/drbd/drbd_nl_gen.c-2469-\ndrivers/block/drbd/drbd_nl_gen.c:2470:\tif (nla_put_u64_64bit(skb, DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_RECEIVED, s-\u003epeer_dev_received, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2471-\t\tgoto nla_put_failure;\ndrivers/block/drbd/drbd_nl_gen.c:2472:\tif (nla_put_u64_64bit(skb, DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_SENT, s-\u003epeer_dev_sent, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2473-\t\tgoto nla_put_failure;\n--\ndrivers/block/drbd/drbd_nl_gen.c-2477-\t\tgoto nla_put_failure;\ndrivers/block/drbd/drbd_nl_gen.c:2478:\tif (nla_put_u64_64bit(skb, DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_OUT_OF_SYNC, s-\u003epeer_dev_out_of_sync, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2479-\t\tgoto nla_put_failure;\ndrivers/block/drbd/drbd_nl_gen.c:2480:\tif (nla_put_u64_64bit(skb, DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_RESYNC_FAILED, s-\u003epeer_dev_resync_failed, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2481-\t\tgoto nla_put_failure;\ndrivers/block/drbd/drbd_nl_gen.c:2482:\tif (nla_put_u64_64bit(skb, DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_BITMAP_UUID, s-\u003epeer_dev_bitmap_uuid, 0))\ndrivers/block/drbd/drbd_nl_gen.c-2483-\t\tgoto nla_put_failure;\n--\ndrivers/infiniband/core/nldev.c=218=static int _rdma_nl_put_driver_u64(struct sk_buff *msg, const char *name,\n--\ndrivers/infiniband/core/nldev.c-223-\t\treturn -EMSGSIZE;\ndrivers/infiniband/core/nldev.c:224:\tif (nla_put_u64_64bit(msg, RDMA_NLDEV_ATTR_DRIVER_U64, value,\ndrivers/infiniband/core/nldev.c-225-\t\t\t RDMA_NLDEV_ATTR_PAD))\n--\ndrivers/infiniband/core/nldev.c=290=static int fill_dev_info(struct sk_buff *msg, struct ib_device *device)\n--\ndrivers/infiniband/core/nldev.c-302-\tBUILD_BUG_ON(sizeof(device-\u003eattrs.device_cap_flags) != sizeof(u64));\ndrivers/infiniband/core/nldev.c:303:\tif (nla_put_u64_64bit(msg, RDMA_NLDEV_ATTR_CAP_FLAGS,\ndrivers/infiniband/core/nldev.c-304-\t\t\t device-\u003eattrs.device_cap_flags,\n--\ndrivers/infiniband/core/nldev.c-312-\ndrivers/infiniband/core/nldev.c:313:\tif (nla_put_u64_64bit(msg, RDMA_NLDEV_ATTR_NODE_GUID,\ndrivers/infiniband/core/nldev.c-314-\t\t\t be64_to_cpu(device-\u003enode_guid),\n--\ndrivers/infiniband/core/nldev.c-316-\t\treturn -EMSGSIZE;\ndrivers/infiniband/core/nldev.c:317:\tif (nla_put_u64_64bit(msg, RDMA_NLDEV_ATTR_SYS_IMAGE_GUID,\ndrivers/infiniband/core/nldev.c-318-\t\t\t be64_to_cpu(device-\u003eattrs.sys_image_guid),\n--\ndrivers/infiniband/core/nldev.c=359=static int fill_port_info(struct sk_buff *msg,\n--\ndrivers/infiniband/core/nldev.c-382-\t\t\t((u64)attr.port_cap_flags2 \u003c\u003c 32);\ndrivers/infiniband/core/nldev.c:383:\t\tif (nla_put_u64_64bit(msg, RDMA_NLDEV_ATTR_CAP_FLAGS,\ndrivers/infiniband/core/nldev.c-384-\t\t\t\t cap_flags, RDMA_NLDEV_ATTR_PAD))\ndrivers/infiniband/core/nldev.c-385-\t\t\treturn -EMSGSIZE;\ndrivers/infiniband/core/nldev.c:386:\t\tif (nla_put_u64_64bit(msg, RDMA_NLDEV_ATTR_SUBNET_PREFIX,\ndrivers/infiniband/core/nldev.c-387-\t\t\t\t attr.subnet_prefix, RDMA_NLDEV_ATTR_PAD))\n--\ndrivers/infiniband/core/nldev.c=416=static int fill_res_info_entry(struct sk_buff *msg,\n--\ndrivers/infiniband/core/nldev.c-427-\t\tgoto err;\ndrivers/infiniband/core/nldev.c:428:\tif (nla_put_u64_64bit(msg, RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_CURR, curr,\ndrivers/infiniband/core/nldev.c-429-\t\t\t RDMA_NLDEV_ATTR_PAD))\ndrivers/infiniband/core/nldev.c-430-\t\tgoto err;\ndrivers/infiniband/core/nldev.c:431:\tif (max \u0026\u0026 nla_put_u64_64bit(msg, RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_MAX,\ndrivers/infiniband/core/nldev.c-432-\t\t\t\t max, RDMA_NLDEV_ATTR_PAD))\n--\ndrivers/infiniband/core/nldev.c=681=static int fill_res_cq_entry(struct sk_buff *msg, bool has_cap_net_admin,\n--\ndrivers/infiniband/core/nldev.c-688-\t\treturn -EMSGSIZE;\ndrivers/infiniband/core/nldev.c:689:\tif (nla_put_u64_64bit(msg, RDMA_NLDEV_ATTR_RES_USECNT,\ndrivers/infiniband/core/nldev.c-690-\t\t\t atomic_read(\u0026cq-\u003eusecnt), RDMA_NLDEV_ATTR_PAD))\n--\ndrivers/infiniband/core/nldev.c=726=static int fill_res_mr_entry(struct sk_buff *msg, bool has_cap_net_admin,\n--\ndrivers/infiniband/core/nldev.c-738-\ndrivers/infiniband/core/nldev.c:739:\tif (nla_put_u64_64bit(msg, RDMA_NLDEV_ATTR_RES_MRLEN, mr-\u003elength,\ndrivers/infiniband/core/nldev.c-740-\t\t\t RDMA_NLDEV_ATTR_PAD))\n--\ndrivers/infiniband/core/nldev.c=772=static int fill_res_pd_entry(struct sk_buff *msg, bool has_cap_net_admin,\n--\ndrivers/infiniband/core/nldev.c-785-\t}\ndrivers/infiniband/core/nldev.c:786:\tif (nla_put_u64_64bit(msg, RDMA_NLDEV_ATTR_RES_USECNT,\ndrivers/infiniband/core/nldev.c-787-\t\t\t atomic_read(\u0026pd-\u003eusecnt), RDMA_NLDEV_ATTR_PAD))\n--\ndrivers/infiniband/core/nldev.c=1026=int rdma_nl_stat_hwcounter_entry(struct sk_buff *msg, const char *name,\n--\ndrivers/infiniband/core/nldev.c-1037-\t\tgoto err;\ndrivers/infiniband/core/nldev.c:1038:\tif (nla_put_u64_64bit(msg, RDMA_NLDEV_ATTR_STAT_HWCOUNTER_ENTRY_VALUE,\ndrivers/infiniband/core/nldev.c-1039-\t\t\t value, RDMA_NLDEV_ATTR_PAD))\n--\ndrivers/infiniband/core/nldev.c=1906=static int nldev_get_chardev(struct sk_buff *skb, struct nlmsghdr *nlh,\n--\ndrivers/infiniband/core/nldev.c-1962-\ndrivers/infiniband/core/nldev.c:1963:\terr = nla_put_u64_64bit(msg, RDMA_NLDEV_ATTR_CHARDEV,\ndrivers/infiniband/core/nldev.c-1964-\t\t\t\thuge_encode_dev(data.cdev-\u003edevt),\n--\ndrivers/infiniband/core/nldev.c-1967-\t\tgoto out_data;\ndrivers/infiniband/core/nldev.c:1968:\terr = nla_put_u64_64bit(msg, RDMA_NLDEV_ATTR_CHARDEV_ABI, data.abi,\ndrivers/infiniband/core/nldev.c-1969-\t\t\t\tRDMA_NLDEV_ATTR_PAD);\n--\ndrivers/infiniband/core/nldev.c=2720=static int fill_frmr_pool_key(struct sk_buff *msg, struct ib_frmr_key *key)\n--\ndrivers/infiniband/core/nldev.c-2732-\t\tgoto err;\ndrivers/infiniband/core/nldev.c:2733:\tif (nla_put_u64_64bit(msg, RDMA_NLDEV_ATTR_FRMR_POOL_KEY_VENDOR_KEY,\ndrivers/infiniband/core/nldev.c-2734-\t\t\t key-\u003evendor_key, RDMA_NLDEV_ATTR_PAD))\ndrivers/infiniband/core/nldev.c-2735-\t\tgoto err;\ndrivers/infiniband/core/nldev.c:2736:\tif (nla_put_u64_64bit(msg, RDMA_NLDEV_ATTR_FRMR_POOL_KEY_NUM_DMA_BLOCKS,\ndrivers/infiniband/core/nldev.c-2737-\t\t\t key-\u003enum_dma_blocks, RDMA_NLDEV_ATTR_PAD))\n--\ndrivers/infiniband/core/nldev.c-2740-\tif (key-\u003ekernel_vendor_key \u0026\u0026\ndrivers/infiniband/core/nldev.c:2741:\t nla_put_u64_64bit(msg,\ndrivers/infiniband/core/nldev.c-2742-\t\t\t RDMA_NLDEV_ATTR_FRMR_POOL_KEY_KERNEL_VENDOR_KEY,\n--\ndrivers/infiniband/core/nldev.c=2753=static int fill_frmr_pool_entry(struct sk_buff *msg, struct ib_frmr_pool *pool)\n--\ndrivers/infiniband/core/nldev.c-2761-\t\tgoto err_unlock;\ndrivers/infiniband/core/nldev.c:2762:\tif (nla_put_u64_64bit(msg, RDMA_NLDEV_ATTR_FRMR_POOL_MAX_IN_USE,\ndrivers/infiniband/core/nldev.c-2763-\t\t\t pool-\u003emax_in_use, RDMA_NLDEV_ATTR_PAD))\ndrivers/infiniband/core/nldev.c-2764-\t\tgoto err_unlock;\ndrivers/infiniband/core/nldev.c:2765:\tif (nla_put_u64_64bit(msg, RDMA_NLDEV_ATTR_FRMR_POOL_IN_USE,\ndrivers/infiniband/core/nldev.c-2766-\t\t\t pool-\u003ein_use, RDMA_NLDEV_ATTR_PAD))\n--\ndrivers/net/bonding/bond_3ad.c=3014=int bond_3ad_stats_fill(struct sk_buff *skb, struct bond_3ad_stats *stats)\n--\ndrivers/net/bonding/bond_3ad.c-3018-\tval = atomic64_read(\u0026stats-\u003elacpdu_rx);\ndrivers/net/bonding/bond_3ad.c:3019:\tif (nla_put_u64_64bit(skb, BOND_3AD_STAT_LACPDU_RX, val,\ndrivers/net/bonding/bond_3ad.c-3020-\t\t\t BOND_3AD_STAT_PAD))\n--\ndrivers/net/bonding/bond_3ad.c-3022-\tval = atomic64_read(\u0026stats-\u003elacpdu_tx);\ndrivers/net/bonding/bond_3ad.c:3023:\tif (nla_put_u64_64bit(skb, BOND_3AD_STAT_LACPDU_TX, val,\ndrivers/net/bonding/bond_3ad.c-3024-\t\t\t BOND_3AD_STAT_PAD))\n--\ndrivers/net/bonding/bond_3ad.c-3026-\tval = atomic64_read(\u0026stats-\u003elacpdu_unknown_rx);\ndrivers/net/bonding/bond_3ad.c:3027:\tif (nla_put_u64_64bit(skb, BOND_3AD_STAT_LACPDU_UNKNOWN_RX, val,\ndrivers/net/bonding/bond_3ad.c-3028-\t\t\t BOND_3AD_STAT_PAD))\n--\ndrivers/net/bonding/bond_3ad.c-3030-\tval = atomic64_read(\u0026stats-\u003elacpdu_illegal_rx);\ndrivers/net/bonding/bond_3ad.c:3031:\tif (nla_put_u64_64bit(skb, BOND_3AD_STAT_LACPDU_ILLEGAL_RX, val,\ndrivers/net/bonding/bond_3ad.c-3032-\t\t\t BOND_3AD_STAT_PAD))\n--\ndrivers/net/bonding/bond_3ad.c-3035-\tval = atomic64_read(\u0026stats-\u003emarker_rx);\ndrivers/net/bonding/bond_3ad.c:3036:\tif (nla_put_u64_64bit(skb, BOND_3AD_STAT_MARKER_RX, val,\ndrivers/net/bonding/bond_3ad.c-3037-\t\t\t BOND_3AD_STAT_PAD))\n--\ndrivers/net/bonding/bond_3ad.c-3039-\tval = atomic64_read(\u0026stats-\u003emarker_tx);\ndrivers/net/bonding/bond_3ad.c:3040:\tif (nla_put_u64_64bit(skb, BOND_3AD_STAT_MARKER_TX, val,\ndrivers/net/bonding/bond_3ad.c-3041-\t\t\t BOND_3AD_STAT_PAD))\n--\ndrivers/net/bonding/bond_3ad.c-3043-\tval = atomic64_read(\u0026stats-\u003emarker_resp_rx);\ndrivers/net/bonding/bond_3ad.c:3044:\tif (nla_put_u64_64bit(skb, BOND_3AD_STAT_MARKER_RESP_RX, val,\ndrivers/net/bonding/bond_3ad.c-3045-\t\t\t BOND_3AD_STAT_PAD))\n--\ndrivers/net/bonding/bond_3ad.c-3047-\tval = atomic64_read(\u0026stats-\u003emarker_resp_tx);\ndrivers/net/bonding/bond_3ad.c:3048:\tif (nla_put_u64_64bit(skb, BOND_3AD_STAT_MARKER_RESP_TX, val,\ndrivers/net/bonding/bond_3ad.c-3049-\t\t\t BOND_3AD_STAT_PAD))\n--\ndrivers/net/bonding/bond_3ad.c-3051-\tval = atomic64_read(\u0026stats-\u003emarker_unknown_rx);\ndrivers/net/bonding/bond_3ad.c:3052:\tif (nla_put_u64_64bit(skb, BOND_3AD_STAT_MARKER_UNKNOWN_RX, val,\ndrivers/net/bonding/bond_3ad.c-3053-\t\t\t BOND_3AD_STAT_PAD))\n--\ndrivers/net/gtp.c=2180=static int gtp_genl_fill_info(struct sk_buff *skb, u32 snd_portid, u32 snd_seq,\n--\ndrivers/net/gtp.c-2218-\tcase GTP_V0:\ndrivers/net/gtp.c:2219:\t\tif (nla_put_u64_64bit(skb, GTPA_TID, pctx-\u003eu.v0.tid, GTPA_PAD) ||\ndrivers/net/gtp.c-2220-\t\t nla_put_u16(skb, GTPA_FLOW, pctx-\u003eu.v0.flow))\n--\ndrivers/net/macsec.c=1555=static int nla_put_sci(struct sk_buff *skb, int attrtype, sci_t value,\n--\ndrivers/net/macsec.c-1557-{\ndrivers/net/macsec.c:1558:\treturn nla_put_u64_64bit(skb, attrtype, (__force u64)value, padattr);\ndrivers/net/macsec.c-1559-}\n--\ndrivers/net/macsec.c=2861=static int copy_rx_sc_stats(struct sk_buff *skb, struct macsec_rx_sc_stats *sum)\ndrivers/net/macsec.c-2862-{\ndrivers/net/macsec.c:2863:\tif (nla_put_u64_64bit(skb, MACSEC_RXSC_STATS_ATTR_IN_OCTETS_VALIDATED,\ndrivers/net/macsec.c-2864-\t\t\t sum-\u003eInOctetsValidated,\ndrivers/net/macsec.c-2865-\t\t\t MACSEC_RXSC_STATS_ATTR_PAD) ||\ndrivers/net/macsec.c:2866:\t nla_put_u64_64bit(skb, MACSEC_RXSC_STATS_ATTR_IN_OCTETS_DECRYPTED,\ndrivers/net/macsec.c-2867-\t\t\t sum-\u003eInOctetsDecrypted,\ndrivers/net/macsec.c-2868-\t\t\t MACSEC_RXSC_STATS_ATTR_PAD) ||\ndrivers/net/macsec.c:2869:\t nla_put_u64_64bit(skb, MACSEC_RXSC_STATS_ATTR_IN_PKTS_UNCHECKED,\ndrivers/net/macsec.c-2870-\t\t\t sum-\u003eInPktsUnchecked,\ndrivers/net/macsec.c-2871-\t\t\t MACSEC_RXSC_STATS_ATTR_PAD) ||\ndrivers/net/macsec.c:2872:\t nla_put_u64_64bit(skb, MACSEC_RXSC_STATS_ATTR_IN_PKTS_DELAYED,\ndrivers/net/macsec.c-2873-\t\t\t sum-\u003eInPktsDelayed,\ndrivers/net/macsec.c-2874-\t\t\t MACSEC_RXSC_STATS_ATTR_PAD) ||\ndrivers/net/macsec.c:2875:\t nla_put_u64_64bit(skb, MACSEC_RXSC_STATS_ATTR_IN_PKTS_OK,\ndrivers/net/macsec.c-2876-\t\t\t sum-\u003eInPktsOK,\ndrivers/net/macsec.c-2877-\t\t\t MACSEC_RXSC_STATS_ATTR_PAD) ||\ndrivers/net/macsec.c:2878:\t nla_put_u64_64bit(skb, MACSEC_RXSC_STATS_ATTR_IN_PKTS_INVALID,\ndrivers/net/macsec.c-2879-\t\t\t sum-\u003eInPktsInvalid,\ndrivers/net/macsec.c-2880-\t\t\t MACSEC_RXSC_STATS_ATTR_PAD) ||\ndrivers/net/macsec.c:2881:\t nla_put_u64_64bit(skb, MACSEC_RXSC_STATS_ATTR_IN_PKTS_LATE,\ndrivers/net/macsec.c-2882-\t\t\t sum-\u003eInPktsLate,\ndrivers/net/macsec.c-2883-\t\t\t MACSEC_RXSC_STATS_ATTR_PAD) ||\ndrivers/net/macsec.c:2884:\t nla_put_u64_64bit(skb, MACSEC_RXSC_STATS_ATTR_IN_PKTS_NOT_VALID,\ndrivers/net/macsec.c-2885-\t\t\t sum-\u003eInPktsNotValid,\ndrivers/net/macsec.c-2886-\t\t\t MACSEC_RXSC_STATS_ATTR_PAD) ||\ndrivers/net/macsec.c:2887:\t nla_put_u64_64bit(skb, MACSEC_RXSC_STATS_ATTR_IN_PKTS_NOT_USING_SA,\ndrivers/net/macsec.c-2888-\t\t\t sum-\u003eInPktsNotUsingSA,\ndrivers/net/macsec.c-2889-\t\t\t MACSEC_RXSC_STATS_ATTR_PAD) ||\ndrivers/net/macsec.c:2890:\t nla_put_u64_64bit(skb, MACSEC_RXSC_STATS_ATTR_IN_PKTS_UNUSED_SA,\ndrivers/net/macsec.c-2891-\t\t\t sum-\u003eInPktsUnusedSA,\n--\ndrivers/net/macsec.c=2936=static int copy_tx_sc_stats(struct sk_buff *skb, struct macsec_tx_sc_stats *sum)\ndrivers/net/macsec.c-2937-{\ndrivers/net/macsec.c:2938:\tif (nla_put_u64_64bit(skb, MACSEC_TXSC_STATS_ATTR_OUT_PKTS_PROTECTED,\ndrivers/net/macsec.c-2939-\t\t\t sum-\u003eOutPktsProtected,\ndrivers/net/macsec.c-2940-\t\t\t MACSEC_TXSC_STATS_ATTR_PAD) ||\ndrivers/net/macsec.c:2941:\t nla_put_u64_64bit(skb, MACSEC_TXSC_STATS_ATTR_OUT_PKTS_ENCRYPTED,\ndrivers/net/macsec.c-2942-\t\t\t sum-\u003eOutPktsEncrypted,\ndrivers/net/macsec.c-2943-\t\t\t MACSEC_TXSC_STATS_ATTR_PAD) ||\ndrivers/net/macsec.c:2944:\t nla_put_u64_64bit(skb, MACSEC_TXSC_STATS_ATTR_OUT_OCTETS_PROTECTED,\ndrivers/net/macsec.c-2945-\t\t\t sum-\u003eOutOctetsProtected,\ndrivers/net/macsec.c-2946-\t\t\t MACSEC_TXSC_STATS_ATTR_PAD) ||\ndrivers/net/macsec.c:2947:\t nla_put_u64_64bit(skb, MACSEC_TXSC_STATS_ATTR_OUT_OCTETS_ENCRYPTED,\ndrivers/net/macsec.c-2948-\t\t\t sum-\u003eOutOctetsEncrypted,\n--\ndrivers/net/macsec.c=2996=static int copy_secy_stats(struct sk_buff *skb, struct macsec_dev_stats *sum)\ndrivers/net/macsec.c-2997-{\ndrivers/net/macsec.c:2998:\tif (nla_put_u64_64bit(skb, MACSEC_SECY_STATS_ATTR_OUT_PKTS_UNTAGGED,\ndrivers/net/macsec.c-2999-\t\t\t sum-\u003eOutPktsUntagged,\ndrivers/net/macsec.c-3000-\t\t\t MACSEC_SECY_STATS_ATTR_PAD) ||\ndrivers/net/macsec.c:3001:\t nla_put_u64_64bit(skb, MACSEC_SECY_STATS_ATTR_IN_PKTS_UNTAGGED,\ndrivers/net/macsec.c-3002-\t\t\t sum-\u003eInPktsUntagged,\ndrivers/net/macsec.c-3003-\t\t\t MACSEC_SECY_STATS_ATTR_PAD) ||\ndrivers/net/macsec.c:3004:\t nla_put_u64_64bit(skb, MACSEC_SECY_STATS_ATTR_OUT_PKTS_TOO_LONG,\ndrivers/net/macsec.c-3005-\t\t\t sum-\u003eOutPktsTooLong,\ndrivers/net/macsec.c-3006-\t\t\t MACSEC_SECY_STATS_ATTR_PAD) ||\ndrivers/net/macsec.c:3007:\t nla_put_u64_64bit(skb, MACSEC_SECY_STATS_ATTR_IN_PKTS_NO_TAG,\ndrivers/net/macsec.c-3008-\t\t\t sum-\u003eInPktsNoTag,\ndrivers/net/macsec.c-3009-\t\t\t MACSEC_SECY_STATS_ATTR_PAD) ||\ndrivers/net/macsec.c:3010:\t nla_put_u64_64bit(skb, MACSEC_SECY_STATS_ATTR_IN_PKTS_BAD_TAG,\ndrivers/net/macsec.c-3011-\t\t\t sum-\u003eInPktsBadTag,\ndrivers/net/macsec.c-3012-\t\t\t MACSEC_SECY_STATS_ATTR_PAD) ||\ndrivers/net/macsec.c:3013:\t nla_put_u64_64bit(skb, MACSEC_SECY_STATS_ATTR_IN_PKTS_UNKNOWN_SCI,\ndrivers/net/macsec.c-3014-\t\t\t sum-\u003eInPktsUnknownSCI,\ndrivers/net/macsec.c-3015-\t\t\t MACSEC_SECY_STATS_ATTR_PAD) ||\ndrivers/net/macsec.c:3016:\t nla_put_u64_64bit(skb, MACSEC_SECY_STATS_ATTR_IN_PKTS_NO_SCI,\ndrivers/net/macsec.c-3017-\t\t\t sum-\u003eInPktsNoSCI,\ndrivers/net/macsec.c-3018-\t\t\t MACSEC_SECY_STATS_ATTR_PAD) ||\ndrivers/net/macsec.c:3019:\t nla_put_u64_64bit(skb, MACSEC_SECY_STATS_ATTR_IN_PKTS_OVERRUN,\ndrivers/net/macsec.c-3020-\t\t\t sum-\u003eInPktsOverrun,\n--\ndrivers/net/macsec.c=3027=static int nla_put_secy(struct macsec_secy *secy, struct sk_buff *skb)\n--\ndrivers/net/macsec.c-3049-\t\t\tMACSEC_SECY_ATTR_PAD) ||\ndrivers/net/macsec.c:3050:\t nla_put_u64_64bit(skb, MACSEC_SECY_ATTR_CIPHER_SUITE,\ndrivers/net/macsec.c-3051-\t\t\t csid, MACSEC_SECY_ATTR_PAD) ||\n--\ndrivers/net/macsec.c=4387=static int macsec_fill_info(struct sk_buff *skb,\n--\ndrivers/net/macsec.c-4412-\t nla_put_u8(skb, IFLA_MACSEC_ICV_LEN, secy-\u003eicv_len) ||\ndrivers/net/macsec.c:4413:\t nla_put_u64_64bit(skb, IFLA_MACSEC_CIPHER_SUITE,\ndrivers/net/macsec.c-4414-\t\t\t csid, IFLA_MACSEC_PAD) ||\n--\ndrivers/net/vxlan/vxlan_vnifilter.c=209=static int __vnifilter_entry_fill_stats(struct sk_buff *skb,\n--\ndrivers/net/vxlan/vxlan_vnifilter.c-219-\tvxlan_vnifilter_stats_get(vbegin, \u0026vstats);\ndrivers/net/vxlan/vxlan_vnifilter.c:220:\tif (nla_put_u64_64bit(skb, VNIFILTER_ENTRY_STATS_RX_BYTES,\ndrivers/net/vxlan/vxlan_vnifilter.c-221-\t\t\t vstats.rx_bytes, VNIFILTER_ENTRY_STATS_PAD) ||\ndrivers/net/vxlan/vxlan_vnifilter.c:222:\t nla_put_u64_64bit(skb, VNIFILTER_ENTRY_STATS_RX_PKTS,\ndrivers/net/vxlan/vxlan_vnifilter.c-223-\t\t\t vstats.rx_packets, VNIFILTER_ENTRY_STATS_PAD) ||\ndrivers/net/vxlan/vxlan_vnifilter.c:224:\t nla_put_u64_64bit(skb, VNIFILTER_ENTRY_STATS_RX_DROPS,\ndrivers/net/vxlan/vxlan_vnifilter.c-225-\t\t\t vstats.rx_drops, VNIFILTER_ENTRY_STATS_PAD) ||\ndrivers/net/vxlan/vxlan_vnifilter.c:226:\t nla_put_u64_64bit(skb, VNIFILTER_ENTRY_STATS_RX_ERRORS,\ndrivers/net/vxlan/vxlan_vnifilter.c-227-\t\t\t vstats.rx_errors, VNIFILTER_ENTRY_STATS_PAD) ||\ndrivers/net/vxlan/vxlan_vnifilter.c:228:\t nla_put_u64_64bit(skb, VNIFILTER_ENTRY_STATS_TX_BYTES,\ndrivers/net/vxlan/vxlan_vnifilter.c-229-\t\t\t vstats.tx_bytes, VNIFILTER_ENTRY_STATS_PAD) ||\ndrivers/net/vxlan/vxlan_vnifilter.c:230:\t nla_put_u64_64bit(skb, VNIFILTER_ENTRY_STATS_TX_PKTS,\ndrivers/net/vxlan/vxlan_vnifilter.c-231-\t\t\t vstats.tx_packets, VNIFILTER_ENTRY_STATS_PAD) ||\ndrivers/net/vxlan/vxlan_vnifilter.c:232:\t nla_put_u64_64bit(skb, VNIFILTER_ENTRY_STATS_TX_DROPS,\ndrivers/net/vxlan/vxlan_vnifilter.c-233-\t\t\t vstats.tx_drops, VNIFILTER_ENTRY_STATS_PAD) ||\ndrivers/net/vxlan/vxlan_vnifilter.c:234:\t nla_put_u64_64bit(skb, VNIFILTER_ENTRY_STATS_TX_ERRORS,\ndrivers/net/vxlan/vxlan_vnifilter.c-235-\t\t\t vstats.tx_errors, VNIFILTER_ENTRY_STATS_PAD))\n--\ndrivers/net/wireguard/netlink.c=77=get_peer(struct wg_peer *peer, struct sk_buff *skb, struct dump_ctx *ctx)\n--\ndrivers/net/wireguard/netlink.c-111-\t\t\t\tpeer-\u003epersistent_keepalive_interval) ||\ndrivers/net/wireguard/netlink.c:112:\t\t nla_put_u64_64bit(skb, WGPEER_A_TX_BYTES, peer-\u003etx_bytes,\ndrivers/net/wireguard/netlink.c-113-\t\t\t\t WGPEER_A_UNSPEC) ||\ndrivers/net/wireguard/netlink.c:114:\t\t nla_put_u64_64bit(skb, WGPEER_A_RX_BYTES, peer-\u003erx_bytes,\ndrivers/net/wireguard/netlink.c-115-\t\t\t\t WGPEER_A_UNSPEC) ||\n--\ndrivers/net/wireless/ath/wil6210/cfg80211.c=2867=static int wil_rf_sector_get_cfg(struct wiphy *wiphy,\n--\ndrivers/net/wireless/ath/wil6210/cfg80211.c-2946-\ndrivers/net/wireless/ath/wil6210/cfg80211.c:2947:\tif (nla_put_u64_64bit(msg, QCA_ATTR_TSF,\ndrivers/net/wireless/ath/wil6210/cfg80211.c-2948-\t\t\t le64_to_cpu(reply.evt.tsf),\n--\ndrivers/net/wireless/ath/wil6210/cfg80211.c=3100=static int wil_rf_sector_get_selected(struct wiphy *wiphy,\n--\ndrivers/net/wireless/ath/wil6210/cfg80211.c-3173-\ndrivers/net/wireless/ath/wil6210/cfg80211.c:3174:\tif (nla_put_u64_64bit(msg, QCA_ATTR_TSF,\ndrivers/net/wireless/ath/wil6210/cfg80211.c-3175-\t\t\t le64_to_cpu(reply.evt.tsf),\n--\ndrivers/net/wireless/mediatek/mt76/testmode.c=537=mt76_testmode_dump_stats(struct mt76_phy *phy, struct sk_buff *msg)\n--\ndrivers/net/wireless/mediatek/mt76/testmode.c-560-\t nla_put_u32(msg, MT76_TM_STATS_ATTR_TX_DONE, td-\u003etx_done) ||\ndrivers/net/wireless/mediatek/mt76/testmode.c:561:\t nla_put_u64_64bit(msg, MT76_TM_STATS_ATTR_RX_PACKETS, rx_packets,\ndrivers/net/wireless/mediatek/mt76/testmode.c-562-\t\t\t MT76_TM_STATS_ATTR_PAD) ||\ndrivers/net/wireless/mediatek/mt76/testmode.c:563:\t nla_put_u64_64bit(msg, MT76_TM_STATS_ATTR_RX_FCS_ERROR, rx_fcs_error,\ndrivers/net/wireless/mediatek/mt76/testmode.c-564-\t\t\t MT76_TM_STATS_ATTR_PAD))\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c=1646=static void mac80211_hwsim_tx_frame_nl(struct ieee80211_hw *hw,\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-1734-\tinfo-\u003erate_driver_data[0] = (void *)cookie;\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:1735:\tif (nla_put_u64_64bit(skb, HWSIM_ATTR_COOKIE, cookie, HWSIM_ATTR_PAD))\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-1736-\t\tgoto nla_put_failure;\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c=3597=static int mac80211_hwsim_send_pmsr_ftm_request_peer(struct sk_buff *msg,\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-3678-\tif (request-\u003eingress_distance \u0026\u0026\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:3679:\t nla_put_u64_64bit(msg, NL80211_PMSR_FTM_REQ_ATTR_INGRESS,\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-3680-\t\t\t request-\u003eingress_distance,\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-3684-\tif (request-\u003eegress_distance \u0026\u0026\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:3685:\t nla_put_u64_64bit(msg, NL80211_PMSR_FTM_REQ_ATTR_EGRESS,\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-3686-\t\t\t request-\u003eegress_distance,\n--\ndrivers/target/target_core_user.c=2855=static int tcmu_send_dev_size_event(struct tcmu_dev *udev, u64 size)\n--\ndrivers/target/target_core_user.c-2864-\t\treturn ret;\ndrivers/target/target_core_user.c:2865:\tret = nla_put_u64_64bit(skb, TCMU_ATTR_DEV_SIZE,\ndrivers/target/target_core_user.c-2866-\t\t\t\tsize, TCMU_ATTR_PAD);\n--\ndrivers/vdpa/mlx5/net/mlx5_vnet.c=3553=static int mlx5_vdpa_get_vendor_vq_stats(struct vdpa_device *vdev, u16 idx,\n--\ndrivers/vdpa/mlx5/net/mlx5_vnet.c-3590-\ndrivers/vdpa/mlx5/net/mlx5_vnet.c:3591:\tif (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE, received_desc,\ndrivers/vdpa/mlx5/net/mlx5_vnet.c-3592-\t\t\t VDPA_ATTR_PAD))\n--\ndrivers/vdpa/mlx5/net/mlx5_vnet.c-3597-\ndrivers/vdpa/mlx5/net/mlx5_vnet.c:3598:\tif (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE, completed_desc,\ndrivers/vdpa/mlx5/net/mlx5_vnet.c-3599-\t\t\t VDPA_ATTR_PAD))\n--\ndrivers/vdpa/vdpa.c=450=static int vdpa_mgmtdev_fill(const struct vdpa_mgmt_dev *mdev, struct sk_buff *msg,\n--\ndrivers/vdpa/vdpa.c-462-\ndrivers/vdpa/vdpa.c:463:\tif (nla_put_u64_64bit(msg, VDPA_ATTR_MGMTDEV_SUPPORTED_CLASSES,\ndrivers/vdpa/vdpa.c-464-\t\t\t vdpa_mgmtdev_get_classes(mdev, NULL),\n--\ndrivers/vdpa/vdpa.c-473-\t}\ndrivers/vdpa/vdpa.c:474:\tif (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_SUPPORTED_FEATURES,\ndrivers/vdpa/vdpa.c-475-\t\t\t mdev-\u003esupported_features, VDPA_ATTR_PAD)) {\n--\ndrivers/vdpa/vdpa.c=886=static int vdpa_dev_net_config_fill(struct vdpa_device *vdev, struct sk_buff *msg)\n--\ndrivers/vdpa/vdpa.c-894-\ndrivers/vdpa/vdpa.c:895:\tif (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_FEATURES, features_device,\ndrivers/vdpa/vdpa.c-896-\t\t\t VDPA_ATTR_PAD))\n--\ndrivers/vdpa/vdpa.c=912=vdpa_dev_blk_capacity_config_fill(struct sk_buff *msg,\n--\ndrivers/vdpa/vdpa.c-918-\ndrivers/vdpa/vdpa.c:919:\treturn nla_put_u64_64bit(msg, VDPA_ATTR_DEV_BLK_CFG_CAPACITY,\ndrivers/vdpa/vdpa.c-920-\t\t\t\t val_u64, VDPA_ATTR_PAD);\n--\ndrivers/vdpa/vdpa.c=1073=static int vdpa_dev_blk_config_fill(struct vdpa_device *vdev,\n--\ndrivers/vdpa/vdpa.c-1082-\ndrivers/vdpa/vdpa.c:1083:\tif (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_FEATURES, features_device,\ndrivers/vdpa/vdpa.c-1084-\t\t\t VDPA_ATTR_PAD))\n--\ndrivers/vdpa/vdpa.c=1121=vdpa_dev_config_fill(struct vdpa_device *vdev, struct sk_buff *msg, u32 portid, u32 seq,\n--\ndrivers/vdpa/vdpa.c-1152-\t\tfeatures_driver = vdev-\u003econfig-\u003eget_driver_features(vdev);\ndrivers/vdpa/vdpa.c:1153:\t\tif (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_NEGOTIATED_FEATURES, features_driver,\ndrivers/vdpa/vdpa.c-1154-\t\t\t\t VDPA_ATTR_PAD)) {\n--\ndrivers/vdpa/vdpa.c=1185=static int vdpa_fill_stats_rec(struct vdpa_device *vdev, struct sk_buff *msg,\n--\ndrivers/vdpa/vdpa.c-1200-\tfeatures = vdev-\u003econfig-\u003eget_driver_features(vdev);\ndrivers/vdpa/vdpa.c:1201:\tif (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_NEGOTIATED_FEATURES,\ndrivers/vdpa/vdpa.c-1202-\t\t\t features, VDPA_ATTR_PAD))\n--\ndrivers/vdpa/vdpa_sim/vdpa_sim_net.c=290=static int vdpasim_net_get_stats(struct vdpasim *vdpasim, u16 idx,\n--\ndrivers/vdpa/vdpa_sim/vdpa_sim_net.c-314-\t\t\tbreak;\ndrivers/vdpa/vdpa_sim/vdpa_sim_net.c:315:\t\tif (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,\ndrivers/vdpa/vdpa_sim/vdpa_sim_net.c-316-\t\t\t\t rx_pkts, VDPA_ATTR_PAD))\n--\ndrivers/vdpa/vdpa_sim/vdpa_sim_net.c-320-\t\t\tbreak;\ndrivers/vdpa/vdpa_sim/vdpa_sim_net.c:321:\t\tif (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,\ndrivers/vdpa/vdpa_sim/vdpa_sim_net.c-322-\t\t\t\t rx_bytes, VDPA_ATTR_PAD))\n--\ndrivers/vdpa/vdpa_sim/vdpa_sim_net.c-326-\t\t\tbreak;\ndrivers/vdpa/vdpa_sim/vdpa_sim_net.c:327:\t\tif (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,\ndrivers/vdpa/vdpa_sim/vdpa_sim_net.c-328-\t\t\t\t rx_errors, VDPA_ATTR_PAD))\n--\ndrivers/vdpa/vdpa_sim/vdpa_sim_net.c-332-\t\t\tbreak;\ndrivers/vdpa/vdpa_sim/vdpa_sim_net.c:333:\t\tif (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,\ndrivers/vdpa/vdpa_sim/vdpa_sim_net.c-334-\t\t\t\t rx_overruns, VDPA_ATTR_PAD))\n--\ndrivers/vdpa/vdpa_sim/vdpa_sim_net.c-338-\t\t\tbreak;\ndrivers/vdpa/vdpa_sim/vdpa_sim_net.c:339:\t\tif (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,\ndrivers/vdpa/vdpa_sim/vdpa_sim_net.c-340-\t\t\t\t rx_drops, VDPA_ATTR_PAD))\n--\ndrivers/vdpa/vdpa_sim/vdpa_sim_net.c-355-\t\t\tbreak;\ndrivers/vdpa/vdpa_sim/vdpa_sim_net.c:356:\t\tif (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,\ndrivers/vdpa/vdpa_sim/vdpa_sim_net.c-357-\t\t\t\t tx_pkts, VDPA_ATTR_PAD))\n--\ndrivers/vdpa/vdpa_sim/vdpa_sim_net.c-361-\t\t\tbreak;\ndrivers/vdpa/vdpa_sim/vdpa_sim_net.c:362:\t\tif (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,\ndrivers/vdpa/vdpa_sim/vdpa_sim_net.c-363-\t\t\t\t tx_bytes, VDPA_ATTR_PAD))\n--\ndrivers/vdpa/vdpa_sim/vdpa_sim_net.c-367-\t\t\tbreak;\ndrivers/vdpa/vdpa_sim/vdpa_sim_net.c:368:\t\tif (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,\ndrivers/vdpa/vdpa_sim/vdpa_sim_net.c-369-\t\t\t\t tx_errors, VDPA_ATTR_PAD))\n\n"
} |
| 25/3 | 2026/09/18 14:58 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 42985 output: 43 thoughts: 57 Thoughts: Here's my thinking: **Examining 64-bit Attribute Handling in `seg6_local.c`** Okay, so I need to understand how the 64-bit attributes are being managed in this context. My initial instinct is to use `nla_put_u64_64bit` - that's the standard, right? It's designed specifically for handling 64-bit values with the appropriate alignment padding. Now, the real question is, how is this actually implemented? Specifically, I'm thinking I should look at `seg6_local.c` and see how it handles counters. That seems like a good place to start, as counters are likely represented by 64-bit values and should provide a good example of the implementation. Let's dig in there and see what we can find. |
| 26/3 | 2026/09/18 14:58 | tool | grepper |
0mArgs:{
"Expression": "nla_put_u64_64bit",
"PathPrefix": "net/ipv6/seg6_local.c"
}Results: {
"Output": "net/ipv6/seg6_local.c=2024=static int seg6_local_fill_nla_counters(struct sk_buff *skb,\n--\nnet/ipv6/seg6_local.c-2026-{\nnet/ipv6/seg6_local.c:2027:\tif (nla_put_u64_64bit(skb, SEG6_LOCAL_CNT_PACKETS, counters-\u003epackets,\nnet/ipv6/seg6_local.c-2028-\t\t\t SEG6_LOCAL_CNT_PAD))\n--\nnet/ipv6/seg6_local.c-2030-\nnet/ipv6/seg6_local.c:2031:\tif (nla_put_u64_64bit(skb, SEG6_LOCAL_CNT_BYTES, counters-\u003ebytes,\nnet/ipv6/seg6_local.c-2032-\t\t\t SEG6_LOCAL_CNT_PAD))\n--\nnet/ipv6/seg6_local.c-2034-\nnet/ipv6/seg6_local.c:2035:\tif (nla_put_u64_64bit(skb, SEG6_LOCAL_CNT_ERRORS, counters-\u003eerrors,\nnet/ipv6/seg6_local.c-2036-\t\t\t SEG6_LOCAL_CNT_PAD))\n"
} |
| 27/3 | 2026/09/18 14:58 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 43617 output: 24 thoughts: 1033 Thoughts: **Analyzing `seg6_mobile_advance_da` and IPv6 Header Manipulation** Okay, so I'm looking at `seg6_mobile.c`, and it appears to follow a very similar pattern to `seg6_local.c`. That's good for consistency, but let's dive deeper and make sure. Right now, I'm specifically examining `seg6_mobile_advance_da`. This function is responsible for rewriting the destination address (DA) of an IPv6 packet, a critical part of the Mobile IPv6 functionality. The code seems to handle the Layer 4 checksum updates based on whether an SRH is present or not. Specifically, I'm focusing on how it handles packet parsing and writing operations. The function first pulls the IPv6 header and determines the length of what needs to be rewritten. The logic involving `srh_present` looks clean. The `l4_off` and `l4_hlen` variables are calculated correctly, seemingly considering fragmentation. The key part is here, where the code checks for the presence of the SRH. If no SRH is present, it calls `ipv6_skip_exthdr`. I want to understand what's happening here. The code looks correct, but I want to be *certain* that the implementation is robust, and doesn't have any hidden issues. This function determines where the Layer 4 header begins, considering potential IPv6 extension headers. It also takes the `frag_off` parameter. My immediate concern is, does `ipv6_skip_exthdr` initialize `frag_off`? I need to know how that is populated within this function. I need to go and examine the `ipv6_skip_exthdr` implementation to make sure there are no assumptions made here that might not be correct in all cases. I'll need to confirm that `frag_off` is handled correctly. If that's initialized correctly by the function then the code here should be fine. I'll need to check the function's contract and make sure that it deals with extension headers correctly. |
| 28/3 | 2026/09/18 14:59 | tool | grepper |
0mArgs:{
"Expression": "int ipv6_skip_exthdr\\("
}Results: {
"Output": "include/net/ipv6.h=1101=u8 ipv6_push_frag_opts(struct sk_buff *skb, struct ipv6_txoptions *opt,\n--\ninclude/net/ipv6.h-1103-\ninclude/net/ipv6.h:1104:int ipv6_skip_exthdr(const struct sk_buff *, int start, u8 *nexthdrp,\ninclude/net/ipv6.h-1105-\t\t __be16 *frag_offp);\n--\nnet/ipv6/exthdrs_core.c=25=EXPORT_SYMBOL(ipv6_ext_hdr);\n--\nnet/ipv6/exthdrs_core.c-71-\nnet/ipv6/exthdrs_core.c:72:int ipv6_skip_exthdr(const struct sk_buff *skb, int start, u8 *nexthdrp,\nnet/ipv6/exthdrs_core.c-73-\t\t __be16 *frag_offp)\n"
} |
| 29/3 | 2026/09/18 14:59 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 45385 output: 40 thoughts: 25 Thoughts: Here's a summary of my mental process: **Diving into IPv6 Extension Headers in `exthdrs_core.c`** Okay, time to get my hands dirty. I need to refresh my understanding of how IPv6 extension headers are handled, specifically the core logic. Let's start by zeroing in on `net/ipv6/exthdrs_core.c` and specifically around line 72. That's usually a good starting point for getting a feel for the control flow within that file, or at least understanding where things start. I'm hoping I can refresh my understanding of where the packet data is accessed, and which core functions are doing the main work, and perhaps I will be reminded of a particularly clever technique, or a subtle potential vulnerability. I'll read with the intention of re-familiarizing myself, and then I can dig deeper into a specific area if necessary. |
| 30/3 | 2026/09/18 14:59 | tool | read-file |
0mArgs:{
"File": "net/ipv6/exthdrs_core.c",
"FirstLine": 72,
"LineCount": 40
}Results: {
"Contents": " 72:\tint ipv6_skip_exthdr(const struct sk_buff *skb, int start, u8 *nexthdrp,\n 73:\t\t\t __be16 *frag_offp)\n 74:\t{\n 75:\t\tu8 nexthdr = *nexthdrp;\n 76:\t\tint exthdr_cnt = 0;\n 77:\t\n 78:\t\t*frag_offp = 0;\n 79:\t\n 80:\t\twhile (ipv6_ext_hdr(nexthdr)) {\n 81:\t\t\tstruct ipv6_opt_hdr _hdr, *hp;\n 82:\t\t\tint hdrlen;\n 83:\t\n 84:\t\t\tif (nexthdr == NEXTHDR_NONE)\n 85:\t\t\t\treturn -1;\n 86:\t\t\tif (unlikely(exthdr_cnt++ \u003e= IP6_MAX_EXT_HDRS_CNT))\n 87:\t\t\t\treturn -1;\n 88:\t\t\thp = skb_header_pointer(skb, start, sizeof(_hdr), \u0026_hdr);\n 89:\t\t\tif (!hp)\n 90:\t\t\t\treturn -1;\n 91:\t\t\tif (nexthdr == NEXTHDR_FRAGMENT) {\n 92:\t\t\t\t__be16 _frag_off, *fp;\n 93:\t\t\t\tfp = skb_header_pointer(skb,\n 94:\t\t\t\t\t\t\tstart+offsetof(struct frag_hdr,\n 95:\t\t\t\t\t\t\t\t frag_off),\n 96:\t\t\t\t\t\t\tsizeof(_frag_off),\n 97:\t\t\t\t\t\t\t\u0026_frag_off);\n 98:\t\t\t\tif (!fp)\n 99:\t\t\t\t\treturn -1;\n 100:\t\n 101:\t\t\t\t*frag_offp = *fp;\n 102:\t\t\t\tif (ntohs(*frag_offp) \u0026 ~0x7)\n 103:\t\t\t\t\tbreak;\n 104:\t\t\t\thdrlen = 8;\n 105:\t\t\t} else if (nexthdr == NEXTHDR_AUTH)\n 106:\t\t\t\thdrlen = ipv6_authlen(hp);\n 107:\t\t\telse\n 108:\t\t\t\thdrlen = ipv6_optlen(hp);\n 109:\t\n 110:\t\t\tnexthdr = hp-\u003enexthdr;\n 111:\t\t\tstart += hdrlen;\n"
} |
| 31/3 | 2026/09/18 14:59 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 46241 output: 22 thoughts: 551 Thoughts: **My Thoughts on `ipv6_skip_exthdr` and Related Functions** Okay, so I'm diving into this `ipv6_skip_exthdr` function, and it seems pretty straightforward at first glance. The very first thing it does is `*frag_offp = 0;`, which is good; it ensures that `frag_off` is always initialized, no surprises there. That's a solid start. But now, what happens if `srh_present` is true? That's where it gets interesting, and I need to make sure I'm following every step. If `srh_present` is indeed true, then: 1. `write_len` is set to `sizeof(*ip6h)`. 2. `skb_ensure_writable(skb, write_len)` is called, which could return an error, likely `-ENOMEM`, if there isn't enough memory. 3. `ip6h` is updated to point to the IPv6 header of the `skb`. 4. `old_da` now holds the original destination address, which is good for comparison later on. 5. `csum` is set to NULL. 6. There's a conditional on `csum`. If `csum` is not NULL, then perform an action. Else, if checksum is complete, update checksum difference. 7. The destination address in `ip6h` is overwritten with the value pointed to by `nh`. 8. And finally, `skb_clear_hash(skb)` is called and then return 0. Now, I'm thinking about related functions, and `seg6_mobile_get_and_validate_srh` pops into my head. Let me refresh my memory on that function. I have to make sure that the functions are called correctly. It looks like `seg6_mobile_get_and_validate_srh` calls `seg6_get_srh` to get the SRH if it's there. It validates the HMAC if defined and checks if the header is absent. Now I want to verify if the function can modify or return anything. Let's check `ipv6_find_hdr`. I need to ensure that it doesn't have any unintended side effects, especially regarding the `flags` parameter or the return value and that it correctly handles the presence or absence of the header. I need to go look up `ipv6_find_hdr` in the code to get the details. |
| 32/3 | 2026/09/18 14:59 | tool | grepper |
0mArgs:{
"Expression": "int ipv6_find_hdr\\("
}Results: {
"Output": "include/net/ipv6.h=1109=enum {\n--\ninclude/net/ipv6.h-1115-/* find specified header and get offset to it */\ninclude/net/ipv6.h:1116:int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset, int target,\ninclude/net/ipv6.h-1117-\t\t unsigned short *fragoff, int *fragflg);\n--\nnet/ipv6/exthdrs_core.c=163=EXPORT_SYMBOL_GPL(ipv6_find_tlv);\n--\nnet/ipv6/exthdrs_core.c-190- */\nnet/ipv6/exthdrs_core.c:191:int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset,\nnet/ipv6/exthdrs_core.c-192-\t\t int target, unsigned short *fragoff, int *flags)\n"
} |
| 33/3 | 2026/09/18 14:59 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 47625 output: 24 thoughts: 1506 Thoughts: **Analyzing the Code for Segment Routing with IPv6 Mobile Support** Okay, let's break this down. First, I need to understand how the L4 checksum is handled, so I'm focusing on `seg6_mobile_l4_csum`. The function itself seems straightforward: it identifies the L4 protocol (TCP, UDP, or ICMPv6) and returns a pointer to the checksum field within the L4 header. The key here is the conditional for UDP: if the UDP checksum is zero and the SKB isn't undergoing partial checksum offloading, it returns `NULL`. This implies that a zero UDP checksum means "no checksum" in this context. And I see `skb_ensure_writable` is called before this function, guaranteeing write access to the header. Next, I'm digging into `input_action_end_map`. The core of this function is the replacement of the destination address (DA) with a mapped Segment ID (SID). The SRH itself remains untouched. I see a critical state check involving `seg6_mobile_get_and_validate_srh`. If the SRH is malformed, we drop the packet. If the SRH is present, or absent, we proceed to `seg6_mobile_advance_da`. I'm focusing on the third argument to `seg6_mobile_advance_da`, which is `srh && srh->segments_left`. I need to be careful here, because the argument is *called* `srh_present` but actually evaluates to the more nuanced `srh && srh->segments_left`. I think I see it now. The code checks if any SRH segments remain after processing. The comment in the code and the self-tests confirm it. If `srh && srh->segments_left` is true (meaning there *are* SRH segments left), then the downstream receiver still has SRH processing to do, and the L4 checksum *should not* be updated. If `srh && srh->segments_left` is false (either no SRH or SL=0), then the receiver is the final destination, and the L4 checksum *must* be updated. That logic makes sense. Finally, I'm checking `seg6_mobile_input`, the main entry point. It checks for IPv6, retrieves the tunnel parameters, calls the specific input handler (based on the `slwt->desc->input` function pointer), and updates counters. Then, it calls `seg6_mobile_update_counters` if the counters are enabled, which I'll keep an eye on. I need to know about the counters, but first, I need to understand how the per-CPU counters are allocated. I see `seg6_mobile_alloc_pcpu_counters` and I need to verify how these per-CPU counters are being handled, especially the zero-initialization. I see `__netdev_alloc_pcpu_stats` allocates per-CPU stats, which are zero-initialized with `__GFP_ZERO`. |
| 34/3 | 2026/09/18 15:00 | tool | grepper |
0mArgs:{
"Expression": "__netdev_alloc_pcpu_stats"
}Results: {
"Output": "include/linux/netdevice.h=3150=static inline void dev_dstats_tx_dropped(struct net_device *dev)\n--\ninclude/linux/netdevice.h-3158-\ninclude/linux/netdevice.h:3159:#define __netdev_alloc_pcpu_stats(type, gfp)\t\t\t\t\\\ninclude/linux/netdevice.h-3160-({\t\t\t\t\t\t\t\t\t\\\n--\ninclude/linux/netdevice.h-3173-#define netdev_alloc_pcpu_stats(type)\t\t\t\t\t\\\ninclude/linux/netdevice.h:3174:\t__netdev_alloc_pcpu_stats(type, GFP_KERNEL)\ninclude/linux/netdevice.h-3175-\n--\nnet/ipv6/seg6_local.c=175=struct seg6_local_counters {\n--\nnet/ipv6/seg6_local.c-181-#define seg6_local_alloc_pcpu_counters(__gfp)\t\t\t\t\\\nnet/ipv6/seg6_local.c:182:\t__netdev_alloc_pcpu_stats(struct pcpu_seg6_local_counters,\t\\\nnet/ipv6/seg6_local.c-183-\t\t\t\t ((__gfp) | __GFP_ZERO))\n--\nnet/ipv6/seg6_mobile.c=62=struct seg6_mobile_counters {\n--\nnet/ipv6/seg6_mobile.c-68-#define seg6_mobile_alloc_pcpu_counters(__gfp)\t\t\t\t\\\nnet/ipv6/seg6_mobile.c:69:\t__netdev_alloc_pcpu_stats(struct pcpu_seg6_mobile_counters,\t\\\nnet/ipv6/seg6_mobile.c-70-\t\t\t\t ((__gfp) | __GFP_ZERO))\n"
} |
| 35/3 | 2026/09/18 15:00 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 50266 output: 37 thoughts: 522 Thoughts: **Analyzing the Counter Parsing Logic** Okay, let's break down this `parse_nla_counters` function. My initial thought is to understand how the SRv6 Mobile counter handling is implemented. I see a nested attribute parsing using `nla_parse_nested` with a specific policy. The code checks for the presence of packet, byte, and error counters as a minimum requirement, and I see they are zero-initialized. Fine, so far so good, it allocates per-cpu counters. However, a question arises: How does this interoperate with iproute2's configuration? Does `iproute2` actually *set* these counters, or does it simply enable counter collection by sending those attributes? And how are the counter attributes populated from userspace? Wait, what are the exact NLA attributes that `iproute2` sends? My memory is jogged – there's that `encap seg6mobile action End.MAP ... count` syntax from the selftest. The question is: when I issue that command, are the packet, bytes, and error counters *actually* sent as nested attributes, or is the presence of the `count` keyword sufficient to enable counters, with the kernel initializing them internally? I need to check how the parser works in `seg6_local.c`, which is the likely place to find that answer. |
| 36/3 | 2026/09/18 15:00 | tool | grepper |
0mArgs:{
"Expression": "parse_nla_counters",
"PathPrefix": "net/ipv6/seg6_local.c"
}Results: {
"Output": "net/ipv6/seg6_local.c=1987=nla_policy seg6_local_counters_policy[SEG6_LOCAL_CNT_MAX + 1] = {\n--\nnet/ipv6/seg6_local.c-1992-\nnet/ipv6/seg6_local.c:1993:static int parse_nla_counters(struct nlattr **attrs,\nnet/ipv6/seg6_local.c-1994-\t\t\t struct seg6_local_lwt *slwt,\n--\nnet/ipv6/seg6_local.c=2294=static struct seg6_action_param seg6_action_params[SEG6_LOCAL_MAX + 1] = {\n--\nnet/ipv6/seg6_local.c-2328-\nnet/ipv6/seg6_local.c:2329:\t[SEG6_LOCAL_COUNTERS]\t= { .parse = parse_nla_counters,\nnet/ipv6/seg6_local.c-2330-\t\t\t\t .put = put_nla_counters,\n"
} |
| 37/3 | 2026/09/18 15:00 | llm | kmsan-evaluator |
1mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 51391 output: 40 thoughts: 26 Thoughts: Here's my summary, as though I'm reading and processing the provided code context: **Analyzing `parse_nla_counters` in `net/ipv6/seg6_local.c`** Alright, let's dive into this `parse_nla_counters` function within the `seg6_local.c` file, specifically within the IPv6 network stack. My initial expectation is that this function will be responsible for parsing Network Layer Attributes (NLAs) related to counters. Since we're in the SEG6 context, I'm anticipating these counters are probably associated with segment routing, likely tracking things like packet counts, byte counts, or perhaps error statistics for local SIDs. I'll be looking for how the NLA data is structured, the types of counters it supports, and how it handles potential errors during the parsing process, like malformed attributes or unexpected lengths. I'll need to figure out the exact data structures it references and how those counters are used in the broader IPv6 network flow. The goal is to understand how these counters are managed and how they fit into the bigger picture of segment routing with IPv6. |
| 38/3 | 2026/09/18 15:01 | tool | read-file |
0mArgs:{
"File": "net/ipv6/seg6_local.c",
"FirstLine": 1993,
"LineCount": 35
}Results: {
"Contents": "1993:\tstatic int parse_nla_counters(struct nlattr **attrs,\n1994:\t\t\t\t struct seg6_local_lwt *slwt,\n1995:\t\t\t\t struct netlink_ext_ack *extack)\n1996:\t{\n1997:\t\tstruct pcpu_seg6_local_counters __percpu *pcounters;\n1998:\t\tstruct nlattr *tb[SEG6_LOCAL_CNT_MAX + 1];\n1999:\t\tint ret;\n2000:\t\n2001:\t\tret = nla_parse_nested_deprecated(tb, SEG6_LOCAL_CNT_MAX,\n2002:\t\t\t\t\t\t attrs[SEG6_LOCAL_COUNTERS],\n2003:\t\t\t\t\t\t seg6_local_counters_policy, NULL);\n2004:\t\tif (ret \u003c 0)\n2005:\t\t\treturn ret;\n2006:\t\n2007:\t\t/* basic support for SRv6 Behavior counters requires at least:\n2008:\t\t * packets, bytes and errors.\n2009:\t\t */\n2010:\t\tif (!tb[SEG6_LOCAL_CNT_PACKETS] || !tb[SEG6_LOCAL_CNT_BYTES] ||\n2011:\t\t !tb[SEG6_LOCAL_CNT_ERRORS])\n2012:\t\t\treturn -EINVAL;\n2013:\t\n2014:\t\t/* counters are always zero initialized */\n2015:\t\tpcounters = seg6_local_alloc_pcpu_counters(GFP_KERNEL);\n2016:\t\tif (!pcounters)\n2017:\t\t\treturn -ENOMEM;\n2018:\t\n2019:\t\tslwt-\u003epcpu_counters = pcounters;\n2020:\t\n2021:\t\treturn 0;\n2022:\t}\n2023:\t\n2024:\tstatic int seg6_local_fill_nla_counters(struct sk_buff *skb,\n2025:\t\t\t\t\t\tstruct seg6_local_counters *counters)\n2026:\t{\n2027:\t\tif (nla_put_u64_64bit(skb, SEG6_LOCAL_CNT_PACKETS, counters-\u003epackets,\n"
} |
| 39/3 | 2026/09/18 15:01 | llm | kmsan-evaluator |
1mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 52276 output: 40 thoughts: 69 Thoughts: Okay, here's my interpretation of those thoughts, tailored for an expert audience, as if they were my own: **Initial Assessment and Verification** Right, this looks *exactly* like `seg6_local.c`. That's the initial impression. But we can't jump to conclusions, not yet. Hold on a second, let's just make sure there aren't any sneaky differences in `seg6_mobile.c`. The potential duplication is interesting, but we have to treat assumptions with healthy skepticism. Okay, let's zero in on lines 400 to 701 of `net/ipv6/seg6_mobile.c`. That's where the real detail lies. Time to dig in and confirm or refute this initial impression, and understand the precise interplay between these segments. I need to understand what nuances are present. |
| 40/3 | 2026/09/18 15:03 | tool | read-file |
0mArgs:{
"File": "net/ipv6/seg6_mobile.c",
"FirstLine": 400,
"LineCount": 100
}Results: {
"Contents": " 400:\t\t},\n 401:\t};\n 402:\t\n 403:\tstatic const struct nla_policy\n 404:\tseg6_mobile_policy[SEG6_MOBILE_MAX + 1] = {\n 405:\t\t[SEG6_MOBILE_ACTION]\t= { .type = NLA_U32 },\n 406:\t\t[SEG6_MOBILE_MAPPED_SID]\t= NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)),\n 407:\t\t[SEG6_MOBILE_COUNTERS]\t= { .type = NLA_NESTED },\n 408:\t};\n 409:\t\n 410:\tstatic const struct seg6_mobile_action_desc *\n 411:\tseg6_mobile_get_action_desc(int action)\n 412:\t{\n 413:\t\tint i;\n 414:\t\n 415:\t\tfor (i = 0; i \u003c ARRAY_SIZE(seg6_mobile_action_table); i++) {\n 416:\t\t\tif (seg6_mobile_action_table[i].action == action)\n 417:\t\t\t\treturn \u0026seg6_mobile_action_table[i];\n 418:\t\t}\n 419:\t\n 420:\t\treturn NULL;\n 421:\t}\n 422:\t\n 423:\t/* call the destroy() callback (if available) for each set attribute in\n 424:\t * @parsed_attrs, starting from the first attribute up to the @max_parsed\n 425:\t * (excluded) attribute.\n 426:\t */\n 427:\tstatic void __destroy_attrs(unsigned long parsed_attrs, int max_parsed,\n 428:\t\t\t\t struct seg6_mobile_lwt *slwt)\n 429:\t{\n 430:\t\tconst struct seg6_mobile_action_param *param;\n 431:\t\tint i;\n 432:\t\n 433:\t\tfor (i = SEG6_MOBILE_ACTION + 1; i \u003c max_parsed; i++) {\n 434:\t\t\tif (!(parsed_attrs \u0026 SEG6_MOBILE_F_ATTR(i)))\n 435:\t\t\t\tcontinue;\n 436:\t\n 437:\t\t\tparam = \u0026seg6_mobile_action_params[i];\n 438:\t\t\tif (param-\u003edestroy)\n 439:\t\t\t\tparam-\u003edestroy(slwt);\n 440:\t\t}\n 441:\t}\n 442:\t\n 443:\tstatic void destroy_attrs(struct seg6_mobile_lwt *slwt)\n 444:\t{\n 445:\t\tunsigned long attrs = slwt-\u003edesc-\u003eattrs | slwt-\u003eparsed_optattrs;\n 446:\t\n 447:\t\t__destroy_attrs(attrs, SEG6_MOBILE_MAX + 1, slwt);\n 448:\t}\n 449:\t\n 450:\tstatic int seg6_mobile_parse_attrs(struct nlattr **attrs,\n 451:\t\t\t\t\t struct seg6_mobile_lwt *slwt,\n 452:\t\t\t\t\t struct netlink_ext_ack *extack)\n 453:\t{\n 454:\t\tconst struct seg6_mobile_action_param *param;\n 455:\t\tconst struct seg6_mobile_action_desc *desc;\n 456:\t\tunsigned long parsed_optattrs = 0;\n 457:\t\tint i, err;\n 458:\t\n 459:\t\tdesc = slwt-\u003edesc;\n 460:\t\n 461:\t\tif (WARN_ON_ONCE(desc-\u003eattrs \u0026 desc-\u003eoptattrs))\n 462:\t\t\treturn -EINVAL;\n 463:\t\n 464:\t\tfor (i = SEG6_MOBILE_ACTION + 1; i \u003c= SEG6_MOBILE_MAX; i++) {\n 465:\t\t\tbool required = desc-\u003eattrs \u0026 SEG6_MOBILE_F_ATTR(i);\n 466:\t\t\tbool optional = desc-\u003eoptattrs \u0026 SEG6_MOBILE_F_ATTR(i);\n 467:\t\n 468:\t\t\tif (!required \u0026\u0026 !optional)\n 469:\t\t\t\tcontinue;\n 470:\t\n 471:\t\t\tif (required \u0026\u0026 !attrs[i]) {\n 472:\t\t\t\tNL_SET_ERR_MSG_MOD(extack,\n 473:\t\t\t\t\t\t \"missing required attribute\");\n 474:\t\t\t\terr = -EINVAL;\n 475:\t\t\t\tgoto err;\n 476:\t\t\t}\n 477:\t\n 478:\t\t\tif (!attrs[i])\n 479:\t\t\t\tcontinue;\n 480:\t\n 481:\t\t\tparam = \u0026seg6_mobile_action_params[i];\n 482:\t\t\terr = param-\u003eparse(attrs, slwt, extack);\n 483:\t\t\tif (err \u003c 0)\n 484:\t\t\t\tgoto err;\n 485:\t\n 486:\t\t\tif (optional)\n 487:\t\t\t\tparsed_optattrs |= SEG6_MOBILE_F_ATTR(i);\n 488:\t\t}\n 489:\t\n 490:\t\tslwt-\u003eparsed_optattrs = parsed_optattrs;\n 491:\t\n 492:\t\treturn 0;\n 493:\t\n 494:\terr:\n 495:\t\t__destroy_attrs(desc-\u003eattrs | parsed_optattrs, i, slwt);\n 496:\t\treturn err;\n 497:\t}\n 498:\t\n 499:\tstatic bool seg6_mobile_counters_enabled(struct seg6_mobile_lwt *slwt)\n"
} |
| 41/3 | 2026/09/18 15:03 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 54049 output: 40 thoughts: 27 Thoughts: Here's my summary, as if I were the one thinking it: **Diving into `seg6_mobile.c`** Alright, time to get my hands dirty. Let's zero in on lines 500 through 600 of `net/ipv6/seg6_mobile.c`. This is where the core of the mobile segment routing handling resides, so that's where I need to start. I'm especially interested in understanding the interaction between the mobility features and the existing segment routing infrastructure. I need to make sure I understand the context. Is this where the core logic for address updates resides, or is it more about state management? Is the code clean and well-commented, or am I going to be wading through spaghetti code? Let's get to it. |
| 42/3 | 2026/09/18 15:03 | tool | read-file |
0mArgs:{
"File": "net/ipv6/seg6_mobile.c",
"FirstLine": 500,
"LineCount": 100
}Results: {
"Contents": " 500:\t{\n 501:\t\treturn slwt-\u003eparsed_optattrs \u0026 SEG6_F_MOBILE_COUNTERS;\n 502:\t}\n 503:\t\n 504:\tstatic void seg6_mobile_update_counters(struct seg6_mobile_lwt *slwt,\n 505:\t\t\t\t\t\tunsigned int len, int err)\n 506:\t{\n 507:\t\tstruct pcpu_seg6_mobile_counters *pcounters;\n 508:\t\n 509:\t\tpcounters = this_cpu_ptr(slwt-\u003epcpu_counters);\n 510:\t\tu64_stats_update_begin(\u0026pcounters-\u003esyncp);\n 511:\t\n 512:\t\tif (likely(!err)) {\n 513:\t\t\tu64_stats_inc(\u0026pcounters-\u003epackets);\n 514:\t\t\tu64_stats_add(\u0026pcounters-\u003ebytes, len);\n 515:\t\t} else {\n 516:\t\t\tu64_stats_inc(\u0026pcounters-\u003eerrors);\n 517:\t\t}\n 518:\t\n 519:\t\tu64_stats_update_end(\u0026pcounters-\u003esyncp);\n 520:\t}\n 521:\t\n 522:\tstatic int seg6_mobile_input(struct sk_buff *skb)\n 523:\t{\n 524:\t\tstruct dst_entry *orig_dst = skb_dst(skb);\n 525:\t\tstruct seg6_mobile_lwt *slwt;\n 526:\t\tunsigned int len = skb-\u003elen;\n 527:\t\tint rc;\n 528:\t\n 529:\t\tif (skb-\u003eprotocol != htons(ETH_P_IPV6)) {\n 530:\t\t\tkfree_skb(skb);\n 531:\t\t\treturn -EINVAL;\n 532:\t\t}\n 533:\t\n 534:\t\tslwt = seg6_mobile_lwtunnel(orig_dst-\u003elwtstate);\n 535:\t\n 536:\t\trc = slwt-\u003edesc-\u003einput(skb, slwt);\n 537:\t\n 538:\t\tif (seg6_mobile_counters_enabled(slwt))\n 539:\t\t\tseg6_mobile_update_counters(slwt, len, rc);\n 540:\t\n 541:\t\treturn rc;\n 542:\t}\n 543:\t\n 544:\tstatic int seg6_mobile_build_state(struct net *net, struct nlattr *nla,\n 545:\t\t\t\t\t unsigned int family, const void *cfg,\n 546:\t\t\t\t\t struct lwtunnel_state **ts,\n 547:\t\t\t\t\t struct netlink_ext_ack *extack)\n 548:\t{\n 549:\t\tconst struct seg6_mobile_action_desc *desc;\n 550:\t\tstruct nlattr *tb[SEG6_MOBILE_MAX + 1];\n 551:\t\tstruct lwtunnel_state *newts;\n 552:\t\tstruct seg6_mobile_lwt *slwt;\n 553:\t\tint err;\n 554:\t\n 555:\t\tif (family != AF_INET6)\n 556:\t\t\treturn -EINVAL;\n 557:\t\n 558:\t\terr = nla_parse_nested(tb, SEG6_MOBILE_MAX, nla,\n 559:\t\t\t\t seg6_mobile_policy, extack);\n 560:\t\tif (err \u003c 0)\n 561:\t\t\treturn err;\n 562:\t\n 563:\t\tif (!tb[SEG6_MOBILE_ACTION]) {\n 564:\t\t\tNL_SET_ERR_MSG_MOD(extack, \"missing SEG6_MOBILE_ACTION\");\n 565:\t\t\treturn -EINVAL;\n 566:\t\t}\n 567:\t\n 568:\t\tdesc = seg6_mobile_get_action_desc(nla_get_u32(tb[SEG6_MOBILE_ACTION]));\n 569:\t\tif (!desc) {\n 570:\t\t\tNL_SET_ERR_MSG_MOD(extack, \"unknown SRv6 Mobile action\");\n 571:\t\t\treturn -EOPNOTSUPP;\n 572:\t\t}\n 573:\t\n 574:\t\tnewts = lwtunnel_state_alloc(sizeof(*slwt));\n 575:\t\tif (!newts)\n 576:\t\t\treturn -ENOMEM;\n 577:\t\n 578:\t\tslwt = seg6_mobile_lwtunnel(newts);\n 579:\t\tslwt-\u003eaction = desc-\u003eaction;\n 580:\t\tslwt-\u003edesc = desc;\n 581:\t\n 582:\t\terr = seg6_mobile_parse_attrs(tb, slwt, extack);\n 583:\t\tif (err \u003c 0) {\n 584:\t\t\tkfree(newts);\n 585:\t\t\treturn err;\n 586:\t\t}\n 587:\t\n 588:\t\tnewts-\u003etype = LWTUNNEL_ENCAP_SEG6_MOBILE;\n 589:\t\tnewts-\u003eflags = LWTUNNEL_STATE_INPUT_REDIRECT;\n 590:\t\n 591:\t\t*ts = newts;\n 592:\t\n 593:\t\treturn 0;\n 594:\t}\n 595:\t\n 596:\tstatic void seg6_mobile_destroy_state(struct lwtunnel_state *lwt)\n 597:\t{\n 598:\t\tdestroy_attrs(seg6_mobile_lwtunnel(lwt));\n 599:\t}\n"
} |
| 43/3 | 2026/09/18 15:03 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 55778 output: 21 thoughts: 1881 Thoughts: **Deep Dive into `seg6_mobile_advance_da` and Related Functions** Okay, let's break this down. First, the plan: I need to grasp how the destination address is updated in this mobile IPv6 segment routing context. I'll examine the code snippet for `seg6_mobile_advance_da`, paying close attention to data structure manipulation and potential issues. Alright, let's start with `seg6_mobile_get_encap_size`. This looks straightforward; it's calculating the size needed for encapsulating a packet. It considers attributes like whether a mapped SID is present and counter information. It calculates the network layer size (`nlsize`) based on the presence of attributes, which is then incremented based on various sizes based on the attributes set. Now, focusing on `seg6_mobile_advance_da` – the core function. The core idea seems to be advancing the destination address (DA) of an IPv6 header, potentially adjusting checksums along the way. First, I see the basic checks, ensuring the SKB is valid and can be pulled. I'm initially concerned about the `frag_off` variable, which is passed to `ipv6_skip_exthdr`. I was worried about an uninitialized value, however, let's check the code: `l4_off = ipv6_skip_exthdr(skb, sizeof(*ip6h), &nexthdr, &frag_off);` I see `ipv6_skip_exthdr` explicitly initializes `frag_off` to 0. It is unconditional. Problem solved. Next, I need to check `old_da`: `old_da = ip6h->daddr;` `ip6h` is already checked via the use of `pskb_may_pull(skb, sizeof(*ip6h))` or `skb_ensure_writable(skb, write_len)`. In either case, it confirms `sizeof(*ip6h)` is 40 bytes. Then `ip6h->daddr` is an IPv6 address (16 bytes), which means `old_da` is a copy on the stack of `ip6h->daddr` and has been initialized. Good to go. Now, regarding the checksum (`csum`) updates. I have questions and need to confirm a few assumptions: `csum = l4_hlen ? seg6_mobile_l4_csum(skb, l4_off, nexthdr) : NULL;` If `l4_hlen != 0`, then the length is updated. Also: `skb_ensure_writable(skb, write_len)` ensures that up to `l4_off + l4_hlen` is in the linear buffer `skb->data`. I was wondering what the `seg6_mobile_l4_csum` function does, and whether it could have any issues. I see: If TCP, it returns `&((struct tcphdr *)(skb->data + l4_off))->check;` `l4_hlen` was `sizeof(struct tcphdr) = 20`. `offsetof(struct tcphdr, check)` is 16, which is < 20. If UDP, it returns `&((struct udphdr *)(skb->data + l4_off))->check;` `l4_hlen` was `sizeof(struct udphdr) = 8`. `offsetof(struct udphdr, check)` is 6, which is < 8. If ICMPV6, it returns `&((struct icmp6hdr *)(skb->data + l4_off))->icmp6_cksum;` `l4_hlen` was `sizeof(struct icmp6hdr) = 8` (or 4? `struct icmp6hdr` is 8 bytes, `icmp6_cksum` is at offset 2). Okay, so `csum` points to a valid part of the `skb->data` buffer. No problem there. The last check is what if the packet size is not large enough. `skb_ensure_writable(skb, write_len)`: If `write_len > skb->len`, `skb_ensure_writable` returns `-EINVAL` or fails! It's good to know. The function itself guards against it, so no buffer overflows here. I can verify it, but it appears to be solid. Okay, I believe that completes my review. |
| 44/3 | 2026/09/18 15:03 | tool | grepper |
0mArgs:{
"Expression": "int skb_ensure_writable"
}Results: {
"Output": "include/linux/skbuff.h=4308=struct sk_buff *skb_vlan_untag(struct sk_buff *skb);\ninclude/linux/skbuff.h:4309:int skb_ensure_writable(struct sk_buff *skb, unsigned int write_len);\ninclude/linux/skbuff.h:4310:int skb_ensure_writable_head_tail(struct sk_buff *skb, struct net_device *dev);\ninclude/linux/skbuff.h-4311-int __skb_vlan_pop(struct sk_buff *skb, u16 *vlan_tci);\n--\nnet/core/skbuff.c=6381=EXPORT_SYMBOL(skb_vlan_untag);\nnet/core/skbuff.c-6382-\nnet/core/skbuff.c:6383:int skb_ensure_writable(struct sk_buff *skb, unsigned int write_len)\nnet/core/skbuff.c-6384-{\n--\nnet/core/skbuff.c=6393=EXPORT_SYMBOL(skb_ensure_writable);\nnet/core/skbuff.c-6394-\nnet/core/skbuff.c:6395:int skb_ensure_writable_head_tail(struct sk_buff *skb, struct net_device *dev)\nnet/core/skbuff.c-6396-{\n"
} |
| 45/3 | 2026/09/18 15:03 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 59040 output: 37 thoughts: 23 Thoughts: Here's my summary, as if I were thinking these thoughts: **Diving into skb_ensure_writable** Okay, time to dissect `skb_ensure_writable` in `net/core/skbuff.c`. I need to refresh my memory on the intricacies of SKB memory management. Let's see... the name itself suggests it's about making an SKB buffer ready for modification. This likely involves handling shared buffers and potential copy-on-write scenarios. I'm guessing it will check the refcount, and if needed allocate a new buffer. I expect to see considerations for fragmentation, linear vs. non-linear skbs. Probably also cache coherency concerns. I'll be looking specifically at any performance implications, memory allocation strategies, and potential locking that might be involved within the function. Let's see how this thing *really* works. |
| 46/3 | 2026/09/18 15:03 | tool | read-file |
0mArgs:{
"File": "net/core/skbuff.c",
"FirstLine": 6383,
"LineCount": 15
}Results: {
"Contents": "6383:\tint skb_ensure_writable(struct sk_buff *skb, unsigned int write_len)\n6384:\t{\n6385:\t\tif (!pskb_may_pull(skb, write_len))\n6386:\t\t\treturn -ENOMEM;\n6387:\t\n6388:\t\tif (!skb_cloned(skb) || skb_clone_writable(skb, write_len))\n6389:\t\t\treturn 0;\n6390:\t\n6391:\t\treturn pskb_expand_head(skb, 0, 0, GFP_ATOMIC);\n6392:\t}\n6393:\tEXPORT_SYMBOL(skb_ensure_writable);\n6394:\t\n6395:\tint skb_ensure_writable_head_tail(struct sk_buff *skb, struct net_device *dev)\n6396:\t{\n6397:\t\tint needed_headroom = dev-\u003eneeded_headroom;\n"
} |
| 47/3 | 2026/09/18 15:03 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 59554 output: 29 thoughts: 381 Thoughts: **My Analysis of IPv6 Segment Routing Header Handling** Okay, so I'm looking at this code and specifically, how it interacts with the `skb_ensure_writable` function. It seems like the critical piece here is the call to `pskb_may_pull(skb, write_len)` which is triggered by `skb_ensure_writable`. I need to understand *when* the write operation occurs in relation to the SRH handling. Now, I'm diving into `seg6_mobile_get_and_validate_srh`. The goal here is pretty clear: retrieve and validate the Segment Routing Header (SRH). It starts by calling `seg6_get_srh(skb, 0)`. That's the entry point! It checks for the presence of the SRH. If found, there's a possible HMAC validation using `seg6_hmac_validate_skb(skb)` based on a config option. If the HMAC checks out, we mark the SRH as present. Otherwise, it's flagged as malformed and we return NULL. The interesting part is when it *doesn't* find the SRH immediately. Then, it tries a more general search using `ipv6_find_hdr`. This looks for a routing header of type `IPPROTO_ROUTING`. If it finds that header, it needs to validate that it is an SRH. If it *doesn't* find it, we flag the SRH as absent. I need to understand what `seg6_get_srh` is actually doing. That's the key to this flow. It's the starting point. So, I need to check the function's definition in `net/ipv6/seg6.c`. I need to figure out what the function is actually doing; is it checking the skb data directly, or is it doing some kind of pulling or copying? I need to know where the write operation is happening if there is one. |
| 48/3 | 2026/09/18 15:04 | tool | grepper |
0mArgs:{
"Expression": "struct ipv6_sr_hdr \\*seg6_get_srh"
}Results: {
"Output": "include/net/seg6.h=75=extern bool seg6_validate_srh(struct ipv6_sr_hdr *srh, int len, bool reduced);\ninclude/net/seg6.h:76:extern struct ipv6_sr_hdr *seg6_get_srh(struct sk_buff *skb, int flags);\ninclude/net/seg6.h-77-extern void seg6_icmp_srh(struct sk_buff *skb, struct inet6_skb_parm *opt);\n--\nnet/ipv6/seg6.c=26=bool seg6_validate_srh(struct ipv6_sr_hdr *srh, int len, bool reduced)\n--\nnet/ipv6/seg6.c-78-\nnet/ipv6/seg6.c:79:struct ipv6_sr_hdr *seg6_get_srh(struct sk_buff *skb, int flags)\nnet/ipv6/seg6.c-80-{\n"
} |
| 49/3 | 2026/09/18 15:04 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 60644 output: 36 thoughts: 25 Thoughts: Here's my summary, as though I'm reading and processing the provided code information: **Analyzing the `seg6_get_srh` Function** Okay, time to dig into the `seg6_get_srh` function within `net/ipv6/seg6.c`. I need to understand how the Segment Routing Header (SRH) is retrieved, particularly within the IPv6 context. I'm assuming this function is crucial for any SRv6-related processing, likely pulling the SRH from a packet. I'll need to look closely at how it handles memory allocation, error conditions, and the actual pointer manipulation involved in extracting the relevant header information. I'm hoping to see well-defined error checks and efficient data access here, since SRv6 can be performance-sensitive. This will likely provide a useful model of how header parsing and extraction work in the kernel. |
| 50/3 | 2026/09/18 15:04 | tool | read-file |
0mArgs:{
"File": "net/ipv6/seg6.c",
"FirstLine": 75,
"LineCount": 40
}Results: {
"Contents": " 75:\t\n 76:\t\treturn true;\n 77:\t}\n 78:\t\n 79:\tstruct ipv6_sr_hdr *seg6_get_srh(struct sk_buff *skb, int flags)\n 80:\t{\n 81:\t\tstruct ipv6_sr_hdr *srh;\n 82:\t\tint len, srhoff = 0;\n 83:\t\n 84:\t\tif (ipv6_find_hdr(skb, \u0026srhoff, IPPROTO_ROUTING, NULL, \u0026flags) \u003c 0)\n 85:\t\t\treturn NULL;\n 86:\t\n 87:\t\tif (!pskb_may_pull(skb, srhoff + sizeof(*srh)))\n 88:\t\t\treturn NULL;\n 89:\t\n 90:\t\tsrh = (struct ipv6_sr_hdr *)(skb-\u003edata + srhoff);\n 91:\t\n 92:\t\tlen = (srh-\u003ehdrlen + 1) \u003c\u003c 3;\n 93:\t\n 94:\t\tif (!pskb_may_pull(skb, srhoff + len))\n 95:\t\t\treturn NULL;\n 96:\t\n 97:\t\t/* note that pskb_may_pull may change pointers in header;\n 98:\t\t * for this reason it is necessary to reload them when needed.\n 99:\t\t */\n 100:\t\tsrh = (struct ipv6_sr_hdr *)(skb-\u003edata + srhoff);\n 101:\t\n 102:\t\tif (!seg6_validate_srh(srh, len, true))\n 103:\t\t\treturn NULL;\n 104:\t\n 105:\t\treturn srh;\n 106:\t}\n 107:\t\n 108:\t/* Determine if an ICMP invoking packet contains a segment routing\n 109:\t * header. If it does, extract the offset to the true destination\n 110:\t * address, which is in the first segment address.\n 111:\t */\n 112:\tvoid seg6_icmp_srh(struct sk_buff *skb, struct inet6_skb_parm *opt)\n 113:\t{\n 114:\t\t__u16 network_header = skb-\u003enetwork_header;\n"
} |
| 51/3 | 2026/09/18 15:04 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 61493 output: 22 thoughts: 247 Thoughts: **My Analysis of SRH Validation and Forwarding** Okay, so I'm digging into how Segment Routing Header (SRH) validation and forwarding are handled, specifically in a mobile context. I'm focusing on these key functions: `seg6_get_srh`, `seg6_mobile_get_and_validate_srh`, `seg6_mobile_forward`, and `seg6_lookup_nexthop`. First, `seg6_get_srh` is the workhorse for initial SRH validation, using `seg6_validate_srh` internally. Now, if that function fails and returns NULL in `seg6_mobile_get_and_validate_srh`, the code intelligently falls back to `ipv6_find_hdr`. This is where it gets interesting, as it checks for *any* IPv6 routing header via `IPPROTO_ROUTING`. If *that* search fails with `-ENOENT`, then we definitively know the SRH is absent, and the state is set to `SEG6_MOBILE_SRH_ABSENT`. But, and this is important, if `ipv6_find_hdr` *does* find *a* routing header (but not a valid one), or if HMAC fails, the state is set to `SEG6_MOBILE_SRH_MALFORMED`. This is a crucial distinction. We are differentiating between *absence* and *malformation*. Now, let's switch gears and examine the forwarding path. Looking at `seg6_mobile_forward`, it's strikingly simple. It calls `seg6_lookup_nexthop` and then uses the standard `dst_input` for the packet, which is quite standard. This suggests that the real heavy lifting of SRH processing and path selection is already done *before* this point. `seg6_lookup_nexthop` is the key function to trace. My next step will be to dive deeper into how `seg6_lookup_nexthop` actually works to determine the next hop based on the SRH, and what it does in relation to the state flag. |
| 52/3 | 2026/09/18 15:04 | tool | grepper |
0mArgs:{
"Expression": "seg6_lookup_nexthop"
}Results: {
"Output": "include/net/seg6.h=80=extern int seg6_do_srh_inline(struct sk_buff *skb, struct ipv6_sr_hdr *osrh);\ninclude/net/seg6.h:81:extern int seg6_lookup_nexthop(struct sk_buff *skb, struct in6_addr *nhaddr,\ninclude/net/seg6.h-82-\t\t\t u32 tbl_id);\n--\ninclude/net/seg6_local.h-16-\ninclude/net/seg6_local.h:17:extern int seg6_lookup_nexthop(struct sk_buff *skb, struct in6_addr *nhaddr,\ninclude/net/seg6_local.h-18-\t\t\t u32 tbl_id);\n--\nnet/core/filter.c=6880=static int bpf_push_seg6_encap(struct sk_buff *skb, u32 type, void *hdr, u32 len)\n--\nnet/core/filter.c-6909-\nnet/core/filter.c:6910:\treturn seg6_lookup_nexthop(skb, NULL, 0);\nnet/core/filter.c-6911-}\n--\nnet/core/filter.c=7032=BPF_CALL_4(bpf_lwt_seg6_action, struct sk_buff *, skb,\n--\nnet/core/filter.c-7046-\t\t\treturn -EINVAL;\nnet/core/filter.c:7047:\t\treturn seg6_lookup_nexthop(skb, (struct in6_addr *)param, 0);\nnet/core/filter.c-7048-\tcase SEG6_LOCAL_ACTION_END_T:\n--\nnet/core/filter.c-7052-\t\t\treturn -EINVAL;\nnet/core/filter.c:7053:\t\treturn seg6_lookup_nexthop(skb, NULL, *(int *)param);\nnet/core/filter.c-7054-\tcase SEG6_LOCAL_ACTION_END_DT6:\n--\nnet/core/filter.c-7071-\t\tbpf_update_srh_state(skb);\nnet/core/filter.c:7072:\t\treturn seg6_lookup_nexthop(skb, NULL, *(int *)param);\nnet/core/filter.c-7073-\tcase SEG6_LOCAL_ACTION_END_B6:\n--\nnet/ipv6/seg6_local.c=288=seg6_lookup_any_nexthop(struct sk_buff *skb, struct in6_addr *nhaddr,\n--\nnet/ipv6/seg6_local.c-348-\nnet/ipv6/seg6_local.c:349:int seg6_lookup_nexthop(struct sk_buff *skb,\nnet/ipv6/seg6_local.c-350-\t\t\tstruct in6_addr *nhaddr, u32 tbl_id)\n--\nnet/ipv6/seg6_local.c=397=static int input_action_end_finish(struct sk_buff *skb,\n--\nnet/ipv6/seg6_local.c-399-{\nnet/ipv6/seg6_local.c:400:\tseg6_lookup_nexthop(skb, NULL, 0);\nnet/ipv6/seg6_local.c-401-\n--\nnet/ipv6/seg6_local.c=845=static int input_action_end_t(struct sk_buff *skb, struct seg6_local_lwt *slwt)\n--\nnet/ipv6/seg6_local.c-854-\nnet/ipv6/seg6_local.c:855:\tseg6_lookup_nexthop(skb, NULL, slwt-\u003etable);\nnet/ipv6/seg6_local.c-856-\n--\nnet/ipv6/seg6_local.c=921=static int input_action_end_dx6_finish(struct net *net, struct sock *sk,\n--\nnet/ipv6/seg6_local.c-938-\nnet/ipv6/seg6_local.c:939:\tseg6_lookup_nexthop(skb, nhaddr, 0);\nnet/ipv6/seg6_local.c-940-\n--\nnet/ipv6/seg6_local.c=1347=static int input_action_end_b6(struct sk_buff *skb, struct seg6_local_lwt *slwt)\n--\nnet/ipv6/seg6_local.c-1361-\nnet/ipv6/seg6_local.c:1362:\tseg6_lookup_nexthop(skb, NULL, 0);\nnet/ipv6/seg6_local.c-1363-\n--\nnet/ipv6/seg6_local.c=1372=static int input_action_end_b6_encap(struct sk_buff *skb,\n--\nnet/ipv6/seg6_local.c-1392-\nnet/ipv6/seg6_local.c:1393:\tseg6_lookup_nexthop(skb, NULL, 0);\nnet/ipv6/seg6_local.c-1394-\n--\nnet/ipv6/seg6_local.c=1430=static int input_action_end_bpf(struct sk_buff *skb,\n--\nnet/ipv6/seg6_local.c-1476-\tif (ret != BPF_REDIRECT)\nnet/ipv6/seg6_local.c:1477:\t\tseg6_lookup_nexthop(skb, NULL, 0);\nnet/ipv6/seg6_local.c-1478-\n--\nnet/ipv6/seg6_mobile.c=219=static int seg6_mobile_forward(struct sk_buff *skb)\nnet/ipv6/seg6_mobile.c-220-{\nnet/ipv6/seg6_mobile.c:221:\tseg6_lookup_nexthop(skb, NULL, 0);\nnet/ipv6/seg6_mobile.c-222-\treturn dst_input(skb);\n"
} |
| 53/3 | 2026/09/18 15:04 | llm | kmsan-evaluator |
1mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 63795 output: 458 thoughts: 4185 Thoughts: **Analysis of Memory Safety and Initialization in SRv6 Mobile Code**
Alright, let's break down this code and ensure we're not introducing any uninitialized memory vulnerabilities. My analysis focuses on the potential for Kernel Memory Sanitizer (KMSAN) to be necessary. The primary concern with KMSAN is to identify instances where the kernel might read from uninitialized memory. Given that this code deals with network data and user-provided configuration, it's essential to meticulously verify every memory operation.
The core of the matter revolves around how the code allocates and uses memory, especially anything accessible via Netlink. For instance, the creation of the `lwtunnel_state` happens via `lwtunnel_state_alloc`, and this is correctly implemented using `kzalloc`. This ensures any newly allocated memory is zero-initialized, which immediately removes a significant class of KMSAN concerns. Similarly, per-CPU statistics (`pcpu_counters`) are allocated via `__netdev_alloc_pcpu_stats` using `__GFP_ZERO`. Zero-initialization is the key to preventing potential information leaks through uninitialized memory.
Next, let's examine data that is passed back to userspace, which is mainly done via Netlink attributes. The primary attributes sent back are: `SEG6_MOBILE_ACTION`, `SEG6_MOBILE_MAPPED_SID`, and `SEG6_MOBILE_COUNTERS`.
- `SEG6_MOBILE_ACTION` sends a simple unsigned 32-bit integer, initialized during the build process.
- `SEG6_MOBILE_MAPPED_SID` copies a `struct in6_addr`, a structure of fixed size, and that data is fully copied, or zero-initialized.
- `SEG6_MOBILE_COUNTERS` sends the per-CPU counters.
The per CPU counters are retrieved, and individually formatted as 64 bit integers, padded as needed to make them fit within the Netlink format. This means any uninitialized padding within the `counters` structure is explicitly managed during the `nla_put` calls, which explicitly initialize the values.
Now, let's talk about conditional logic and variables. Any variables used in conditional branching (like in the packet header processing or the attribute parsing) have been explicitly initialized to a known value prior to being used. For example, `frag_off`, `nexthdr`, `srh_state` or `counters` are all initialized and set before they are used.
Finally, regarding potential risks in packet handling and buffer operations (e.g., `pskb_may_pull`, `skb_ensure_writable`, `ipv6_skip_exthdr`, etc.), I want to highlight that any potential issues in these areas would be out-of-bounds accesses, NULL dereferences, or memory corruption. These would be caught by KASAN and standard sanitizers and debuggers. Given all these considerations, KMSAN does not seem to be necessary in this case. The code is carefully designed to avoid reading uninitialized memory.
|
| 54/3 | 2026/09/18 15:06 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch introduces support for the SRv6 Mobile User Plane (RFC 9433) behaviors via the lightweight tunnel (lwtunnel) encapsulation framework, adding `seg6_mobile.c`.\n\nA detailed evaluation of the memory safety aspects shows no risk of uninitialized memory usage or info-leaks:\n1. Dynamic allocations: State memory allocated via `lwtunnel_state_alloc()` uses `kzalloc()`, and per-CPU counters allocated via `seg6_mobile_alloc_pcpu_counters()` use `__netdev_alloc_pcpu_stats()` with `__GFP_ZERO`. All heap and per-CPU structures are completely zero-initialized.\n2. Kernel-to-userspace data flow: In `seg6_mobile_fill_encap()`, attributes are serialized individually using `nla_put_u32()`, `nla_put_in6_addr()`, and `nla_put_u64_64bit()` with alignment padding (`SEG6_MOBILE_CNT_PAD`). Stack structures (`struct seg6_mobile_counters`) are zero-initialized (`= { 0, 0, 0 }`). No uninitialized struct padding or uninitialized memory is copied or leaked to userspace.\n3. Control flow and local variables: All stack variables (`srh_state`, `frag_off`, `nexthdr`, `l4_off`, `l4_hlen`, `csum`, `tb`, etc.) are explicitly initialized before use or initialized unconditionally by the invoked helpers (e.g. `ipv6_skip_exthdr()`).\n4. KASAN vs. KMSAN: Any potential bugs introduced in packet header parsing, SKB linear buffer expansion (`skb_ensure_writable()`), or netlink parsing would manifest as out-of-bounds reads/writes, NULL pointer dereferences, or memory corruption, all of which are caught by KASAN and standard kernel debuggers.\n\nTherefore, a dedicated KMSAN session is not warranted.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch introduces support for the SRv6 Mobile User Plane (RFC 9433) behaviors via the lightweight tunnel (lwtunnel) encapsulation framework, adding `seg6_mobile.c`.\n\nA detailed evaluation of the memory safety aspects shows no risk of uninitialized memory usage or info-leaks:\n1. Dynamic allocations: State memory allocated via `lwtunnel_state_alloc()` uses `kzalloc()`, and per-CPU counters allocated via `seg6_mobile_alloc_pcpu_counters()` use `__netdev_alloc_pcpu_stats()` with `__GFP_ZERO`. All heap and per-CPU structures are completely zero-initialized.\n2. Kernel-to-userspace data flow: In `seg6_mobile_fill_encap()`, attributes are serialized individually using `nla_put_u32()`, `nla_put_in6_addr()`, and `nla_put_u64_64bit()` with alignment padding (`SEG6_MOBILE_CNT_PAD`). Stack structures (`struct seg6_mobile_counters`) are zero-initialized (`= { 0, 0, 0 }`). No uninitialized struct padding or uninitialized memory is copied or leaked to userspace.\n3. Control flow and local variables: All stack variables (`srh_state`, `frag_off`, `nexthdr`, `l4_off`, `l4_hlen`, `csum`, `tb`, etc.) are explicitly initialized before use or initialized unconditionally by the invoked helpers (e.g. `ipv6_skip_exthdr()`).\n4. KASAN vs. KMSAN: Any potential bugs introduced in packet header parsing, SKB linear buffer expansion (`skb_ensure_writable()`), or netlink parsing would manifest as out-of-bounds reads/writes, NULL pointer dereferences, or memory corruption, all of which are caught by KASAN and standard kernel debuggers.\n\nTherefore, a dedicated KMSAN session is not warranted.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|