Note: Alejandro's series is now at v4, and 05/11 is unchanged there, so
this applies on top of v4 too:
https://patchwork.ozlabs.org/project/openvswitch/list/?series=528505

On Sat, Oct 17, 2026 at 8:57 AM Anil Kaushik
<[email protected]> wrote:
>
> The GTP-U optional field (sequence number, N-PDU number and next
> extension header type) is present when any of the E, S or PN flags is
> set (3GPP TS 29.281, section 5.1), but netdev_gtpu_pop_header() only
> looks at the S flag.  It also does not skip the extension headers when
> the E flag is set.
>
> On the 5G N3 interface, G-PDUs usually have only the E flag set and
> carry a PDU Session Container (3GPP TS 38.415).  These packets are
> decapsulated at the wrong offset: the optional field and the extension
> header are taken as the inner packet, which is then dropped as a
> non-IP packet.
>
> Take the optional field into account for the E and PN flags too, and
> walk the extension header chain, using the length of each header, in
> 4-octet units, and the next type in its last octet.  Drop the packet if
> an extension header is empty or does not fit in the packet.
>
> Fixes: 3c6d05a02e0f ("userspace: Add GTP-U support.")
> Assisted-by: LLM
> Signed-off-by: Anil Kaushik <[email protected]>
> ---
>
> Notes:
>     This applies on top of the pending "netdev-native-tnl: Check GTP-U
>     header length on pop." from Alejandro Pacheco, patch 05/11 of his v2
>     "Fix header length checks in the userspace datapath":
>     
> https://patchwork.ozlabs.org/project/openvswitch/patch/CAB8Pm=yz4mtbctzogj7dhxxtaobcqrqva_btlszotlnfwau...@mail.gmail.com/
>
>     It does not apply on main without it.
>
>  lib/netdev-native-tnl.c  | 34 ++++++++++++++--
>  lib/packets.h            |  1 +
>  tests/tunnel-push-pop.at | 83 ++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 115 insertions(+), 3 deletions(-)
>
> diff --git a/lib/netdev-native-tnl.c b/lib/netdev-native-tnl.c
> index dc14542ec..8205cef4c 100644
> --- a/lib/netdev-native-tnl.c
> +++ b/lib/netdev-native-tnl.c
> @@ -838,16 +838,44 @@ netdev_gtpu_pop_header(struct dp_packet *packet)
>      tnl->tun_id = be32_to_be64(get_16aligned_be32(&gtph->teid));
>
>      if (tnl->gtpu_msgtype == GTPU_MSGTYPE_GPDU) {
> +        size_t l4_size = dp_packet_l4_size(packet);
> +        const uint8_t *l4 = dp_packet_l4(packet);
>          uint8_t ip_ver = 0;
>
> +        /* The optional field is present if any of the E, S or PN flags is
> +         * set, not only S. */
>          gtpu_hlen = GTPU_HLEN;
> -        if (gtph->md.flags & GTPU_S_MASK) {
> +        if (gtph->md.flags & (GTPU_E_MASK | GTPU_S_MASK | GTPU_PN_MASK)) {
>              gtpu_hlen += sizeof(struct gtpuhdr_opt);
>          }
> -        if (gtpu_hlen > dp_packet_l4_size(packet)) {
> +        if (gtpu_hlen > l4_size) {
>              goto err;
>          }
> -        if (gtpu_hlen < dp_packet_l4_size(packet)) {
> +
> +        /* Skip the extension headers, e.g. the PDU Session Container used
> +         * on the 5G N3 interface.  Each one has its length, in 4-octet
> +         * units, in its first octet, and the type of the next one in its
> +         * last octet. */
> +        if (gtph->md.flags & GTPU_E_MASK) {
> +            uint8_t next = l4[GTPU_HLEN + offsetof(struct gtpuhdr_opt,
> +                                                  next_ext_type)];
> +
> +            while (next) {
> +                unsigned int ext_len;
> +
> +                if (gtpu_hlen >= l4_size) {
> +                    goto err;
> +                }
> +                ext_len = l4[gtpu_hlen] * 4;
> +                if (!ext_len || ext_len > l4_size - gtpu_hlen) {
> +                    goto err;
> +                }
> +                gtpu_hlen += ext_len;
> +                next = l4[gtpu_hlen - 1];
> +            }
> +        }
> +
> +        if (gtpu_hlen < l4_size) {
>              const struct ip_header *ip;
>
>              ip = ALIGNED_CAST(const struct ip_header *,
> diff --git a/lib/packets.h b/lib/packets.h
> index ea0bda840..a467e04ef 100644
> --- a/lib/packets.h
> +++ b/lib/packets.h
> @@ -1455,6 +1455,7 @@ static inline ovs_be32 get_erspan_ts(enum erspan_ts_gra 
> gra)
>  #define GTPU_P_MASK     0x10
>  #define GTPU_E_MASK     0x04
>  #define GTPU_S_MASK     0x02
> +#define GTPU_PN_MASK    0x01
>
>  /* GTP-U UDP port. */
>  #define GTPU_DST_PORT   2152
> diff --git a/tests/tunnel-push-pop.at b/tests/tunnel-push-pop.at
> index ab393cfc7..39fa4cffc 100644
> --- a/tests/tunnel-push-pop.at
> +++ b/tests/tunnel-push-pop.at
> @@ -193,6 +193,89 @@ OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep 
> 100022eb0000000120000237 | wc -l`
>  OVS_VSWITCHD_STOP
>  AT_CLEANUP
>
> +AT_SETUP([tunnel_push_pop - gtpu decap with extension header])
> +OVS_VSWITCHD_START([add-port br0 p0 -- set Interface p0 type=dummy dnl
> +                    ofport_request=1 other-config:hwaddr=aa:55:aa:55:00:00])
> +AT_CHECK([ovs-vsctl add-br int-br -- set bridge int-br datapath_type=dummy],
> +         [0])
> +AT_CHECK([ovs-vsctl add-port int-br t1 -- set Interface t1 type=gtpu \
> +                    options:remote_ip=1.1.2.92 options:key=123 \
> +                    ofport_request=2])
> +
> +AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
> +])
> +AT_CHECK([ovs-ofctl add-flow br0 action=normal])
> +AT_CHECK([ovs-ofctl add-flow int-br "cookie=0x1,in_port=2,ip,actions=drop"])
> +AT_CHECK([ovs-appctl netdev-dummy/receive p0 dnl
> +'recirc_id(0),in_port(100),eth(src=f8:bc:12:44:34:b6,dst=ff:ff:ff:ff:ff:ff),dnl
> +eth_type(0x0806),arp(sip=1.1.2.92,tip=1.1.2.88,op=2,sha=f8:bc:12:44:34:b6,dnl
> +tha=00:00:00:00:00:00)'])
> +AT_CHECK([ovs-appctl time/warp 1000], [0], [ignore])
> +AT_CHECK([ovs-appctl revalidator/wait])
> +
> +dnl G-PDU with only the E flag set, as sent on the 5G N3 interface: the
> +dnl optional field is there, followed by a PDU Session Container (QFI 9).
> +m4_define([GTPU_EXT_PKT], m4_join([],
> +dnl eth(src=00:1b:21:3c:ab:64,dst=aa:55:aa:55:00:00,type=0x0800)
> +[aa55aa550000001b213cab640800],
> +dnl ipv4(src=1.1.2.92,dst=1.1.2.88,proto=17,tot_len=72)
> +[4500004800010000401173ef0101025c01010258],
> +dnl udp(src=4789,dst=2152,len=52)
> +[12b5086800340000],
> +dnl gtpu(flags=0x34,msgtype=255,len=36,teid=123)
> +[34ff00240000007b],
> +dnl gtpu optional field: next extension is a PDU Session Container.
> +[00000085],
> +dnl PDU Session Container: len=1, UL PDU, QFI=9, no next extension.
> +[01100900],
> +dnl ipv4(src=30.0.0.1,dst=30.0.0.2,proto=1,tot_len=28)
> +[4500001c000100008001fedd1e0000011e000002],
> +dnl icmp(type=0,code=0)
> +[0000ffff00000000]))
> +
> +AT_CHECK([ovs-appctl netdev-dummy/receive p0 GTPU_EXT_PKT])
> +AT_CHECK([ovs-appctl time/warp 1000], [0], [ignore])
> +AT_CHECK([ovs-ofctl dump-ports int-br | grep 'port  2'], [0], [dnl
> +  port  2: rx pkts=1, bytes=28, drop=?, errs=?, frame=?, over=?, crc=?
> +])
> +AT_CHECK([ovs-ofctl dump-flows int-br | grep 'cookie=0x1,' | dnl
> +          grep -c 'n_packets=1,'], [0], [1
> +])
> +
> +dnl Same packet, but with an extension header length of 0: dropped.
> +m4_define([GTPU_EXT_LEN0_PKT], m4_join([],
> +[aa55aa550000001b213cab640800],
> +[4500004800010000401173ef0101025c01010258],
> +[12b5086800340000],
> +[34ff00240000007b],
> +[00000085],
> +dnl PDU Session Container with len=0.
> +[00100900],
> +[4500001c000100008001fedd1e0000011e000002],
> +[0000ffff00000000]))
> +
> +dnl The optional field announces an extension header that is missing: 
> dropped.
> +m4_define([GTPU_EXT_TRUNC_PKT], m4_join([],
> +[aa55aa550000001b213cab640800],
> +dnl ipv4(src=1.1.2.92,dst=1.1.2.88,proto=17,tot_len=40)
> +[45000028000100004011740f0101025c01010258],
> +dnl udp(src=4789,dst=2152,len=20)
> +[12b5086800140000],
> +dnl gtpu(flags=0x34,msgtype=255,len=4,teid=123)
> +[34ff00040000007b],
> +[00000085]))
> +
> +AT_CHECK([ovs-appctl netdev-dummy/receive p0 GTPU_EXT_LEN0_PKT])
> +AT_CHECK([ovs-appctl netdev-dummy/receive p0 GTPU_EXT_TRUNC_PKT])
> +AT_CHECK([ovs-appctl time/warp 1000], [0], [ignore])
> +AT_CHECK([ovs-ofctl dump-ports int-br | grep 'port  2'], [0], [dnl
> +  port  2: rx pkts=1, bytes=28, drop=?, errs=?, frame=?, over=?, crc=?
> +])
> +CHECK_COVERAGE([datapath_drop_tunnel_pop_error], [2])
> +
> +OVS_VSWITCHD_STOP
> +AT_CLEANUP
> +
>  AT_SETUP([tunnel_push_pop - action])
>
>  OVS_VSWITCHD_START([add-port br0 p0 -- set Interface p0 type=dummy 
> ofport_request=1 other-config:hwaddr=aa:55:aa:55:00:00])
> --
> 2.25.1
>
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to