Add the End.MAP behavior (RFC 9433 Section 6.2): an endpoint that replaces the IPv6 destination address with a configured next SID and forwards via IPv6 routing without consuming the SRH. The new nh6 attribute selects the replacement SID. Add three drop reasons that End.MAP emits to dropreason-core.h, so dropped packets show up in the standard skb:kfree_skb tracepoint: SEG6_MOBILE_INVALID_SRH_SL SEG6_MOBILE_HOP_LIMIT_EXCEEDED SEG6_MOBILE_NOMEM Configuration: ip -6 route add 2001:db8:f::/64 \ encap seg6local action End.MAP nh6 2001:db8:1::e \ dev Link: https://www.rfc-editor.org/rfc/rfc9433.html#section-6.2 Signed-off-by: Yuya Kusakabe --- include/net/dropreason-core.h | 12 +++ include/uapi/linux/seg6_local.h | 2 + net/ipv6/seg6_local.c | 73 ++++++++++++++++ tools/testing/selftests/net/Makefile | 1 + tools/testing/selftests/net/srv6_end_map_test.sh | 103 +++++++++++++++++++++++ 5 files changed, 191 insertions(+) diff --git a/include/net/dropreason-core.h b/include/net/dropreason-core.h index e0ca3904ff8e..1be5c54d7605 100644 --- a/include/net/dropreason-core.h +++ b/include/net/dropreason-core.h @@ -127,6 +127,8 @@ FN(PSP_INPUT) \ FN(PSP_OUTPUT) \ FN(RECURSION_LIMIT) \ + FN(SEG6_MOBILE_INVALID_SRH_SL) \ + FN(SEG6_MOBILE_NOMEM) \ FNe(MAX) /** @@ -600,6 +602,16 @@ enum skb_drop_reason { SKB_DROP_REASON_PSP_OUTPUT, /** @SKB_DROP_REASON_RECURSION_LIMIT: Dead loop on virtual device. */ SKB_DROP_REASON_RECURSION_LIMIT, + /** + * @SKB_DROP_REASON_SEG6_MOBILE_INVALID_SRH_SL: invalid Segments Left + * value or SRH validation failure on an SRv6 Mobile path. + */ + SKB_DROP_REASON_SEG6_MOBILE_INVALID_SRH_SL, + /** + * @SKB_DROP_REASON_SEG6_MOBILE_NOMEM: skb head/tail expansion or + * helper allocation failed on an SRv6 Mobile path. + */ + SKB_DROP_REASON_SEG6_MOBILE_NOMEM, /** * @SKB_DROP_REASON_MAX: the maximum of core drop reasons, which * shouldn't be used as a real 'reason' - only for tracing code gen diff --git a/include/uapi/linux/seg6_local.h b/include/uapi/linux/seg6_local.h index 4fdc424c9cb3..45386fdfa821 100644 --- a/include/uapi/linux/seg6_local.h +++ b/include/uapi/linux/seg6_local.h @@ -67,6 +67,8 @@ enum { SEG6_LOCAL_ACTION_END_BPF = 15, /* decap and lookup of DA in v4 or v6 table */ SEG6_LOCAL_ACTION_END_DT46 = 16, + /* swap DA with new SID, leave SRH untouched (RFC 9433 Section 6.2) */ + SEG6_LOCAL_ACTION_END_MAP = 17, __SEG6_LOCAL_ACTION_MAX, }; diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c index 2b41e4c0dddd..bd8e3312973f 100644 --- a/net/ipv6/seg6_local.c +++ b/net/ipv6/seg6_local.c @@ -1468,6 +1468,73 @@ static int input_action_end_bpf(struct sk_buff *skb, return -EINVAL; } +/* SRH validation helper for SRv6 Mobile (RFC 9433) behaviors that may + * receive an SRv6 encapsulated packet. Returns the SRH on success or + * NULL on validation failure / when the SRH is absent. The caller + * uses @missing to distinguish the two NULL cases: an SRH-less packet + * may be acceptable depending on the behavior. + */ +static struct ipv6_sr_hdr *seg6_mobile_get_validated_srh(struct sk_buff *skb, + bool *missing) +{ + struct ipv6_sr_hdr *srh = seg6_get_srh(skb, 0); + + if (!srh) { + if (missing) + *missing = true; + return NULL; + } + if (missing) + *missing = false; + +#ifdef CONFIG_IPV6_SEG6_HMAC + if (!seg6_hmac_validate_skb(skb)) + return NULL; +#endif + return srh; +} + +/* RFC 9433 Section 6.2 -- End.MAP + * Replace the outer IPv6 destination address with the configured next + * SID, decrement the Hop Limit, and forward via IPv6 routing. The + * SRH is left untouched, so any subsequent End* behavior continues to + * see the original Segment List unchanged. + */ +static int input_action_end_map(struct sk_buff *skb, + struct seg6_local_lwt *slwt) +{ + enum skb_drop_reason reason; + struct ipv6_sr_hdr *srh; + struct ipv6hdr *ip6h; + bool no_srh = false; + + reason = SKB_DROP_REASON_SEG6_MOBILE_INVALID_SRH_SL; + + /* When an SRH is present it must HMAC-validate before we touch + * the destination; an SRH-less packet is also accepted because + * End.MAP does not consume the SRH. + */ + srh = seg6_mobile_get_validated_srh(skb, &no_srh); + if (!srh && !no_srh) + goto drop; + + if (skb_ensure_writable(skb, sizeof(*ip6h))) { + reason = SKB_DROP_REASON_SEG6_MOBILE_NOMEM; + goto drop; + } + + ip6h = ipv6_hdr(skb); + ip6h->daddr = slwt->nh6; + + skb_dst_drop(skb); + seg6_lookup_nexthop(skb, NULL, 0); + return dst_input(skb); + +drop: + kfree_skb_reason(skb, reason); + return -EINVAL; +} + static struct seg6_action_desc seg6_action_table[] = { { .action = SEG6_LOCAL_ACTION_END, @@ -1565,6 +1632,12 @@ static struct seg6_action_desc seg6_action_table[] = { .optattrs = SEG6_F_LOCAL_COUNTERS, .input = input_action_end_bpf, }, + { + .action = SEG6_LOCAL_ACTION_END_MAP, + .attrs = SEG6_F_ATTR(SEG6_LOCAL_NH6), + .optattrs = SEG6_F_LOCAL_COUNTERS, + .input = input_action_end_map, + }, }; diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index a275ed584026..4fbb1eff79f8 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -90,6 +90,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 \ 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 000000000000..7ee54b4cc97f --- /dev/null +++ b/tools/testing/selftests/net/srv6_end_map_test.sh @@ -0,0 +1,103 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# shellcheck disable=SC2034,SC2154 +# +# Selftest for the SRv6 End.MAP behavior (RFC 9433 Section 6.2). +# +# +--------+ 2001:db8:1::/64 +--------+ 2001:db8:2::/64 +--------+ +# | srupf1 | ------------------- | srupf2 | ------------------- | srupf3 | +# +--------+ veth-1 +--------+ veth-2 +--------+ +# (intermediate +# SRv6-aware UPF, +# End.MAP) +# +# All three netns are SRv6-aware UPFs in the RFC 9433 sense (not +# 3GPP UPFs). Per RFC 9433 Section 6.2 End.MAP is used by the +# intermediate UPF (here srupf2): srupf2 has an End.MAP SID for +# locator 2001:db8:f::/64 mapping to the new SID 2001:db8:2::e. +# srupf1 sends an IPv6 packet to 2001:db8:f::1; on srupf3 the +# destination address is expected to have been replaced by +# 2001:db8:2::e. + +source lib.sh + +readonly TIMEOUT=4 + +cleanup() +{ + cleanup_all_ns +} + +trap cleanup EXIT + +setup() +{ + setup_ns srupf1 srupf2 srupf3 + + ip -n "$srupf1" link set lo up + ip -n "$srupf2" link set lo up + ip -n "$srupf3" link set lo up + + ip link add veth-1 netns "$srupf1" type veth peer name veth-1-srupf2 \ + netns "$srupf2" + ip -n "$srupf1" addr add 2001:db8:1::1/64 dev veth-1 nodad + ip -n "$srupf2" addr add 2001:db8:1::2/64 dev veth-1-srupf2 nodad + ip -n "$srupf1" link set veth-1 up + ip -n "$srupf2" link set veth-1-srupf2 up + + ip link add veth-2 netns "$srupf2" type veth peer name veth-2-srupf3 \ + netns "$srupf3" + ip -n "$srupf2" addr add 2001:db8:2::1/64 dev veth-2 nodad + ip -n "$srupf3" addr add 2001:db8:2::e/64 dev veth-2-srupf3 nodad + ip -n "$srupf2" link set veth-2 up + ip -n "$srupf3" link set veth-2-srupf3 up + + ip netns exec "$srupf2" sysctl -wq net.ipv6.conf.all.forwarding=1 + + ip -n "$srupf1" -6 route add 2001:db8:f::/64 via 2001:db8:1::2 + + ip -n "$srupf2" -6 route add 2001:db8:f::/64 \ + encap seg6local action End.MAP nh6 2001:db8:2::e \ + dev veth-2 + + # allow srupf3 to reply back to srupf1 + ip -n "$srupf3" -6 route add 2001:db8:1::/64 via 2001:db8:2::1 +} + +check_dependencies() +{ + if ! command -v ping >/dev/null; then + echo "SKIP: ping is required"; exit "$ksft_skip" + fi + + if ! ip route help 2>&1 | grep -qF "End.MAP"; then + echo "SKIP: iproute2 too old, missing seg6local action End.MAP" + exit "$ksft_skip" + fi +} + +run_test() +{ + # srupf3 replies to ICMPv6 echo on 2001:db8:2::e, so a successful + # ping from srupf1 to the End.MAP SID demonstrates that the action + # replaced the destination address with 2001:db8:2::e. + if ! ip netns exec "$srupf1" ping -6 -c 1 -W "$TIMEOUT" \ + 2001:db8:f::1 >/dev/null 2>&1; then + return 1 + fi + return 0 +} + +main() +{ + check_dependencies + setup + + if run_test; then + echo "TEST: End.MAP [PASS]"; exit "$ksft_pass" + else + echo "TEST: End.MAP [FAIL]"; exit "$ksft_fail" + fi +} + +main "$@" -- 2.50.1