* [PATCH net-next v9 00/10] ipv6: Address ext hdr DoS vulnerabilities
@ 2026-03-14 17:51 Tom Herbert
2026-03-14 17:51 ` [PATCH net-next v9 01/10] ipv6: Check of max HBH or DestOp sysctl is zero and drop if it is Tom Herbert
` (10 more replies)
0 siblings, 11 replies; 15+ messages in thread
From: Tom Herbert @ 2026-03-14 17:51 UTC (permalink / raw)
To: davem, kuba, netdev, justin.iurman, willemdebruijn.kernel, pabeni
Cc: Tom Herbert
IPv6 extension headers are defined to be quite open ended with few
limits. For instance, RFC8200 requires a receiver to process any
number of extension headers in a packet in any order. This flexibility
comes at the cost of a potential Denial of Service attack. The only
thing that might mitigate the DoS attacks is the fact that packets
with extension headers experience high drop rates on the Internet so
that a DoS attack based on extension wouldn't be very effective at
Internet scale.
This patch set addresses some of the more egregious vulnerabilities
of extension headers to DoS attack.
- If sysctl.max_dst_opts_cnt or hbh_opts_cnt are set to 0 then that
disallows packets with Destination Options or Hop-by-Hop Options even
if the packet contain zero non-padding options
- Add a case for IPV6_TLV_TNL_ENCAP_LIMIT in the switch on TLV type
in ip6_parse_tlv function. This TLV is handled in tunnel processing,
however it needs to be detected in ip6_parse_tlv to properly account
for it as recognized non-padding option
- Move IPV6_TLV_TNL_ENCAP_LIMIT to uapi/linux/in6.h so that all the
TLV definitions are in one place
- Set the default limits of non-padding Hop-by-Hop and Destination
options to 2. This means that if a packet contains more then two
non-padding options then it will be dropped. The previous limit
was 8, but that was too liberal considering that the stack only
support two Destination Options and the most Hop-by-Hop options
likely to ever be in the same packet are IOAM and JUMBO. The limit
can be increased via sysctl for private use and experimentation
- Enforce RFC8200 recommended ordering of Extension Headers. This
also enforces that any Extension Header occurs at most once
in a packet except for Destination Options that may appear
twice. The enforce_ext_hdr_order sysctl controls enforcement. If
it's set to true then order is enforced, if it's set to false then
neither order nor number of occurrences are enforced.
The enforced ordering is:
IPv6 header
Hop-by-Hop Options header
Destination Options before the Routing header
Routing header
Fragment header
Authentication header
Encapsulating Security Payload header
Destination Options header
Upper-Layer header
--- Background
We selected the default value of eight in RFC8504 based on an
expectation that there might be new options defined and that the
Internet would be fixed to reliably support extension headers
including those with options. I do not believe either of those are
going to happen.
Hop-by-Hop Options are ostensibly the right way to do network to host
and host to network signaling.The only HBH options that might get any
substantial deployment are Router Alert option and IOAM. The Router
Alert option is being deprecated and IOAM is at best a "nice to
have". The best use case of Hop-by-Hop options is congestion
signaling, unfortunately the die was cast when CSIG authors decided to
place the information in VLANs at L2 and cajole the information to be
routable through a switch. IMO, the miss on CSIG pretty much is the
nail in the coffin for Hop-by-Hop options to ever be widely deployed
(https://www.ietf.org/archive/id/draft-ravi-ippm-csig-01.txt).
Destination Options have proven even less useful than Hop-by-Hop
Options. The only Destination Options supported by the stack is the
Tunnel Encap Limit option and Home Address Options. The Tunnel Encap
Option was buried in the v6 tunnel code which is why it wasn't obvious
it was supported in the first version of the patch set. I'll assume
that might be useful, so this patch set cleans up the code for it. I
don't believe there's any use of Home Address Option.
A major problem with DestOpts, HBH, Routing Header, and Fragment
header is that they have no inherent security. Their use presents a
security risk especially when sent over untrusted networks including
the Internet. Given that and that the high drop rates of extension
headers on the Internet, I am proposing that Extension header except
for ESP being deprecated on the Internet
(https://www.ietf.org/archive/id/draft-herbert-deprecate-eh-01.txt).
An update to RFC8200 is being proposed in IETF to allow enforcement of
extension header ordering and number of occurrences. An I-D is in
https://www.ietf.org/archive/id/draft-iurman-6man-eh-occurrences-00.txt
--- Testing
Add selftest eh_limits.sh. Tested by running:
$ sudo ./eh_limits.sh
TEST: EH-limits - default sysctls [ OK ]
TEST: EH-limits - modified sysctls [ OK ]
Tests passed: 2
Tests failed: 0
$ sudo ./eh_limits.sh -v
>>>>> Default
TEST: Two non-padding options in HBH and DestOpts: Received as expected
TEST: Big destination option: Received as expected
TEST: Almost Big HBH option: Received as expected
TEST: Big HBH option: Received as expected
TEST: Too much HBH padding: Didn't receive as expected
TEST: Too much DestOpt padding: Didn't receive as expected
TEST: Too much DestOpt padding #2: Didn't receive as expected
TEST: Too much DestOpt padding #3: Didn't receive as expected
TEST: Almost too much DestOpt padding #2: Received as expected
TEST: Two Dest Ops: Didn't receive as expected
TEST: OOO Routing header: Didn't receive as expected
TEST: Two Routing headers: Didn't receive as expected
TEST: Two Destination options okay: Received as expected
TEST: Two Destination options: Didn't receive as expected
TEST: Two Destination options after RH: Didn't receive as expected
TEST: Many EH OOO: Didn't receive as expected
TEST: Two fragment Headers: Didn't receive as expected
TEST: EH-limits - default sysctls [ OK ]
>>>>> No order enforce, 8 options, 66 length limit
TEST: Two non-padding options in HBH and DestOpts: Received as expected
TEST: Big destination option: Didn't receive as expected
TEST: Almost Big HBH option: Received as expected
TEST: Big HBH option: Didn't receive as expected
TEST: Too much HBH padding: Didn't receive as expected
TEST: Too much DestOpt padding: Didn't receive as expected
TEST: Too much DestOpt padding #2: Didn't receive as expected
TEST: Too much DestOpt padding #3: Didn't receive as expected
TEST: Almost too much DestOpt padding #2: Received as expected
TEST: Two Dest Ops: Received as expected
TEST: OOO Routing header: Received as expected
TEST: Two Routing headers: Received as expected
TEST: Two Destination options okay: Received as expected
TEST: Two Destination options: Received as expected
TEST: Two Destination options after RH: Received as expected
TEST: Many EH OOO: Received as expected
TEST: Two fragment Headers: Didn't receive as expected
TEST: EH-limits - modified sysctls [ OK ]
Tests passed: 2
Tests failed: 0
V4: Switch order of patches to avoid transient build failure
V5: Allow Destination Options before the Routing header, fixes
suggested by Justin Iurman
v6: Fix a bug and create a test for extension header limits
v7: Run pylint on new Python file, run shellcheck on new bash file,
Make fixes as needed
v8: Repost
v9: Fix subject prefix
Tom Herbert (10):
ipv6: Check of max HBH or DestOp sysctl is zero and drop if it is
ipv6: Cleanup IPv6 TLV definitions
ipv6: Add case for IPV6_TLV_TNL_ENCAP_LIMIT in EH TLV switch
ipv6: Set HBH and DestOpt limits to 2
ipv6: Document defaults for max_{dst|hbh}_opts_number sysctls
ipv6: Enforce Extension Header ordering
ipv6: Document enforce_ext_hdr_order sysctl
test: Add proto_nums.py in networking selftests
test: Add ext_hdr.py in networking selftests
test: Add networking selftest for eh limits
Documentation/networking/ip-sysctl.rst | 52 ++-
include/net/ipv6.h | 9 +-
include/net/netns/ipv6.h | 1 +
include/net/protocol.h | 14 +
include/uapi/linux/in6.h | 21 +-
include/uapi/linux/ip6_tunnel.h | 1 -
net/ipv6/af_inet6.c | 1 +
net/ipv6/exthdrs.c | 32 +-
net/ipv6/ip6_input.c | 42 +++
net/ipv6/reassembly.c | 1 +
net/ipv6/sysctl_net_ipv6.c | 7 +
net/ipv6/xfrm6_protocol.c | 2 +
tools/testing/selftests/net/Makefile | 1 +
tools/testing/selftests/net/eh_limits.py | 349 ++++++++++++++++++++
tools/testing/selftests/net/eh_limits.sh | 205 ++++++++++++
tools/testing/selftests/net/ext_hdr.py | 385 ++++++++++++++++++++++
tools/testing/selftests/net/proto_nums.py | 231 +++++++++++++
17 files changed, 1326 insertions(+), 28 deletions(-)
create mode 100755 tools/testing/selftests/net/eh_limits.py
create mode 100755 tools/testing/selftests/net/eh_limits.sh
create mode 100755 tools/testing/selftests/net/ext_hdr.py
create mode 100644 tools/testing/selftests/net/proto_nums.py
--
2.43.0
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH net-next v9 01/10] ipv6: Check of max HBH or DestOp sysctl is zero and drop if it is
2026-03-14 17:51 [PATCH net-next v9 00/10] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert
@ 2026-03-14 17:51 ` Tom Herbert
2026-03-14 17:51 ` [PATCH net-next v9 02/10] ipv6: Cleanup IPv6 TLV definitions Tom Herbert
` (9 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Tom Herbert @ 2026-03-14 17:51 UTC (permalink / raw)
To: davem, kuba, netdev, justin.iurman, willemdebruijn.kernel, pabeni
Cc: Tom Herbert, Justin Iurman
In IPv6 Destination options processing function check if
net->ipv6.sysctl.max_dst_opts_cnt is zero up front. If it is zero then
drop the packet since Destination Options processing is disabled.
Similarly, in IPv6 hop-by-hop options processing function check if
net->ipv6.sysctl.max_hbh_opts_cnt is zero up front. If it is zero then
drop the packet since Hop-by-Hop Options processing is disabled.
Signed-off-by: Tom Herbert <tom@herbertland.com>
Reviewed-by: Justin Iurman <justin.iurman@gmail.com>
---
net/ipv6/exthdrs.c | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c
index 5e3610a926cf..113efbc19abe 100644
--- a/net/ipv6/exthdrs.c
+++ b/net/ipv6/exthdrs.c
@@ -301,9 +301,11 @@ static int ipv6_destopt_rcv(struct sk_buff *skb)
#endif
struct dst_entry *dst = skb_dst(skb);
struct net *net = dev_net(skb->dev);
- int extlen;
+ int extlen, max_opts_cnt;
- if (!pskb_may_pull(skb, skb_transport_offset(skb) + 8) ||
+ max_opts_cnt = READ_ONCE(net->ipv6.sysctl.max_dst_opts_cnt);
+ if (!max_opts_cnt ||
+ !pskb_may_pull(skb, skb_transport_offset(skb) + 8) ||
!pskb_may_pull(skb, (skb_transport_offset(skb) +
((skb_transport_header(skb)[1] + 1) << 3)))) {
__IP6_INC_STATS(dev_net(dst_dev(dst)), idev,
@@ -322,8 +324,7 @@ static int ipv6_destopt_rcv(struct sk_buff *skb)
dstbuf = opt->dst1;
#endif
- if (ip6_parse_tlv(false, skb,
- READ_ONCE(net->ipv6.sysctl.max_dst_opts_cnt))) {
+ if (ip6_parse_tlv(false, skb, max_opts_cnt)) {
skb->transport_header += extlen;
opt = IP6CB(skb);
#if IS_ENABLED(CONFIG_IPV6_MIP6)
@@ -1038,7 +1039,7 @@ int ipv6_parse_hopopts(struct sk_buff *skb)
{
struct inet6_skb_parm *opt = IP6CB(skb);
struct net *net = dev_net(skb->dev);
- int extlen;
+ int extlen, max_opts_cnt;
/*
* skb_network_header(skb) is equal to skb->data, and
@@ -1046,7 +1047,9 @@ int ipv6_parse_hopopts(struct sk_buff *skb)
* sizeof(struct ipv6hdr) by definition of
* hop-by-hop options.
*/
- if (!pskb_may_pull(skb, sizeof(struct ipv6hdr) + 8) ||
+ max_opts_cnt = READ_ONCE(net->ipv6.sysctl.max_hbh_opts_cnt);
+ if (!max_opts_cnt ||
+ !pskb_may_pull(skb, sizeof(struct ipv6hdr) + 8) ||
!pskb_may_pull(skb, (sizeof(struct ipv6hdr) +
((skb_transport_header(skb)[1] + 1) << 3)))) {
fail_and_free:
@@ -1059,8 +1062,7 @@ int ipv6_parse_hopopts(struct sk_buff *skb)
goto fail_and_free;
opt->flags |= IP6SKB_HOPBYHOP;
- if (ip6_parse_tlv(true, skb,
- READ_ONCE(net->ipv6.sysctl.max_hbh_opts_cnt))) {
+ if (ip6_parse_tlv(true, skb, max_opts_cnt)) {
skb->transport_header += extlen;
opt = IP6CB(skb);
opt->nhoff = sizeof(struct ipv6hdr);
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v9 02/10] ipv6: Cleanup IPv6 TLV definitions
2026-03-14 17:51 [PATCH net-next v9 00/10] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert
2026-03-14 17:51 ` [PATCH net-next v9 01/10] ipv6: Check of max HBH or DestOp sysctl is zero and drop if it is Tom Herbert
@ 2026-03-14 17:51 ` Tom Herbert
2026-03-14 17:51 ` [PATCH net-next v9 03/10] ipv6: Add case for IPV6_TLV_TNL_ENCAP_LIMIT in EH TLV switch Tom Herbert
` (8 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Tom Herbert @ 2026-03-14 17:51 UTC (permalink / raw)
To: davem, kuba, netdev, justin.iurman, willemdebruijn.kernel, pabeni
Cc: Tom Herbert, Justin Iurman
Move IPV6_TLV_TNL_ENCAP_LIMIT to uapi/linux/in6.h to be with the rest
of the TLV definitions. Label each of the TLV definitions as to whether
they are a Hop-by-Hop option, Destination option, or both.
Signed-off-by: Tom Herbert <tom@herbertland.com>
Reviewed-by: Justin Iurman <justin.iurman@gmail.com>
---
include/uapi/linux/in6.h | 21 ++++++++++++++-------
include/uapi/linux/ip6_tunnel.h | 1 -
2 files changed, 14 insertions(+), 8 deletions(-)
diff --git a/include/uapi/linux/in6.h b/include/uapi/linux/in6.h
index 5a47339ef7d7..438283dc5fde 100644
--- a/include/uapi/linux/in6.h
+++ b/include/uapi/linux/in6.h
@@ -140,14 +140,21 @@ struct in6_flowlabel_req {
/*
* IPv6 TLV options.
+ *
+ * Hop-by-Hop and Destination options share the same number space.
+ * For each option below whether it is a Hop-by-Hop option or
+ * a Destination option is indicated by HBH or DestOpt.
*/
-#define IPV6_TLV_PAD1 0
-#define IPV6_TLV_PADN 1
-#define IPV6_TLV_ROUTERALERT 5
-#define IPV6_TLV_CALIPSO 7 /* RFC 5570 */
-#define IPV6_TLV_IOAM 49 /* RFC 9486 */
-#define IPV6_TLV_JUMBO 194
-#define IPV6_TLV_HAO 201 /* home address option */
+#define IPV6_TLV_PAD1 0 /* HBH or DestOpt */
+#define IPV6_TLV_PADN 1 /* HBH or DestOpt */
+#define IPV6_TLV_TNL_ENCAP_LIMIT 4 /* RFC 2473, DestOpt */
+#define IPV6_TLV_ROUTERALERT 5 /* HBH */
+#define IPV6_TLV_CALIPSO 7 /* RFC 5570, HBH */
+#define IPV6_TLV_IOAM 49 /* RFC 9486, HBH or Destopt
+ * IOAM sent and rcvd as HBH
+ */
+#define IPV6_TLV_JUMBO 194 /* HBH */
+#define IPV6_TLV_HAO 201 /* home address option, DestOpt */
/*
* IPV6 socket options
diff --git a/include/uapi/linux/ip6_tunnel.h b/include/uapi/linux/ip6_tunnel.h
index 85182a839d42..35af4d9c35fb 100644
--- a/include/uapi/linux/ip6_tunnel.h
+++ b/include/uapi/linux/ip6_tunnel.h
@@ -6,7 +6,6 @@
#include <linux/if.h> /* For IFNAMSIZ. */
#include <linux/in6.h> /* For struct in6_addr. */
-#define IPV6_TLV_TNL_ENCAP_LIMIT 4
#define IPV6_DEFAULT_TNL_ENCAP_LIMIT 4
/* don't add encapsulation limit if one isn't present in inner packet */
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v9 03/10] ipv6: Add case for IPV6_TLV_TNL_ENCAP_LIMIT in EH TLV switch
2026-03-14 17:51 [PATCH net-next v9 00/10] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert
2026-03-14 17:51 ` [PATCH net-next v9 01/10] ipv6: Check of max HBH or DestOp sysctl is zero and drop if it is Tom Herbert
2026-03-14 17:51 ` [PATCH net-next v9 02/10] ipv6: Cleanup IPv6 TLV definitions Tom Herbert
@ 2026-03-14 17:51 ` Tom Herbert
2026-03-14 17:51 ` [PATCH net-next v9 04/10] ipv6: Set HBH and DestOpt limits to 2 Tom Herbert
` (7 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Tom Herbert @ 2026-03-14 17:51 UTC (permalink / raw)
To: davem, kuba, netdev, justin.iurman, willemdebruijn.kernel, pabeni
Cc: Tom Herbert, Justin Iurman, Willem de Bruijn
IPV6_TLV_TNL_ENCAP_LIMIT is a recognized Destination option that is
processed in ip6_tunnel.c. Add a case for it in the switch in
ip6_parse_tlv so that it is recognized as a known option.
Also remove the unlikely around the check for max_count < 0 since the
default limits for HBH and Destination options can be less than zero.
Signed-off-by: Tom Herbert <tom@herbertland.com>
Reviewed-by: Justin Iurman <justin.iurman@gmail.com>
Reviewed-by: Willem de Bruijn <willemb@google.com>
---
net/ipv6/exthdrs.c | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c
index 113efbc19abe..e61d97621108 100644
--- a/net/ipv6/exthdrs.c
+++ b/net/ipv6/exthdrs.c
@@ -122,7 +122,7 @@ static bool ip6_parse_tlv(bool hopbyhop,
int tlv_count = 0;
int padlen = 0;
- if (unlikely(max_count < 0)) {
+ if (max_count < 0) {
disallow_unknowns = true;
max_count = -max_count;
}
@@ -202,6 +202,16 @@ static bool ip6_parse_tlv(bool hopbyhop,
if (!ipv6_dest_hao(skb, off))
return false;
break;
+#endif
+#if IS_ENABLED(CONFIG_IPV6_TUNNEL)
+ case IPV6_TLV_TNL_ENCAP_LIMIT:
+ /* The tunnel encapsulation option.
+ * This is handled in ip6_tunnel.c so
+ * we don't need to do anything here
+ * except to accept it as a recognized
+ * option
+ */
+ break;
#endif
default:
if (!ip6_tlvopt_unknown(skb, off,
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v9 04/10] ipv6: Set HBH and DestOpt limits to 2
2026-03-14 17:51 [PATCH net-next v9 00/10] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert
` (2 preceding siblings ...)
2026-03-14 17:51 ` [PATCH net-next v9 03/10] ipv6: Add case for IPV6_TLV_TNL_ENCAP_LIMIT in EH TLV switch Tom Herbert
@ 2026-03-14 17:51 ` Tom Herbert
2026-03-14 17:51 ` [PATCH net-next v9 05/10] ipv6: Document defaults for max_{dst|hbh}_opts_number sysctls Tom Herbert
` (6 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Tom Herbert @ 2026-03-14 17:51 UTC (permalink / raw)
To: davem, kuba, netdev, justin.iurman, willemdebruijn.kernel, pabeni
Cc: Tom Herbert, Justin Iurman
Set the default limits of non-padding Hop-by-Hop and Destination
options to 2. This means that if a packet contains more than two
non-padding options then it will be dropped. The previous limit
was 8, but that was too liberal considering that the stack only
support two Destination Options and the most Hop-by-Hop options
likely to ever be in the same packet are IOAM and JUMBO. The limit
can be increased via sysctl for private use and experimentation.
Signed-off-by: Tom Herbert <tom@herbertland.com>
Reviewed-by: Justin Iurman <justin.iurman@gmail.com>
---
include/net/ipv6.h | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/include/net/ipv6.h b/include/net/ipv6.h
index 0958cc5c6ec3..1b9819ad0c34 100644
--- a/include/net/ipv6.h
+++ b/include/net/ipv6.h
@@ -84,9 +84,12 @@ struct ip_tunnel_info;
* silently discarded.
*/
-/* Default limits for Hop-by-Hop and Destination options */
-#define IP6_DEFAULT_MAX_DST_OPTS_CNT 8
-#define IP6_DEFAULT_MAX_HBH_OPTS_CNT 8
+/* Default limits for Hop-by-Hop and Destination non-padding options. The
+ * default value for both is 2. This sets a limit at two non-padding options
+ * (see sysctl documentation)
+ */
+#define IP6_DEFAULT_MAX_DST_OPTS_CNT 2
+#define IP6_DEFAULT_MAX_HBH_OPTS_CNT 2
#define IP6_DEFAULT_MAX_DST_OPTS_LEN INT_MAX /* No limit */
#define IP6_DEFAULT_MAX_HBH_OPTS_LEN INT_MAX /* No limit */
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v9 05/10] ipv6: Document defaults for max_{dst|hbh}_opts_number sysctls
2026-03-14 17:51 [PATCH net-next v9 00/10] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert
` (3 preceding siblings ...)
2026-03-14 17:51 ` [PATCH net-next v9 04/10] ipv6: Set HBH and DestOpt limits to 2 Tom Herbert
@ 2026-03-14 17:51 ` Tom Herbert
2026-03-14 17:51 ` [PATCH net-next v9 06/10] ipv6: Enforce Extension Header ordering Tom Herbert
` (5 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Tom Herbert @ 2026-03-14 17:51 UTC (permalink / raw)
To: davem, kuba, netdev, justin.iurman, willemdebruijn.kernel, pabeni
Cc: Tom Herbert, Justin Iurman
In the descriptions of max_dst_opts_number and max_hbh_opts_number
sysctls add text about how a zero setting means that a packet with
any Destination or Hop-by-Hop options is dropped.
Report the defaults for max_dst_opts_number and max_hbh_opts_number
are 2 which means up to two options may be accepted.
Signed-off-by: Tom Herbert <tom@herbertland.com>
Reviewed-by: Justin Iurman <justin.iurman@gmail.com>
---
Documentation/networking/ip-sysctl.rst | 22 ++++++++++++++--------
1 file changed, 14 insertions(+), 8 deletions(-)
diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst
index 2e3a746fcc6d..453643c70c8d 100644
--- a/Documentation/networking/ip-sysctl.rst
+++ b/Documentation/networking/ip-sysctl.rst
@@ -2511,19 +2511,25 @@ mld_qrv - INTEGER
max_dst_opts_number - INTEGER
Maximum number of non-padding TLVs allowed in a Destination
- options extension header. If this value is less than zero
- then unknown options are disallowed and the number of known
- TLVs allowed is the absolute value of this number.
+ options extension header. If this value is zero then receive
+ Destination Options processing is disabled in which case packets
+ with the Destination Options extension header are dropped. If
+ this value is less than zero then unknown options are disallowed
+ and the number of known TLVs allowed is the absolute value of
+ this number.
- Default: 8
+ Default: 2
max_hbh_opts_number - INTEGER
Maximum number of non-padding TLVs allowed in a Hop-by-Hop
- options extension header. If this value is less than zero
- then unknown options are disallowed and the number of known
- TLVs allowed is the absolute value of this number.
+ options extension header. If this value is zero then receive
+ Hop-by-Hop Options processing is disabled in which case packets
+ with the Hop-by-Hop Options extension header are dropped.
+ If this value is less than zero then unknown options are disallowed
+ and the number of known TLVs allowed is the absolute value of this
+ number.
- Default: 8
+ Default: 2
max_dst_opts_length - INTEGER
Maximum length allowed for a Destination options extension
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v9 06/10] ipv6: Enforce Extension Header ordering
2026-03-14 17:51 [PATCH net-next v9 00/10] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert
` (4 preceding siblings ...)
2026-03-14 17:51 ` [PATCH net-next v9 05/10] ipv6: Document defaults for max_{dst|hbh}_opts_number sysctls Tom Herbert
@ 2026-03-14 17:51 ` Tom Herbert
2026-03-14 17:51 ` [PATCH net-next v9 07/10] ipv6: Document enforce_ext_hdr_order sysctl Tom Herbert
` (4 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Tom Herbert @ 2026-03-14 17:51 UTC (permalink / raw)
To: davem, kuba, netdev, justin.iurman, willemdebruijn.kernel, pabeni
Cc: Tom Herbert
RFC8200 highly recommends that different Extension Headers be sent in
a prescribed order and all Extension Header types occur at most once
in a packet with the exception of Destination Options that may
occur twice. This patch enforces the ordering be followed in received
packets.
The allowed order of Extension Headers is:
IPv6 header
Hop-by-Hop Options header
Destination Options before the Routing Header
Routing header
Fragment header
Authentication header
Encapsulating Security Payload header
Destination Options header
Upper-Layer header
Each Extension Header may be present only once in a packet expect
for Destination Options that may occur twice.
net.ipv6.enforce_ext_hdr_order is a sysctl to enable or disable
enforcement of extension Header order. If it is set to zero then
Extension Header order and number of occurrences is not checked
in receive processing (except for Hop-by-Hop Options that
must be the first Extension Header and can only occur once in
a packet).
Signed-off-by: Tom Herbert <tom@herbertland.com>
---
include/net/netns/ipv6.h | 1 +
include/net/protocol.h | 14 +++++++++++++
net/ipv6/af_inet6.c | 1 +
net/ipv6/exthdrs.c | 2 ++
net/ipv6/ip6_input.c | 42 ++++++++++++++++++++++++++++++++++++++
net/ipv6/reassembly.c | 1 +
net/ipv6/sysctl_net_ipv6.c | 7 +++++++
net/ipv6/xfrm6_protocol.c | 2 ++
8 files changed, 70 insertions(+)
diff --git a/include/net/netns/ipv6.h b/include/net/netns/ipv6.h
index 499e4288170f..af14cfa99c01 100644
--- a/include/net/netns/ipv6.h
+++ b/include/net/netns/ipv6.h
@@ -61,6 +61,7 @@ struct netns_sysctl_ipv6 {
u8 fib_notify_on_flag_change;
u8 icmpv6_error_anycast_as_unicast;
u8 icmpv6_errors_extension_mask;
+ u8 enforce_ext_hdr_order;
};
struct netns_ipv6 {
diff --git a/include/net/protocol.h b/include/net/protocol.h
index b2499f88f8f8..0f1676625570 100644
--- a/include/net/protocol.h
+++ b/include/net/protocol.h
@@ -50,6 +50,19 @@ struct net_protocol {
};
#if IS_ENABLED(CONFIG_IPV6)
+
+/* Order of extension headers as prescribed in RFC8200. The ordering and
+ * number of extension headers in a packet can be enforced in IPv6 receive
+ * processing.
+ */
+#define IPV6_EXT_HDR_ORDER_HOP BIT(0)
+#define IPV6_EXT_HDR_ORDER_DEST_BEFORE_RH BIT(1)
+#define IPV6_EXT_HDR_ORDER_ROUTING BIT(2)
+#define IPV6_EXT_HDR_ORDER_FRAGMENT BIT(3)
+#define IPV6_EXT_HDR_ORDER_AUTH BIT(4)
+#define IPV6_EXT_HDR_ORDER_ESP BIT(5)
+#define IPV6_EXT_HDR_ORDER_DEST BIT(6)
+
struct inet6_protocol {
int (*handler)(struct sk_buff *skb);
@@ -61,6 +74,7 @@ struct inet6_protocol {
unsigned int flags; /* INET6_PROTO_xxx */
u32 secret;
+ u32 ext_hdr_order;
};
#define INET6_PROTO_NOPOLICY 0x1
diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c
index eb9fff86baa1..c5387c119570 100644
--- a/net/ipv6/af_inet6.c
+++ b/net/ipv6/af_inet6.c
@@ -953,6 +953,7 @@ static int __net_init inet6_net_init(struct net *net)
net->ipv6.sysctl.max_dst_opts_len = IP6_DEFAULT_MAX_DST_OPTS_LEN;
net->ipv6.sysctl.max_hbh_opts_len = IP6_DEFAULT_MAX_HBH_OPTS_LEN;
net->ipv6.sysctl.fib_notify_on_flag_change = 0;
+ net->ipv6.sysctl.enforce_ext_hdr_order = 1;
atomic_set(&net->ipv6.fib6_sernum, 1);
net->ipv6.sysctl.ioam6_id = IOAM6_DEFAULT_ID;
diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c
index e61d97621108..a66a8339dc8d 100644
--- a/net/ipv6/exthdrs.c
+++ b/net/ipv6/exthdrs.c
@@ -845,11 +845,13 @@ static int ipv6_rthdr_rcv(struct sk_buff *skb)
static const struct inet6_protocol rthdr_protocol = {
.handler = ipv6_rthdr_rcv,
.flags = INET6_PROTO_NOPOLICY,
+ .ext_hdr_order = IPV6_EXT_HDR_ORDER_ROUTING,
};
static const struct inet6_protocol destopt_protocol = {
.handler = ipv6_destopt_rcv,
.flags = INET6_PROTO_NOPOLICY,
+ .ext_hdr_order = IPV6_EXT_HDR_ORDER_DEST,
};
static const struct inet6_protocol nodata_protocol = {
diff --git a/net/ipv6/ip6_input.c b/net/ipv6/ip6_input.c
index 967b07aeb683..1b22bac9a164 100644
--- a/net/ipv6/ip6_input.c
+++ b/net/ipv6/ip6_input.c
@@ -395,6 +395,27 @@ void ipv6_list_rcv(struct list_head *head, struct packet_type *pt,
ip6_sublist_rcv(&sublist, curr_dev, curr_net);
}
+static u32 check_dst_opts_before_rh(const struct inet6_protocol *ipprot,
+ u32 ext_hdrs)
+{
+ /* Check if Destination Options before the Routing Header are
+ * present.
+ */
+ if (ipprot->ext_hdr_order != IPV6_EXT_HDR_ORDER_ROUTING ||
+ !(ext_hdrs & IPV6_EXT_HDR_ORDER_DEST))
+ return ext_hdrs;
+
+ /* We have Destination Options before the Routing Header. Set
+ * the mask of received extension headers to reflect that. We promote
+ * the bit from indicating just Destination Options present to
+ * Destination Options before the Routing Header being present
+ */
+ ext_hdrs = (ext_hdrs & ~IPV6_EXT_HDR_ORDER_DEST) |
+ IPV6_EXT_HDR_ORDER_DEST_BEFORE_RH;
+
+ return ext_hdrs;
+}
+
INDIRECT_CALLABLE_DECLARE(int tcp_v6_rcv(struct sk_buff *));
/*
@@ -406,6 +427,7 @@ void ip6_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int nexthdr,
const struct inet6_protocol *ipprot;
struct inet6_dev *idev;
unsigned int nhoff;
+ u32 ext_hdrs = 0;
SKB_DR(reason);
bool raw;
@@ -467,6 +489,26 @@ void ip6_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int nexthdr,
goto discard;
}
}
+
+ if (ipprot->ext_hdr_order &&
+ READ_ONCE(net->ipv6.sysctl.enforce_ext_hdr_order)) {
+ /* The protocol is an extension header and EH ordering
+ * is being enforced. Discard packet if we've already
+ * seen this EH or one that is lower in the order list
+ */
+ if (ipprot->ext_hdr_order <= ext_hdrs) {
+ /* Check if there's Destination Options
+ * before the Routing Header
+ */
+ ext_hdrs = check_dst_opts_before_rh(ipprot,
+ ext_hdrs);
+ if (ipprot->ext_hdr_order <= ext_hdrs)
+ goto discard;
+ }
+
+ ext_hdrs |= ipprot->ext_hdr_order;
+ }
+
if (!(ipprot->flags & INET6_PROTO_NOPOLICY)) {
if (!xfrm6_policy_check(NULL, XFRM_POLICY_IN, skb)) {
SKB_DR_SET(reason, XFRM_POLICY);
diff --git a/net/ipv6/reassembly.c b/net/ipv6/reassembly.c
index 11f9144bebbe..e17876bf945a 100644
--- a/net/ipv6/reassembly.c
+++ b/net/ipv6/reassembly.c
@@ -420,6 +420,7 @@ static int ipv6_frag_rcv(struct sk_buff *skb)
static const struct inet6_protocol frag_protocol = {
.handler = ipv6_frag_rcv,
.flags = INET6_PROTO_NOPOLICY,
+ .ext_hdr_order = IPV6_EXT_HDR_ORDER_FRAGMENT,
};
#ifdef CONFIG_SYSCTL
diff --git a/net/ipv6/sysctl_net_ipv6.c b/net/ipv6/sysctl_net_ipv6.c
index d2cd33e2698d..543b6acdb11d 100644
--- a/net/ipv6/sysctl_net_ipv6.c
+++ b/net/ipv6/sysctl_net_ipv6.c
@@ -213,6 +213,13 @@ static struct ctl_table ipv6_table_template[] = {
.proc_handler = proc_doulongvec_minmax,
.extra2 = &ioam6_id_wide_max,
},
+ {
+ .procname = "enforce_ext_hdr_order",
+ .data = &init_net.ipv6.sysctl.enforce_ext_hdr_order,
+ .maxlen = sizeof(u8),
+ .mode = 0644,
+ .proc_handler = proc_dou8vec_minmax,
+ },
};
static struct ctl_table ipv6_rotable[] = {
diff --git a/net/ipv6/xfrm6_protocol.c b/net/ipv6/xfrm6_protocol.c
index ea2f805d3b01..5826edf67f64 100644
--- a/net/ipv6/xfrm6_protocol.c
+++ b/net/ipv6/xfrm6_protocol.c
@@ -197,12 +197,14 @@ static const struct inet6_protocol esp6_protocol = {
.handler = xfrm6_esp_rcv,
.err_handler = xfrm6_esp_err,
.flags = INET6_PROTO_NOPOLICY,
+ .ext_hdr_order = IPV6_EXT_HDR_ORDER_ESP,
};
static const struct inet6_protocol ah6_protocol = {
.handler = xfrm6_ah_rcv,
.err_handler = xfrm6_ah_err,
.flags = INET6_PROTO_NOPOLICY,
+ .ext_hdr_order = IPV6_EXT_HDR_ORDER_AUTH
};
static const struct inet6_protocol ipcomp6_protocol = {
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v9 07/10] ipv6: Document enforce_ext_hdr_order sysctl
2026-03-14 17:51 [PATCH net-next v9 00/10] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert
` (5 preceding siblings ...)
2026-03-14 17:51 ` [PATCH net-next v9 06/10] ipv6: Enforce Extension Header ordering Tom Herbert
@ 2026-03-14 17:51 ` Tom Herbert
2026-03-14 17:51 ` [PATCH net-next v9 08/10] test: Add proto_nums.py in networking selftests Tom Herbert
` (3 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Tom Herbert @ 2026-03-14 17:51 UTC (permalink / raw)
To: davem, kuba, netdev, justin.iurman, willemdebruijn.kernel, pabeni
Cc: Tom Herbert
Document the enforce_ext_hdr_order sysctl that controls whether
Extension Header order is enforced on receive.
Signed-off-by: Tom Herbert <tom@herbertland.com>
---
Documentation/networking/ip-sysctl.rst | 30 ++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst
index 453643c70c8d..fa0ed1936f59 100644
--- a/Documentation/networking/ip-sysctl.rst
+++ b/Documentation/networking/ip-sysctl.rst
@@ -2617,6 +2617,36 @@ ioam6_id_wide - LONG INTEGER
Default: 0xFFFFFFFFFFFFFF
+enforce_ext_hdr_order - BOOLEAN
+ Enforce recommended Extension Header ordering in RFC8200.
+ If the sysctl is set to 1 then the ordering is enforced in
+ received packets and each Extension Header may be present
+ at most once per packet (except for Destination Options that
+ may occur twice). If the sysctl is set to 0 then ordering is
+ not enforced and Extension Headers may be present in any
+ order and have any number of occurrences per packet (except
+ for Hop-by-Hop Options that must always be the first Extension
+ Header and occur at most once in a packet)).
+
+ The Extension Header order is:
+
+ IPv6 header
+ Hop-by-Hop Options header
+ Destination Options before the Routing header
+ Routing header
+ Fragment header
+ Authentication header
+ Encapsulating Security Payload header
+ Destination Options header
+ Upper-Layer header
+
+ Possible values:
+
+ - 0 (disabled)
+ - 1 (enabled)
+
+ Default: 1 (enabled)
+
IPv6 Fragmentation:
ip6frag_high_thresh - INTEGER
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v9 08/10] test: Add proto_nums.py in networking selftests
2026-03-14 17:51 [PATCH net-next v9 00/10] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert
` (6 preceding siblings ...)
2026-03-14 17:51 ` [PATCH net-next v9 07/10] ipv6: Document enforce_ext_hdr_order sysctl Tom Herbert
@ 2026-03-14 17:51 ` Tom Herbert
2026-03-17 15:22 ` Simon Horman
2026-03-14 17:51 ` [PATCH net-next v9 09/10] test: Add ext_hdr.py " Tom Herbert
` (2 subsequent siblings)
10 siblings, 1 reply; 15+ messages in thread
From: Tom Herbert @ 2026-03-14 17:51 UTC (permalink / raw)
To: davem, kuba, netdev, justin.iurman, willemdebruijn.kernel, pabeni
Cc: Tom Herbert
Add proto_nums.py that contains various python definitions of
common protocol constants
Signed-off-by: Tom Herbert <tom@herbertland.com>
---
tools/testing/selftests/net/proto_nums.py | 231 ++++++++++++++++++++++
1 file changed, 231 insertions(+)
create mode 100644 tools/testing/selftests/net/proto_nums.py
diff --git a/tools/testing/selftests/net/proto_nums.py b/tools/testing/selftests/net/proto_nums.py
new file mode 100644
index 000000000000..dc775164dacc
--- /dev/null
+++ b/tools/testing/selftests/net/proto_nums.py
@@ -0,0 +1,231 @@
+# SPDX-License-Identifier: GPL-2.0
+
+# Various protocol constant definitions
+
+from enum import Enum
+
+# IP protocol numbers
+class IP_Proto(Enum):
+ IP_PROTO_HOPOPT = 0
+ IP_PROTO_ICMP = 1
+ IP_PROTO_IGMP = 2
+ IP_PROTO_GGP = 3
+ IP_PROTO_IPv4 = 4
+ IP_PROTO_ST = 5
+ IP_PROTO_TCP = 6
+ IP_PROTO_CBT = 7
+ IP_PROTO_EGP = 8
+ IP_PROTO_IGP = 9
+ IP_PROTO_BBN_RCC_MON = 10
+ IP_PROTO_NVP_II = 11
+ IP_PROTO_PUP = 12
+ IP_PROTO_ARGUS = 13
+ IP_PROTO_EMCON = 14
+ IP_PROTO_XNET = 15
+ IP_PROTO_CHAOS = 16
+ IP_PROTO_UDP = 17
+ IP_PROTO_MUX = 18
+ IP_PROTO_DCN_MEAS = 19
+ IP_PROTO_HMP = 20
+ IP_PROTO_PRM = 21
+ IP_PROTO_XNS_IDP = 22
+ IP_PROTO_TRUNK_1 = 23
+ IP_PROTO_TRUNK_2 = 24
+ IP_PROTO_LEAF_1 = 25
+ IP_PROTO_LEAF_2 = 26
+ IP_PROTO_RDP = 27
+ IP_PROTO_IRTP = 28
+ IP_PROTO_ISO_TP4 = 29
+ IP_PROTO_NETBLT = 30
+ IP_PROTO_MFE_NSP = 31
+ IP_PROTO_MERIT_INP = 32
+ IP_PROTO_DCCP = 33
+ IP_PROTO_3PC = 34
+ IP_PROTO_IDPR = 35
+ IP_PROTO_XTP = 36
+ IP_PROTO_DDP = 37
+ IP_PROTO_IDPR_CMTP = 38
+ IP_PROTO_TP_PLUS_PLUS = 39
+ IP_PROTO_IL = 40
+ IP_PROTO_IPv6 = 41
+ IP_PROTO_SDRP = 42
+ IP_PROTO_IPv6_Route = 43
+ IP_PROTO_IPv6_Frag = 44
+ IP_PROTO_IDRP = 45
+ IP_PROTO_RSVP = 46
+ IP_PROTO_GRE = 47
+ IP_PROTO_DSR = 48
+ IP_PROTO_BNA = 49
+ IP_PROTO_ESP = 50
+ IP_PROTO_AH = 51
+ IP_PROTO_I_NLSP = 52
+ IP_PROTO_SWIPE = 53
+ IP_PROTO_NARP = 54
+ IP_PROTO_Min_IPv4 = 55
+ IP_PROTO_TLSP = 56
+ IP_PROTO_SKIP = 57
+ IP_PROTO_IPv6_ICMP = 58
+ IP_PROTO_IPv6_NoNxt = 59
+ IP_PROTO_IPv6_Opts = 60
+ IP_PROTO_any_1 = 61
+ IP_PROTO_CFTP = 62
+ IP_PROTO_any_2 = 63
+ IP_PROTO_SAT_EXPAK = 64
+ IP_PROTO_KRYPTOLAN = 65
+ IP_PROTO_RVD = 66
+ IP_PROTO_IPPC = 67
+ IP_PROTO_any_3 = 68
+ IP_PROTO_SAT_MON = 69
+ IP_PROTO_VISA = 70
+ IP_PROTO_IPCV = 71
+ IP_PROTO_CPNX = 72
+ IP_PROTO_CPHB = 73
+ IP_PROTO_WSN = 74
+ IP_PROTO_PVP = 75
+ IP_PROTO_BR_SAT_MON = 76
+ IP_PROTO_SUN_ND = 77
+ IP_PROTO_WB_MON = 78
+ IP_PROTO_WB_EXPAK = 79
+ IP_PROTO_ISO_IP = 80
+ IP_PROTO_VMTP = 81
+ IP_PROTO_SECURE_VMTP = 82
+ IP_PROTO_VINES = 83
+ IP_PROTO_IPTM = 84
+ IP_PROTO_NSFNET_IGP = 85
+ IP_PROTO_DGP = 86
+ IP_PROTO_TCF = 87
+ IP_PROTO_EIGRP = 88
+ IP_PROTO_OSPFIGP = 89
+ IP_PROTO_Sprite_RPC = 90
+ IP_PROTO_LARP = 91
+ IP_PROTO_MTP = 92
+ IP_PROTO_AX_25 = 93
+ IP_PROTO_IPIP = 94
+ IP_PROTO_MICP = 95
+ IP_PROTO_SCC_SP = 96
+ IP_PROTO_ETHERIP = 97
+ IP_PROTO_ENCAP = 98
+ IP_PROTO_any_4 = 99
+ IP_PROTO_GMTP = 100
+ IP_PROTO_IFMP = 101
+ IP_PROTO_PNNI = 102
+ IP_PROTO_PIM = 103
+ IP_PROTO_ARIS = 104
+ IP_PROTO_SCPS = 105
+ IP_PROTO_QNX = 106
+ IP_PROTO_A_N = 107
+ IP_PROTO_IPComp = 108
+ IP_PROTO_SNP = 109
+ IP_PROTO_Compaq_Peer = 110
+ IP_PROTO_IPX_in_IP = 111
+ IP_PROTO_VRRP = 112
+ IP_PROTO_PGM = 113
+ IP_PROTO_any_5 = 114
+ IP_PROTO_L2TP = 115
+ IP_PROTO_DDX = 116
+ IP_PROTO_IATP = 117
+ IP_PROTO_STP = 118
+ IP_PROTO_SRP = 119
+ IP_PROTO_UTI = 120
+ IP_PROTO_SMP = 121
+ IP_PROTO_SM = 122
+ IP_PROTO_PTP = 123
+ IP_PROTO_ISIS = 124
+ IP_PROTO_FIRE = 125
+ IP_PROTO_CRTP = 126
+ IP_PROTO_CRUDP = 127
+ IP_PROTO_SSCOPMCE = 128
+ IP_PROTO_IPLT = 129
+ IP_PROTO_SPS = 130
+ IP_PROTO_PIPE = 131
+ IP_PROTO_SCTP = 132
+ IP_PROTO_FC = 133
+ IP_PROTO_RSVP_E2E_IGNORE = 134
+ IP_PROTO_Mobility = 135
+ IP_PROTO_UDPLite = 136
+ IP_PROTO_MPLS_in_IP = 137
+ IP_PROTO_manet = 138
+ IP_PROTO_HIP = 139
+ IP_PROTO_Shim6 = 140
+ IP_PROTO_WESP = 141
+ IP_PROTO_ROHC = 142
+ IP_PROTO_Ethernet = 143
+ IP_PROTO_AGGFRAG = 144
+ IP_PROTO_NSH = 145
+ IP_PROTO_Homa = 146
+ IP_PROTO_BIT_EMU = 147
+
+# Hop-by-Hop and Destination Options numbers
+class HBHDst_Types(Enum):
+ HBHDST_TYPE_PAD1 = 0x0
+ HBHDST_TYPE_PADN = 0x1
+ HBHDST_TYPE_JUMBO = 0xc2
+ HBHDST_TYPE_RPL = 0x23
+ HBHDST_TYPE_RPL_DEPRECATED = 0x63
+ HBHDST_TYPE_TNL_ENCAP_LIMIT = 0x4
+ HBHDST_TYPE_ROUTER_ALERT = 0x5
+ HBHDST_TYPE_QUICK_START = 0x26
+ HBHDST_TYPE_CALIPSO = 0x7
+ HBHDST_TYPE_SMF_DPD = 0x8
+ HBHDST_TYPE_HAO = 0xc9
+ HBHDST_TYPE_ENDPOINT_ID = 0x8a
+ HBHDST_TYPE_ILNP_NONCE = 0x8b
+ HBHDST_TYPE_LINE_ID = 0x8c
+ HBHDST_TYPE_DEPRECATED = 0x4d
+ HBHDST_TYPE_MPL = 0x6d
+ HBHDST_TYPE_DFF = 0xee
+ HBHDST_TYPE_PDM = 0x0f
+ HBHDST_TYPE_MIN_PATH_MTU = 0x30
+ HBHDST_TYPE_IOAM_NO_CHNG = 0x11
+ HBHDST_TYPE_IOAM_CHNG = 0x31
+ HBHDST_TYPE_ALT_MARK = 0x12
+ HBHDST_TYPE_RFC3692_1 = 0x1e
+ HBHDST_TYPE_RFC3692_2 = 0x3e
+ HBHDST_TYPE_RFC3692_3 = 0x5e
+ HBHDST_TYPE_RFC3692_4 = 0x7e
+ HBHDST_TYPE_RFC3692_5 = 0x9e
+ HBHDST_TYPE_RFC3692_6 = 0xbe
+ HBHDST_TYPE_RFC3692_7 = 0xde
+
+# Routing header types
+class RoutingTypes(Enum):
+ ROUTING_TYPE_SRC_RT = 0
+ ROUTING_TYPE_NIMROD = 1
+ ROUTING_TYPE_2 = 2
+ ROUTING_TYPE_RPL = 3
+ ROUTING_TYPE_SRH = 4
+ ROUTING_TYPE_CRH16 = 5
+ ROUTING_TYPE_CRH32 = 6
+ ROUTING_TYPE_RFC3692_1 = 253
+ ROUTING_TYPE_RFC3692_2 = 254
+
+# Canonical extension header order
+class EH_Order(Enum):
+ IPV6_EXT_HDR_ORDER_HOP = 1 << 0
+ IPV6_EXT_HDR_ORDER_DEST_BEFORE_RH = 1 << 1
+ IPV6_EXT_HDR_ORDER_ROUTING = 1 << 2
+ IPV6_EXT_HDR_ORDER_FRAGMENT = 1 << 3
+ IPV6_EXT_HDR_ORDER_AUTH = 1 << 4
+ IPV6_EXT_HDR_ORDER_ESP = 1 << 5
+ IPV6_EXT_HDR_ORDER_DEST = 1 << 6
+
+# ICMPv6 types
+class ICMP6_Type(Enum):
+ ICMPV6_DEST_UNREACH = 1
+ ICMPV6_PKT_TOOBIG = 2
+ ICMPV6_TIME_EXCEED = 3
+ ICMPV6_PARAMPROB = 4
+ ICMPV6_ECHO_REQUEST = 128
+ ICMPV6_ECHO_REPLY = 129
+ ICMPV6_MGM_QUERY = 130
+ ICMPV6_MGM_REPORT = 131
+ ICMPV6_MGM_REDUCTION = 132
+ ICMPV6_NI_QUERY = 139
+ ICMPV6_NI_REPLY = 140
+ ICMPV6_MLD2_REPORT = 143
+ ICMPV6_DHAAD_REQUEST = 144
+ ICMPV6_DHAAD_REPLY = 145
+ ICMPV6_MOBILE_PREFIX_SOL = 146
+ ICMPV6_MOBILE_PREFIX_ADV = 147
+ ICMPV6_MRDISC_ADV = 151
+ ICMPV6_MRDISC_SOL = 152
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v9 09/10] test: Add ext_hdr.py in networking selftests
2026-03-14 17:51 [PATCH net-next v9 00/10] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert
` (7 preceding siblings ...)
2026-03-14 17:51 ` [PATCH net-next v9 08/10] test: Add proto_nums.py in networking selftests Tom Herbert
@ 2026-03-14 17:51 ` Tom Herbert
2026-03-17 15:24 ` [net-next,v9,09/10] " Simon Horman
2026-03-14 17:51 ` [PATCH net-next v9 10/10] test: Add networking selftest for eh limits Tom Herbert
2026-03-14 17:58 ` [PATCH net-next v9 00/10] ipv6: Address ext hdr DoS vulnerabilities Jakub Kicinski
10 siblings, 1 reply; 15+ messages in thread
From: Tom Herbert @ 2026-03-14 17:51 UTC (permalink / raw)
To: davem, kuba, netdev, justin.iurman, willemdebruijn.kernel, pabeni
Cc: Tom Herbert
Add ext_hdr.py that contains various Extension Header format definitions
and related helper functions.
This includes the Make_EH_Chain function that creates an Extension
Header chain based on an input list. The input list has the format:
[(<type>, <args>), (<type>, <args>), ... (<type>, <args>)]
where <type> is "H" for Hop-by-Hop Options, "D" for Destination
Options, "R" for Routing Header, "F" for Fragment header, "A" for
Authentication Header, and "E" for ESP header.
<args> is specific to the type of extension header. For Hop-by-Hop
and Destination Options <args> is a list of options in the format:
[(<opt_type>, <opt_length>), (<opt_type>, <opt_length>), ...
(<opt_type>, <opt_length>)]
For the Routing Header, <args> is a list of SIDs in the format:
[IPv6_address, IPv6_address, ... IPv6_address]
For the Fragment Header, <args> is the identifier number
Authentication and ESP are not currently supported by Make_EH_Chain
Signed-off-by: Tom Herbert <tom@herbertland.com>
---
tools/testing/selftests/net/ext_hdr.py | 385 +++++++++++++++++++++++++
1 file changed, 385 insertions(+)
create mode 100755 tools/testing/selftests/net/ext_hdr.py
diff --git a/tools/testing/selftests/net/ext_hdr.py b/tools/testing/selftests/net/ext_hdr.py
new file mode 100755
index 000000000000..bfb7da4a7c88
--- /dev/null
+++ b/tools/testing/selftests/net/ext_hdr.py
@@ -0,0 +1,385 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+# Helper functions for creating extension headers using scapy
+
+import ctypes
+import shlex
+import socket
+import sys
+import subprocess
+import scapy
+import proto_nums
+
+
+# Read a sysctl
+def sysctl_read(name):
+ try:
+ # shlex.split helps handle arguments correctly
+ command = shlex.split(f"sysctl -n {name}")
+ # Use check=True to raise an exception if the command fails
+ result = subprocess.run(command, check=True,
+ capture_output=True, text=True)
+ value = result.stdout.strip()
+ except subprocess.CalledProcessError as ex:
+ print(f"Error reading sysctl: {ex.stderr}")
+ except FileNotFoundError:
+ print("The 'sysctl' command was not found. "
+ "Check your system's PATH.")
+
+ return int(value)
+
+# Common definitions for Destination and Hop-by-Hop options
+
+# Common Destination and Hop-by-Hop Options header
+class HbhDstOptions(ctypes.BigEndianStructure):
+ _pack_ = 1
+ _fields_ = [
+ ("next_hdr", ctypes.c_uint8),
+ ("hdr_ext_len", ctypes.c_uint8)
+ ]
+
+ def __init__(self, next_hdr, length):
+ self.next_hdr = next_hdr
+ self.hdr_ext_len = length
+
+# Common single Destination and Hop-by-Hop Option header
+class HbhDstOption(ctypes.BigEndianStructure):
+ _pack_ = 1
+ _fields_ = [
+ ("opt_type", ctypes.c_uint8),
+ ("opt_data_len", ctypes.c_uint8),
+ ]
+
+ def __init__(self, opt_type, length):
+ self.opt_type = opt_type
+ self.opt_data_len = length
+
+# Make PAD1 option
+def make_hbh_dst_option_pad1():
+ opt_bytes = bytearray(1)
+ opt_bytes[0] = proto_nums.HBHDst_Types.HBHDST_TYPE_PAD1.value
+ return (scapy.all.Raw(opt_bytes), 1)
+
+# Make a full DestOpt or HBH Option with some length
+def make_hbh_dst_option_with_data(opt_type, opt_len):
+ hdr = scapy.all.Raw(load=HbhDstOption(opt_type, opt_len))
+ opt_bytes = scapy.all.Raw(bytearray(opt_len))
+ allhdr = hdr/opt_bytes
+ return (scapy.all.Raw(allhdr), 2 + opt_len)
+
+# Make PADN option
+def make_hbh_dst_option_pad_n(opt_len):
+ return make_hbh_dst_option_with_data(
+ proto_nums.HBHDst_Types.HBHDST_TYPE_PADN.value, opt_len)
+
+# Make a Destination or Hop-by-Hop Options list. Input is list of pairs as
+# (type, length). Option data is set to zeroes.
+#
+# Return value is (hdr, len, outcome) where hdr is the raw bytes and length
+# is the length of the header including two bytes for the common extension
+# header (the returned header does not include the two byte common header).
+# outcome is True or False depending on whether the options are expected to
+# exceed a sysctl limit and would be dropped
+def make_hbh_dst_options_list(opt_list, max_cnt, max_len):
+ hdr = scapy.all.Raw()
+ eh_len = 0
+
+ num_non_padding_opts = 0
+ max_consect_pad_len = 0
+
+ consect_padlen = 0
+
+ # Create the set of options
+ for opt_type, jlen in opt_list:
+ if opt_type == proto_nums.HBHDst_Types.HBHDST_TYPE_PAD1.value:
+ # PAD1 is a special case
+ pair = make_hbh_dst_option_pad1()
+ consect_padlen += pair[1]
+ else:
+ pair = make_hbh_dst_option_with_data(opt_type, jlen)
+
+ if opt_type == proto_nums.HBHDst_Types.HBHDST_TYPE_PADN.value:
+ consect_padlen += pair[1]
+ else:
+ if consect_padlen > max_consect_pad_len:
+ max_consect_pad_len = consect_padlen
+ consect_padlen = 0
+ num_non_padding_opts += 1
+
+ # Append the option, add to cumulative length
+ hdr = hdr/pair[0]
+ eh_len += pair[1]
+
+ # Add two to length to account for two byte extension header
+ eh_len += 2
+
+ if eh_len % 8 != 0:
+ # The extension header length must be a multiple of eight bytes.
+ # If we're short add a padding option
+ plen = 8 - (eh_len % 8)
+ if plen == 1:
+ pair = make_hbh_dst_option_pad1()
+ else:
+ pair = make_hbh_dst_option_pad_n(plen - 2)
+
+ consect_padlen += pair[1]
+ hdr = hdr/pair[0]
+ eh_len += plen
+
+ if consect_padlen > max_consect_pad_len:
+ max_consect_pad_len = consect_padlen
+
+ outcome = True
+ if num_non_padding_opts > max_cnt:
+ # The number of options we created is greater then the sysctl
+ # limit, so we expect the packet to be dropped
+ outcome = False
+ if eh_len > max_len:
+ # The length of the extension is greater then the sysctl limit,
+ # so we expect the packet to be dropped
+ outcome = False
+ if max_consect_pad_len > 7:
+ # The maximum consecutive number of bytes of padding is
+ # greater than seven, so we expect the packet to be dropped
+ outcome = False
+
+ return (hdr, eh_len - 2, outcome)
+
+# Make a full Hop-by-Hop or Destination Options header
+def make_full_hbh_dst_options_list(next_hdr, opt_list, max_cnt, max_len):
+ pair = make_hbh_dst_options_list(opt_list, max_cnt, max_len)
+ opt_len = pair[1] + 2
+
+ opts = HbhDstOptions(next_hdr, (opt_len - 1) // 8)
+ hdr = scapy.all.Raw(load=opts)/pair[0]
+
+ return (hdr, opt_len, pair[2])
+
+# Routing header definitions
+
+# Base Routing Header
+class RoutingHdr(ctypes.BigEndianStructure):
+ _pack_ = 1
+ _fields_ = [
+ ("next_hdr", ctypes.c_uint8),
+ ("hdr_ext_len", ctypes.c_uint8),
+ ("routing_type", ctypes.c_uint8),
+ ("segments_left", ctypes.c_uint8)
+ ]
+
+# SRv6 Routing Header
+class Srv6RoutingHdr(ctypes.BigEndianStructure):
+ _pack_ = 1
+ _fields_ = [
+ ("rh", RoutingHdr),
+ ("last_entry", ctypes.c_uint8),
+ ("flags", ctypes.c_uint8),
+ ("tags", ctypes.c_uint16),
+ # Variable list
+ # TLV options
+ ]
+
+ def __init__(self, next_hdr, hdr_ext_len, segments_left, last_entry):
+ self.rh.next_hdr = next_hdr
+ self.rh.hdr_ext_len = hdr_ext_len
+ self.rh.routing_type = proto_nums.RoutingTypes.ROUTING_TYPE_SRH.value
+ self.rh.segments_left = segments_left
+
+ self.last_entry = last_entry
+
+# Make an SRv6 Routing Header (with no segments left)
+def make_srv6_routing_hdr(next_hdr, sids):
+
+ bhdr = scapy.all.Raw()
+ num_sids = 0
+
+ # Set up each SID in the list
+ for sid in sids:
+ sid_bytes = socket.inet_pton(socket.AF_INET6, sid)
+ bhdr = bhdr/scapy.all.Raw(load=sid_bytes)
+ num_sids += 1
+
+ eh_len = num_sids * 16
+
+ hdr = Srv6RoutingHdr(next_hdr, eh_len // 8, 0, num_sids - 1)
+
+ bhdr = scapy.all.Raw(load=hdr)/bhdr
+
+ return (bhdr, eh_len + 8, True)
+
+# Fragment header
+
+# Basic Fragment Header
+class FragmentHdr(ctypes.BigEndianStructure):
+ _pack_ = 1
+ _fields_ = [
+ ("next_hdr", ctypes.c_uint8),
+ ("rsvd", ctypes.c_uint8),
+ ("fragment_offset", ctypes.c_uint16, 13),
+ ("rsvd2", ctypes.c_uint16, 2),
+ ("more", ctypes.c_uint16, 1),
+ ("identfication", ctypes.c_uint32),
+ ]
+
+ def __init__(self, next_hdr, fragment_offset, more, ident):
+ self.next_hdr = next_hdr
+ self.fragment_offset = fragment_offset
+ self.more = more
+ self.identfication = ident
+
+# Make a raw fragment header
+def make_fragment_hdr(next_hdr, fragment_offset, more, ident):
+ hdr = FragmentHdr(next_hdr, fragment_offset, more, ident)
+
+ return (scapy.all.Raw(load=hdr), 8, True)
+
+# Authentication Header
+
+# Base Authentication Header
+class AuthHdr(ctypes.BigEndianStructure):
+ _pack_ = 1
+ _fields_ = [
+ ("next_hdr", ctypes.c_uint8),
+ ("payload_len", ctypes.c_uint8),
+ ("spi", ctypes.c_uint32)
+ # ICV is variable length
+ ]
+
+ def __init__(self, next_hdr, payload_len, spi):
+ self.next_hdr = next_hdr
+ self.payload_len = payload_len
+ self.spi = spi
+
+# ESP
+
+# Base ESP header
+class EspHdr(ctypes.BigEndianStructure):
+ _pack_ = 1
+ _fields_ = [
+ ("spi", ctypes.c_uint32),
+ ("seqno", ctypes.c_uint32)
+ # Payload data + padding
+ # ICV is variable length
+ ]
+
+ def __init__(self, spi, seqno):
+ self.spi = spi
+ self.seqno = seqno
+
+# Check if EH list is out of order
+def check_eh_order(eh_list):
+ # OOO is okay if sysctl is not enforcing in order
+ do_check = sysctl_read("net.ipv6.enforce_ext_hdr_order")
+
+ seen = 0
+ for eh_type, _args in eh_list:
+ if eh_type == "H":
+ order = proto_nums.EH_Order.IPV6_EXT_HDR_ORDER_HOP.value
+ elif eh_type == "D":
+ if (seen &
+ proto_nums.EH_Order.IPV6_EXT_HDR_ORDER_ROUTING.value):
+ order = proto_nums.EH_Order.IPV6_EXT_HDR_ORDER_DEST.value
+ else:
+ order = proto_nums.EH_Order.IPV6_EXT_HDR_ORDER_DEST_BEFORE_RH.value
+ elif eh_type == "R":
+ order = proto_nums.EH_Order.IPV6_EXT_HDR_ORDER_ROUTING.value
+ elif eh_type == "F":
+ order = proto_nums.EH_Order.IPV6_EXT_HDR_ORDER_FRAGMENT.value
+ if seen & order != 0:
+ # Linux stack doesn't allow more than one
+ # Fragment Header in a packet
+ return False
+ elif eh_type == "A":
+ order = proto_nums.EH_Order.IPV6_EXT_HDR_ORDER_AUTH.value
+ elif eh_type == "E":
+ order = proto_nums.EH_Order.IPV6_EXT_HDR_ORDER_ESP.value
+
+ if (do_check and seen >= order):
+ return False
+ seen |= order
+
+ return True
+
+# Compute the next headers for an EH chain. Returns a new list of EHs
+# with the next header attached to each element
+def compute_next_hdrs(next_hdr, eh_list):
+ nlist = []
+
+ # Run through the list in reverse and set the next header up for each
+ # enty
+ for eh_type, args in reversed(eh_list):
+ entry = (eh_type, args, next_hdr)
+ nlist.insert(0, entry)
+ if eh_type == "H":
+ next_hdr = proto_nums.IP_Proto.IP_PROTO_HOPOPT.value
+ elif eh_type == "D":
+ next_hdr = proto_nums.IP_Proto.IP_PROTO_IPv6_Opts.value
+ elif eh_type == "R":
+ next_hdr = proto_nums.IP_Proto.IP_PROTO_IPv6_Route.value
+ elif eh_type == "F":
+ next_hdr = proto_nums.IP_Proto.IP_PROTO_IPv6_Frag.value
+ elif eh_type == "A":
+ next_hdr = proto_nums.IP_Proto.IP_PROTO_AH.value
+ elif eh_type == "E":
+ next_hdr = proto_nums.IP_Proto.IP_PROTO_ESP.value
+
+ return nlist, next_hdr
+
+# Make an extension header chain from a list
+# The list contains a set of pairs in the form (<eh_type>, <args>)
+# <eh_type> is:
+# "H"-- Hop-by-Hop Options
+# "D"-- Destination Options
+# "R"-- Routing Header
+# "F"-- Fragment Header
+# "A"-- Authentication Header
+# "E"-- ESP
+#
+# <args> is specific to EH type
+def make_eh_chain(next_hdr, eh_list):
+ nlist = []
+
+ # Run through the list in reverse and set the next header up for each
+ # enty
+ nlist, next_hdr = compute_next_hdrs(next_hdr, eh_list)
+
+ outcome = check_eh_order(eh_list)
+
+ hdr = scapy.all.Raw()
+ eh_len = 0
+
+ for eh_type, args, nnext_hdr in reversed(nlist):
+ if eh_type == "H":
+ # args is a list of (<opt_type>, <opt_len>) pairs
+ pair = make_full_hbh_dst_options_list(nnext_hdr, args,
+ sysctl_read("net.ipv6.max_hbh_opts_number"),
+ sysctl_read("net.ipv6.max_hbh_length"))
+ elif eh_type == "D":
+ # args is a list of (<opt_type>, <opt_len>) pairs
+ pair = make_full_hbh_dst_options_list(nnext_hdr, args,
+ sysctl_read("net.ipv6.max_dst_opts_number"),
+ sysctl_read("net.ipv6.max_dst_opts_length"))
+ elif eh_type == "R":
+ # args is a list of IPv6 address string
+ pair = make_srv6_routing_hdr(nnext_hdr, args)
+ elif eh_type == "F":
+ # Arg is (<identifier>)
+ pair = make_fragment_hdr(nnext_hdr, 0, False, args)
+ elif eh_type == "A":
+ print("Auth type not supported for test")
+ sys.exit(1)
+ elif eh_type == "E":
+ print("ESP type not supported for test")
+ sys.exit(1)
+ else:
+ print("Unknown EH type character")
+ sys.exit(1)
+
+ hdr = pair[0]/hdr
+ eh_len += pair[1]
+
+ if pair[2] is False:
+ outcome = False
+
+ return (hdr, eh_len, next_hdr, outcome)
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v9 10/10] test: Add networking selftest for eh limits
2026-03-14 17:51 [PATCH net-next v9 00/10] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert
` (8 preceding siblings ...)
2026-03-14 17:51 ` [PATCH net-next v9 09/10] test: Add ext_hdr.py " Tom Herbert
@ 2026-03-14 17:51 ` Tom Herbert
2026-03-17 15:32 ` Simon Horman
2026-03-14 17:58 ` [PATCH net-next v9 00/10] ipv6: Address ext hdr DoS vulnerabilities Jakub Kicinski
10 siblings, 1 reply; 15+ messages in thread
From: Tom Herbert @ 2026-03-14 17:51 UTC (permalink / raw)
To: davem, kuba, netdev, justin.iurman, willemdebruijn.kernel, pabeni
Cc: Tom Herbert
Add a networking selftest for Extension Header limits. The
limits to test are in systcls:
net.ipv6.enforce_ext_hdr_order
net.ipv6.max_dst_opts_number
net.ipv6.max_hbh_opts_number
net.ipv6.max_hbh_length
net.ipv6.max_dst_opts_length
The basic idea of the test is to fabricate ICMPv6 Echo Request
packets with various combinations of Extension Headers. The packets
are sent to a host in another namespace. If a an ICMPv6 Echo Reply
is received then the packet wasn't dropped due to a limit being
exceeded, and if it was dropped then we assume that a limit was
exceeded. For each test packet we derive an expectation as to
whether the packet will be dropped or not. Test success depends
on whether our expectation is matched. i.e. if we expect a reply
then the test succeeds if we see a reply, and if we don't expect a
reply then the test succeeds if we don't see a reply.
The test is divided into a frontend bash script (eh_limits.sh) and a
backend Python script (eh_limits.py).
The frontend sets up two network namespaces with IPv6 addresses
configured on veth's. We then invoke the backend to send the
test packets. This first pass is done with default sysctl settings.
On a second pass we change the various sysctl settings and run
again.
The backend runs through the various test cases described in the
Make_Test_Packets function. This function calls Make_Packet for
a test case where arguments provide the Extension Header chain to
be tested. The Run_Test function loops through the various packets
and tests if a reply is received versus the expectation. If a test
case fails then an error status is returned by the backend.
The backend script can also be run with the "-w <pcap_file>" to
write the created packets to a pcap file instead of running the
test.
Signed-off-by: Tom Herbert <tom@herbertland.com>
---
tools/testing/selftests/net/Makefile | 1 +
tools/testing/selftests/net/eh_limits.py | 349 +++++++++++++++++++++++
tools/testing/selftests/net/eh_limits.sh | 205 +++++++++++++
3 files changed, 555 insertions(+)
create mode 100755 tools/testing/selftests/net/eh_limits.py
create mode 100755 tools/testing/selftests/net/eh_limits.sh
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 6bced3ed798b..bd07d5c28905 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -25,6 +25,7 @@ TEST_PROGS := \
cmsg_time.sh \
double_udp_encap.sh \
drop_monitor_tests.sh \
+ eh_limits.sh \
fcnal-ipv4.sh \
fcnal-ipv6.sh \
fcnal-other.sh \
diff --git a/tools/testing/selftests/net/eh_limits.py b/tools/testing/selftests/net/eh_limits.py
new file mode 100755
index 000000000000..46a460b9149e
--- /dev/null
+++ b/tools/testing/selftests/net/eh_limits.py
@@ -0,0 +1,349 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+# Test of extension header limits
+
+import getopt
+import struct
+import sys
+import socket
+import scapy.all
+import proto_nums
+import ext_hdr
+
+# Constants
+
+VERBOSE = False
+SOURCE_MAC = "00:11:22:33:44:55"
+DESTINATION_MAC = "AA:BB:CC:DD:EE:FF"
+SOURCE_IP = "2001:db8::7"
+DESTINATION_IP = "2001:db8::8"
+PACKET_LIST = []
+PCAP_OUT=""
+GLOB_IDENT = 1111
+ETHER_FRAME = scapy.all.Raw()
+WITH_ETH = False
+
+# Parse command line options
+def cli_args():
+ global VERBOSE, SOURCE_MAC, DESTINATION_MAC, SOURCE_IP
+ global DESTINATION_IP, PCAP_OUT
+
+ args = sys.argv[1:]
+
+ try:
+ opts, args = getopt.getopt(args, "vw:",
+ ["verbose", "src_eth=", "dst_eth", "src_ip=",
+ "dst_ip=", "pcap_out="])
+ except getopt.GetoptError as err:
+ # Print error message and exit
+ print(err)
+ sys.exit(2)
+
+ for opt, arg in opts:
+ if opt in ("-v", "--verbose"):
+ VERBOSE = True
+ elif opt in ("--src_eth"):
+ SOURCE_MAC = arg
+ elif opt in ("--dst_eth"):
+ DESTINATION_MAC = arg
+ elif opt in ("--src_ip"):
+ SOURCE_IP = arg
+ elif opt in ("--dst_ip"):
+ DESTINATION_IP = arg
+ elif opt in ("-w", "--pcap_out"):
+ PCAP_OUT = arg
+
+# Make an ICMP echo request packet with the requested Extension Header chain
+def make_packet(text_name, eh_list):
+ global GLOB_IDENT
+
+ hdr = scapy.all.Raw()
+ plen = 0
+
+ hdr = scapy.all.ICMPv6EchoRequest(id=GLOB_IDENT)/hdr
+ plen += 8
+
+ pair = ext_hdr.make_eh_chain(
+ proto_nums.IP_Proto.IP_PROTO_IPv6_ICMP.value, eh_list)
+ hdr = pair[0]/hdr
+ plen += pair[1]
+
+ ipv6_pkt = scapy.all.IPv6(src=SOURCE_IP, dst=DESTINATION_IP,
+ nh=pair[2], plen=plen)
+ hdr = ipv6_pkt / hdr
+ plen += 40
+
+ if WITH_ETH:
+ hdr = ETHER_FRAME/hdr
+ plen += 14
+
+ PACKET_LIST.append((hdr, plen, GLOB_IDENT, pair[3], text_name))
+ GLOB_IDENT += 1
+
+# Write a pacp file with all the created packets
+def write_pcap(pcap_out):
+ global ETHER_FRAME, WITH_ETH
+
+ ETHER_FRAME = scapy.all.Ether(src=SOURCE_MAC, dst=DESTINATION_MAC,
+ type=0x86DD)
+ WITH_ETH = True
+
+ packets=[]
+ for packet in PACKET_LIST:
+ packets.append(packet[0])
+
+ scapy.all.wrpcap(pcap_out, packets)
+
+def process_return(recvd_it, packet):
+ if VERBOSE:
+ if recvd_it:
+ if packet[3]:
+ print(f"TEST: {packet[4]}: Received as expected")
+ else:
+ print(f"TEST: {packet[4]}: Unexpected receive")
+ else:
+ if packet[3]:
+ print(f"TEST: {packet[4]}: Didn't receive, "
+ "but receive expected")
+ else:
+ print(f"TEST: {packet[4]}: Didn't receive as expected")
+
+ if (recvd_it and packet[3] is not True):
+ # We got a reply but weren't expecting one
+ print(f"FAIL: Receive was unexpected for {packet[4]}")
+ return False
+
+ if (not recvd_it and packet[3]):
+ # We didn't get a reply but weret expecting one
+ print(f"FAIL: Expected to receive for {packet[4]}")
+ return False
+
+ return True
+
+# Run ping test
+def run_test():
+ # Open raw ICMP socket
+ try:
+ sock = socket.socket(socket.AF_INET6, socket.SOCK_RAW,
+ socket.IPPROTO_ICMPV6)
+ except PermissionError:
+ print("This script requires root privileges.")
+ return 2
+
+ # Bind to interface by its IP address
+ sock.bind((SOURCE_IP, 0))
+
+ # Run through each packet
+ for packet in PACKET_LIST:
+ # Send packet
+ scapy.all.send(packet[0], verbose=False)
+
+ sock.settimeout(0.100)
+ recvd_it = False
+
+ # Try to get ICMP echo reply
+ try:
+ while not recvd_it:
+ rpacket, _addr = sock.recvfrom(1024)
+ icmp_type = rpacket[0]
+ identifier = struct.unpack(">H", rpacket[4:6])
+ if (icmp_type ==
+ proto_nums.ICMP6_Type.ICMPV6_ECHO_REPLY.value and
+ identifier[0] == packet[2]):
+ recvd_it = True
+
+ except socket.timeout:
+ pass
+
+ process_return(recvd_it, packet)
+
+ return 0
+
+# Make packets for various test cases
+def make_test_packets():
+ # Two non-padding options in HBH and DestOpt, should succeed
+ # with default sysctls
+ make_packet("Two non-padding options in HBH and DestOpts",
+ [
+ ("H", [(11, 4), (0, 0), (0, 0), (12, 3)]),
+ ("D", [(1, 4), (12, 3)]),
+ ("R", [ "888::1", "9999::1"]),
+ ("F", (0x89abcdef)),
+ ("D", [(1, 4), (12, 3)]),
+ ])
+
+ # Big destination option, should fail when
+ # net.ipv6.max_dst_opts_length equals 64
+ make_packet("Big destination option",
+ [
+ ("H", [(11, 4), (0, 0), (0, 0), (12, 3)]),
+ ("D", [(1, 4), (12, 255)]),
+ ("R", [ "888::1", "9999::1"]),
+ ("F", (0x89abcdef)),
+ ])
+
+ # Almost Big HBH option should succeed when
+ # net.ipv6.max_hbh_length equals 64
+ make_packet("Almost Big HBH option",
+ [
+ ("H", [(11, 53), (1, 0), (12, 3)]),
+ ("D", [(1, 4), (12, 1)]),
+ ("R", [ "888::1", "9999::1"]),
+ ("F", (0x89abcdef)),
+ ])
+
+ # Big Hop-by-Hop option, should fail when
+ # net.ipv6.max_hbh_length equals 64
+ make_packet("Big HBH option",
+ [
+ ("H", [(11, 53), (1, 0), (0, 0), (12, 3)]),
+ ("D", [(1, 4), (12, 1)]),
+ ("R", [ "888::1", "9999::1"]),
+ ("F", (0x89abcdef)),
+ ])
+
+ # Too much HBH padding, should always fail
+ make_packet("Too much HBH padding",
+ [
+ ("H", [(12, 3), (1, 8)]),
+ ("D", [(1, 4), (12, 3)]),
+ ("R", [ "888::1", "9999::1"]),
+ ("F", (0x89abcdef)),
+ ("D", [(1, 4), (12, 3)]),
+ ])
+
+ # Too much DestOpt padding, should always fail
+ make_packet("Too much DestOpt padding",
+ [
+ ("H", [(12, 3), (1, 8)]),
+ ("D", [(1, 4), (12, 3)]),
+ ("R", [ "888::1", "9999::1"]),
+ ("F", (0x89abcdef)),
+ ("D", [(1, 4), (12, 3), (0, 0), (1, 6), (0, 0), (12, 3)]),
+ ])
+
+ # Too much DestOpt padding, should always fail
+ make_packet("Too much DestOpt padding #2",
+ [
+ ("H", [(12, 3)]),
+ ("D", [(1, 4), (12, 3)]),
+ ("R", [ "888::1", "9999::1"]),
+ ("F", (0x89abcdef)),
+ ("D", [(1, 4), (12, 3), (0, 0), (0, 0), (0, 0), (0, 0),
+ (0, 0), (0, 0), (0, 0), (0, 0), (12, 3)]),
+ ])
+
+ # Too much DestOpt padding, should always fail
+
+ make_packet("Too much DestOpt padding #3",
+ [
+ ("D", [(0, 0), (0, 0), (0, 0), (0, 0),
+ (0, 0), (0, 0), (0, 0), (0, 0)]),
+ ])
+
+ # Almost too much DestOpt padding, should succeed with default
+ # sysctl settings
+ make_packet("Almost too much DestOpt padding #2",
+ [
+ ("H", [(12, 3)]),
+ ("D", [(1, 4), (12, 3)]),
+ ("R", [ "888::1", "9999::1"]),
+ ("F", (0x89abcdef)),
+ ("D", [(1, 4), (12, 3), (0, 0), (0, 0), (0, 0), (0, 0),
+ (0, 0), (0, 0), (0, 0), (12, 3)]),
+ ])
+
+ # Two Dest Ops, should fail unless net.ipv6.enforce_ext_hdr_order
+ # equals 1
+ make_packet("Two Dest Ops",
+ [
+ ("D", []),
+ ("D", []),
+ ])
+
+ # OOO Routing headers, should fail unless
+ # net.ipv6.enforce_ext_hdr_order equals 1
+ make_packet("OOO Routing header",
+ [
+ ("F", (0x89abcdef)),
+ ("R", [ "888::1", "9999::1"]),
+ ])
+
+ # Two Routing headers, should fail unless
+ # net.ipv6.enforce_ext_hdr_order equals 1
+ make_packet("Two Routing headers",
+ [
+ ("R", [ "888::1", "9999::1"]),
+ ("R", [ "888::1", "9999::1"]),
+ ])
+
+ # Two DestOpt headers with Routing header should succeed with default
+ # sysctl settings
+ make_packet("Two Destination options okay",
+ [
+ ("D", [(1, 4), (12, 3)]),
+ ("R", [ "888::1", "9999::1"]),
+ ("D", [(1, 4), (12, 3)]),
+ ])
+
+ # Two DestOpt headers without Routing header should fail unless
+ # net.ipv6.enforce_ext_hdr_order equals 1
+ make_packet("Two Destination options",
+ [
+ ("D", [(1, 4), (12, 3)]),
+ ("D", [(1, 4), (12, 3)]),
+ ])
+
+ # Two DestOpt headers after Routing header, should fail unless
+ # net.ipv6.enforce_ext_hdr_order equals 1
+ make_packet("Two Destination options after RH",
+ [
+ ("R", [ "888::1", "9999::1"]),
+ ("D", [(1, 4), (12, 3)]),
+ ("D", [(1, 4), (12, 3)]),
+ ])
+
+ # Many extension headers, should fail unless
+ # net.ipv6.enforce_ext_hdr_order equals 1
+ make_packet("Many EH OOO",
+ [
+ ("H", [(1, 4), (12, 3)]),
+ ("R", [ "888::1", "9999::1"]),
+ ("D", [(1, 4), (12, 3)]),
+ ("D", [(1, 4), (12, 3), (12, 3)]),
+ ("R", [ "888::1", "9999::1"]),
+ ("D", [(1, 4), (12, 3), (12, 3)]),
+ ("R", [ "888::1", "9999::1"]),
+ ("R", [ "888::1", "9999::1"]),
+ ("D", [(1, 4), (12, 3), (12, 3)]),
+ ("R", [ "888::1", "9999::1"]),
+ ("D", [(1, 4), (12, 3)]),
+ ("F", (0x89abcdef)),
+ ("D", [(1, 4), (12, 3), (12, 3)]),
+ ("R", [ "888::1", "9999::1"]),
+ ("D", [(1, 4), (12, 3), (12, 3)]),
+ ("R", [ "888::1", "9999::1"]),
+ ("R", [ "888::1", "9999::1"]),
+ ("D", [(1, 4), (12, 3), (12, 3)]),
+ ])
+
+ # Two fragment headers, should always fail due to stack
+ # implementation
+ make_packet("Two fragment Headers",
+ [
+ ("F", (0x89abcdef)),
+ ("F", (0x89abcdef)),
+ ])
+
+cli_args()
+
+make_test_packets()
+
+if PCAP_OUT != "":
+ write_pcap(PCAP_OUT)
+ STATUS = 0
+else:
+ STATUS = run_test()
+
+sys.exit(STATUS)
diff --git a/tools/testing/selftests/net/eh_limits.sh b/tools/testing/selftests/net/eh_limits.sh
new file mode 100755
index 000000000000..7703f29f2092
--- /dev/null
+++ b/tools/testing/selftests/net/eh_limits.sh
@@ -0,0 +1,205 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+# Tests extension header limits.
+#
+# We start by setting up two network namespaces with IPv6 addresses.
+# Then we create ICMPv6 Echo Request packets with various combinations of
+# Extension Headers, and send them from one namespace to the other and
+# check if a reply is received. Based on the sysctl settings certain packets
+# are expected to produce echo replies and others are expected to be drop
+# because an Extension Header related limit is exceeded. If an Echo Reply is
+# received or not received per our expectations then the test passes,
+# otherwise if the result is unexpected that's a test failure.
+# Tests extension header limits.
+
+source lib.sh
+
+# all tests in this script. Can be overridden with -t option
+TESTS="eh_limits"
+
+VERBOSE=""
+PAUSE_ON_FAIL=no
+PAUSE=no
+NAME="EH-limits"
+
+IP1="2001:db8::1"
+IP2="2001:db8::2"
+
+log_test()
+{
+ local rc=$1
+ local expected=$2
+ local msg="$3"
+
+ if [ "${rc}" -eq "${expected}" ]; then
+ printf " TEST: %-60s [ OK ]\n" "${msg}"
+ nsuccess=$((nsuccess+1))
+ else
+ nfail=$((nfail+1))
+ printf " 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
+
+ if [ "${PAUSE}" = "yes" ]; then
+ echo
+ echo "hit enter to continue, 'q' to quit"
+ read -r a
+ [ "$a" = "q" ] && exit 1
+ fi
+}
+
+################################################################################
+# Setup
+
+setup()
+{
+ set -e
+
+ setup_ns ns1 ns2
+
+ NS_EXEC="ip netns exec"
+
+ ip link add veth1 type veth peer name veth2
+
+ ip link set veth1 netns "$ns1"
+ ip link set veth2 netns "$ns2"
+
+ $NS_EXEC "$ns1" ip addr add ${IP1}/64 dev veth1
+ $NS_EXEC "$ns1" ip link set veth1 up
+ $NS_EXEC "$ns1" ip link set lo up
+
+ $NS_EXEC "$ns2" ip addr add ${IP2}/64 dev veth2
+ $NS_EXEC "$ns2" ip link set veth2 up
+ $NS_EXEC "$ns2" ip link set lo up
+
+ # Enable SRv6 on the receiver since that's the type of routing header
+ # used in the test
+ $NS_EXEC "$ns2" sysctl -w net.ipv6.conf.all.seg6_enabled=1 > /dev/null
+ $NS_EXEC "$ns2" sysctl -w net.ipv6.conf.veth2.seg6_enabled=1 > /dev/null
+
+ set +e
+
+ # Send a ping to do neighuor discovery
+ $NS_EXEC "$ns1" ping6 -w 2 $IP2 -c 1 > /dev/null
+}
+
+exit_cleanup_all()
+{
+ cleanup_all_ns
+ exit "${EXIT_STATUS}"
+}
+
+eh_limits_test()
+{
+ local ip_addrs="--src_ip $IP1 --dst_ip $IP2"
+
+ if [ "$VERBOSE" = "-v" ]; then
+ echo ">>>>> Default"
+ fi
+
+ # Run the test with default sysctl settings
+ $NS_EXEC "$ns1" python3 ./eh_limits.py $VERBOSE $ip_addrs
+ $NS_EXEC "$ns1" python3 ./eh_limits.py $ip_addrs
+
+ log_test $? 0 "$NAME - default sysctls"
+
+ if [ "$VERBOSE" = "-v" ]; then
+ echo ">>>>> No order enforce, 8 options, 66 length limit"
+ fi
+
+ # Set extension header limit sysctls. We do this on both sides since
+ # the sender reads the sysctl's to determine pass/fail expectations
+
+ $NS_EXEC "$ns1" sysctl -w net.ipv6.enforce_ext_hdr_order=0 > /dev/null
+ $NS_EXEC "$ns1" sysctl -w net.ipv6.max_dst_opts_number=8 > /dev/null
+ $NS_EXEC "$ns1" sysctl -w net.ipv6.max_hbh_opts_number=8 > /dev/null
+ $NS_EXEC "$ns1" sysctl -w net.ipv6.max_hbh_length=64 > /dev/null
+ $NS_EXEC "$ns1" sysctl -w net.ipv6.max_dst_opts_length=64 > /dev/null
+
+ $NS_EXEC "$ns2" sysctl -w net.ipv6.enforce_ext_hdr_order=0 > /dev/null
+ $NS_EXEC "$ns2" sysctl -w net.ipv6.max_dst_opts_number=8 > /dev/null
+ $NS_EXEC "$ns2" sysctl -w net.ipv6.max_hbh_opts_number=8 > /dev/null
+ $NS_EXEC "$ns2" sysctl -w net.ipv6.max_hbh_length=64 > /dev/null
+ $NS_EXEC "$ns2" sysctl -w net.ipv6.max_dst_opts_length=64 > /dev/null
+
+ # Run the test with modified sysctl settings
+ $NS_EXEC "$ns1" python3 ./eh_limits.py $VERBOSE $ip_addrs
+
+ log_test $? 0 "$NAME - modified sysctls"
+}
+
+################################################################################
+# usage
+
+usage()
+{
+ cat <<EOF
+usage: ${0##*/} OPTS
+
+ -t <test> Test(s) to run (default: all)
+ (options: $TESTS)
+ -p Pause on fail
+ -P Pause after each test before cleanup
+ -v verbose mode (show commands and output)
+EOF
+}
+
+################################################################################
+# main
+
+require_command scapy
+
+while getopts :t:pPhv o
+do
+ case $o in
+ t) TESTS=$OPTARG;;
+ p) PAUSE_ON_FAIL=yes;;
+ P) PAUSE=yes;;
+ v) VERBOSE="-v";;
+ h) usage; exit 0;;
+ *) usage; exit 1;;
+ esac
+done
+
+# make sure we don't pause twice
+[ "${PAUSE}" = "yes" ] && PAUSE_ON_FAIL=no
+
+ksft_skip=4
+
+if [ "$(id -u)" -ne 0 ];then
+ echo "SKIP: Need root privileges"
+ exit "$ksft_skip"
+fi
+
+if [ ! -x "$(command -v ip)" ]; then
+ echo "SKIP: Could not run test without ip tool"
+ exit "$ksft_skip"
+fi
+
+if [ ! -x "$(command -v socat)" ]; then
+ echo "SKIP: Could not run test without socat tool"
+ exit "$ksft_skip"
+fi
+
+# start clean
+cleanup &> /dev/null
+
+for t in $TESTS
+do
+ case $t in
+ eh_limits) setup; eh_limits_test; cleanup_all_ns;;
+
+ help) echo "Test names: $TESTS"; exit 0;;
+ esac
+done
+
+if [ "$TESTS" != "none" ]; then
+ printf "\nTests passed: %3d\n" ${nsuccess}
+ printf "Tests failed: %3d\n" ${nfail}
+fi
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH net-next v9 00/10] ipv6: Address ext hdr DoS vulnerabilities
2026-03-14 17:51 [PATCH net-next v9 00/10] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert
` (9 preceding siblings ...)
2026-03-14 17:51 ` [PATCH net-next v9 10/10] test: Add networking selftest for eh limits Tom Herbert
@ 2026-03-14 17:58 ` Jakub Kicinski
10 siblings, 0 replies; 15+ messages in thread
From: Jakub Kicinski @ 2026-03-14 17:58 UTC (permalink / raw)
To: Tom Herbert; +Cc: davem, netdev, justin.iurman, willemdebruijn.kernel, pabeni
On Sat, 14 Mar 2026 10:51:14 -0700 Tom Herbert wrote:
> Subject: [PATCH net-next v9 00/10] ipv6: Address ext hdr DoS vulnerabilities
Sir, 24h between reposts, please. I could have told you that net-next
is our default tree, anyway. So you're just spamming the list and the
CI for no good reason.
Quoting documentation:
tl;dr
-----
- designate your patch to a tree - ``[PATCH net]`` or ``[PATCH net-next]``
- for fixes the ``Fixes:`` tag is required, regardless of the tree
- don't post large series (> 15 patches), break them up
- don't repost your patches within one 24h period
- reverse xmas tree
See: https://www.kernel.org/doc/html/next/process/maintainer-netdev.html#tl-dr
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH net-next v9 08/10] test: Add proto_nums.py in networking selftests
2026-03-14 17:51 ` [PATCH net-next v9 08/10] test: Add proto_nums.py in networking selftests Tom Herbert
@ 2026-03-17 15:22 ` Simon Horman
0 siblings, 0 replies; 15+ messages in thread
From: Simon Horman @ 2026-03-17 15:22 UTC (permalink / raw)
To: Tom Herbert
Cc: davem, kuba, netdev, justin.iurman, willemdebruijn.kernel, pabeni
On Sat, Mar 14, 2026 at 10:51:22AM -0700, Tom Herbert wrote:
> Add proto_nums.py that contains various python definitions of
> common protocol constants
>
> Signed-off-by: Tom Herbert <tom@herbertland.com>
> ---
> tools/testing/selftests/net/proto_nums.py | 231 ++++++++++++++++++++++
Hi Tom,
I think this new file needs to be added to
tools/testing/selftests/net/Makefile
Likewise for patches 9/10 and 10/10.
...
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [net-next,v9,09/10] test: Add ext_hdr.py in networking selftests
2026-03-14 17:51 ` [PATCH net-next v9 09/10] test: Add ext_hdr.py " Tom Herbert
@ 2026-03-17 15:24 ` Simon Horman
0 siblings, 0 replies; 15+ messages in thread
From: Simon Horman @ 2026-03-17 15:24 UTC (permalink / raw)
To: tom
Cc: Simon Horman, netdev, justin.iurman, willemdebruijn.kernel,
pabeni, willemb, davem, kuba, justin.iurman
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
Simon says: pylint also has quite a lot to say about this patch. I'm
assuming it is all minor stuff. But could you look into addressing the
issues it flags?
---
test: Add ext_hdr.py in networking selftests
This commit adds a new Python helper file that contains Extension Header
format definitions and related functions for building IPv6 extension
header chains in networking selftests.
> diff --git a/tools/testing/selftests/net/ext_hdr.py b/tools/testing/selftests/net/ext_hdr.py
> new file mode 100755
> index 000000000000..bfb7da4a7c88
> --- /dev/null
> +++ b/tools/testing/selftests/net/ext_hdr.py
> @@ -0,0 +1,385 @@
> +#!/usr/bin/env python3
> +# SPDX-License-Identifier: GPL-2.0
> +
> +# Helper functions for creating extension headers using scapy
> +
> +import ctypes
> +import shlex
> +import socket
> +import sys
> +import subprocess
> +import scapy
> +import proto_nums
> +
> +
> +# Read a sysctl
> +def sysctl_read(name):
> + try:
> + # shlex.split helps handle arguments correctly
> + command = shlex.split(f"sysctl -n {name}")
> + # Use check=True to raise an exception if the command fails
> + result = subprocess.run(command, check=True,
> + capture_output=True, text=True)
> + value = result.stdout.strip()
> + except subprocess.CalledProcessError as ex:
> + print(f"Error reading sysctl: {ex.stderr}")
> + except FileNotFoundError:
> + print("The 'sysctl' command was not found. "
> + "Check your system's PATH.")
> +
> + return int(value)
What happens when an exception is caught here? When either
subprocess.CalledProcessError or FileNotFoundError occurs, the exception
handlers print an error message but don't set 'value'. The function then
reaches the 'return int(value)' statement, which will raise
UnboundLocalError.
This will crash the test in several places:
tools/testing/selftests/net/ext_hdr.py:check_eh_order() uses the return:
do_check = sysctl_read("net.ipv6.enforce_ext_hdr_order")
tools/testing/selftests/net/ext_hdr.py:make_eh_chain() uses the return:
pair = make_full_hbh_dst_options_list(nnext_hdr, args,
sysctl_read("net.ipv6.max_hbh_opts_number"),
sysctl_read("net.ipv6.max_hbh_length"))
and:
pair = make_full_hbh_dst_options_list(nnext_hdr, args,
sysctl_read("net.ipv6.max_dst_opts_number"),
sysctl_read("net.ipv6.max_dst_opts_length"))
Should the exception handlers return a default value or re-raise to let
callers handle it?
> +
> +# Common definitions for Destination and Hop-by-Hop options
[ ... ]
Should ext_hdr.py be added to TEST_FILES in the Makefile? The selftests
build system documentation requires that any file imported by test scripts
must be added to TEST_FILES. Without this, tests will work in the source
tree but fail after 'make install' with ModuleNotFoundError.
The file is imported at line 12 ('import proto_nums'), and appears to be
a library meant for import rather than direct execution. Both ext_hdr.py
and proto_nums.py need to be added to TEST_FILES in
tools/testing/selftests/net/Makefile.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH net-next v9 10/10] test: Add networking selftest for eh limits
2026-03-14 17:51 ` [PATCH net-next v9 10/10] test: Add networking selftest for eh limits Tom Herbert
@ 2026-03-17 15:32 ` Simon Horman
0 siblings, 0 replies; 15+ messages in thread
From: Simon Horman @ 2026-03-17 15:32 UTC (permalink / raw)
To: Tom Herbert
Cc: davem, kuba, netdev, justin.iurman, willemdebruijn.kernel, pabeni
On Sat, Mar 14, 2026 at 10:51:24AM -0700, Tom Herbert wrote:
> Add a networking selftest for Extension Header limits. The
> limits to test are in systcls:
>
> net.ipv6.enforce_ext_hdr_order
> net.ipv6.max_dst_opts_number
> net.ipv6.max_hbh_opts_number
> net.ipv6.max_hbh_length
> net.ipv6.max_dst_opts_length
>
> The basic idea of the test is to fabricate ICMPv6 Echo Request
> packets with various combinations of Extension Headers. The packets
> are sent to a host in another namespace. If a an ICMPv6 Echo Reply
> is received then the packet wasn't dropped due to a limit being
> exceeded, and if it was dropped then we assume that a limit was
> exceeded. For each test packet we derive an expectation as to
> whether the packet will be dropped or not. Test success depends
> on whether our expectation is matched. i.e. if we expect a reply
> then the test succeeds if we see a reply, and if we don't expect a
> reply then the test succeeds if we don't see a reply.
>
> The test is divided into a frontend bash script (eh_limits.sh) and a
> backend Python script (eh_limits.py).
>
> The frontend sets up two network namespaces with IPv6 addresses
> configured on veth's. We then invoke the backend to send the
> test packets. This first pass is done with default sysctl settings.
> On a second pass we change the various sysctl settings and run
> again.
>
> The backend runs through the various test cases described in the
> Make_Test_Packets function. This function calls Make_Packet for
> a test case where arguments provide the Extension Header chain to
> be tested. The Run_Test function loops through the various packets
> and tests if a reply is received versus the expectation. If a test
> case fails then an error status is returned by the backend.
>
> The backend script can also be run with the "-w <pcap_file>" to
> write the created packets to a pcap file instead of running the
> test.
>
> Signed-off-by: Tom Herbert <tom@herbertland.com>
> ---
> tools/testing/selftests/net/Makefile | 1 +
> tools/testing/selftests/net/eh_limits.py | 349 +++++++++++++++++++++++
> tools/testing/selftests/net/eh_limits.sh | 205 +++++++++++++
> 3 files changed, 555 insertions(+)
> create mode 100755 tools/testing/selftests/net/eh_limits.py
> create mode 100755 tools/testing/selftests/net/eh_limits.sh
Hi Tom,
Shellcheck flags several instances of the following:
- https://www.shellcheck.net/wiki/SC2154 -- ns1 is referenced but not assigned.
- https://www.shellcheck.net/wiki/SC2086 -- Double quote to prevent globbing ...
In the case of SC2086 I think this can be trivially addressed by adding
double quotes.
While I think SC2154 should probably be ignored using
# shellcheck disable=SC2154
We're trying to make new scripts shellcheck clean, so I'd appreciate it if
you could look into this.
Also, pylint has also something to say about this patch.
...
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-03-17 15:32 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-14 17:51 [PATCH net-next v9 00/10] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert
2026-03-14 17:51 ` [PATCH net-next v9 01/10] ipv6: Check of max HBH or DestOp sysctl is zero and drop if it is Tom Herbert
2026-03-14 17:51 ` [PATCH net-next v9 02/10] ipv6: Cleanup IPv6 TLV definitions Tom Herbert
2026-03-14 17:51 ` [PATCH net-next v9 03/10] ipv6: Add case for IPV6_TLV_TNL_ENCAP_LIMIT in EH TLV switch Tom Herbert
2026-03-14 17:51 ` [PATCH net-next v9 04/10] ipv6: Set HBH and DestOpt limits to 2 Tom Herbert
2026-03-14 17:51 ` [PATCH net-next v9 05/10] ipv6: Document defaults for max_{dst|hbh}_opts_number sysctls Tom Herbert
2026-03-14 17:51 ` [PATCH net-next v9 06/10] ipv6: Enforce Extension Header ordering Tom Herbert
2026-03-14 17:51 ` [PATCH net-next v9 07/10] ipv6: Document enforce_ext_hdr_order sysctl Tom Herbert
2026-03-14 17:51 ` [PATCH net-next v9 08/10] test: Add proto_nums.py in networking selftests Tom Herbert
2026-03-17 15:22 ` Simon Horman
2026-03-14 17:51 ` [PATCH net-next v9 09/10] test: Add ext_hdr.py " Tom Herbert
2026-03-17 15:24 ` [net-next,v9,09/10] " Simon Horman
2026-03-14 17:51 ` [PATCH net-next v9 10/10] test: Add networking selftest for eh limits Tom Herbert
2026-03-17 15:32 ` Simon Horman
2026-03-14 17:58 ` [PATCH net-next v9 00/10] ipv6: Address ext hdr DoS vulnerabilities Jakub Kicinski
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox