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(>ph->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
