DOCA SDK Documentation

GTP Parsing Example

This example demonstrates how to add a simple flex parser node to the existing hardware defined parse graph.

DPU Flex Parser GTP.png

Sample Code

This example starts with some basic definitions.

#include <doca_model.p4>
#include <doca_headers.p4>
#include <doca_externs.p4>
#include <doca_parser.p4>

const bit<32> WIRE_PORT = 32w00;
const bit<32> GTP_VPORT = 32w01;
const bit<32> DEFAULT_VPORT = 32w04;

struct metadata_t {
}
#define GTP_U_PORT 2152

Then we define the GTP-U version 1 header.

header Gtp_v1_h {
    bit<3>        version;               /** For GTPv1, this has a value of 1. */
    bit           protocol_type;         /** GTP (value 1) from GTP' (value 0) */
    bit           reserved;
    bit           extension_header_flag; /** extension header optional field. */
    bit           seq_number_flag;       /** Sequence Number optional field */
    bit           n_pdu_number_flag;     /** N-PDU number optional field */
    bit<8>        message_type;          /** types of messages are defined in 3GPP TS 29.060 section 7.1 */
    bit<16>       message_length;        /** length of the payload in bytes */
    bit<32>       teid;                  /** Tunnel endpoint identifier */
    bit<16>       sequence_number;       /** optional */
    bit<8>        n_pdu_number;          /** optional */
    bit<8>        next_extension_hdr_type; /** optional if any of the E, S, or PN bits are on. The field must be interpreted only if the E bit is on */
}

Then we add NV_FIXED_HEADERS to the headers struct, along with the new GTP header.

struct headers_t {
    NV_FIXED_HEADERS
    Gtp_v1_h   gtpv1;
}


Using the nv_transition_from annotation, the GTP parser state is connected as a select transition from the UDP state.

parser packet_parser(packet_in packet, out headers_t headers) {
    NV_FIXED_PARSER(packet, headers)

    @nv_transition_from("nv_parse_udp", GTP_U_PORT)
    state parse_gtp
    {
        packet.extract(headers.gtpv1);
        transition accept;
    }
}

The control example uses a single flow table that matches on input port and GTP tunnel endpoint ID. The policy is then to forward the GTP packet to a port or drop the packet.

/**
 * This control admits GTP packets only if the tunnel ID matches
 *
 */
 control gtp_tunnel(
    inout headers_t headers,
    in nv_standard_metadata_t std_meta,
    inout metadata_t user_meta,
    inout nv_empty_metadata_t pkt_out_meta
) {
    NvDirectCounter(NvCounterType.PACKETS_AND_BYTES) gtp_counter;

    action send_to_port_gtp_table(nv_logical_port_t port) {
        gtp_counter.count();
        nv_set_l4_src_port(headers, 1234);
        nv_send_to_port(port);
    }

    action drop_gtp_table() {
        gtp_counter.count();
        nv_drop();
    }

    table gtp_table {
        key = {
            std_meta.ingress_port: exact;
            headers.gtpv1.teid: exact;
        }
        actions = {
            send_to_port_gtp_table;
            drop_gtp_table;
            NoAction;
        }
        default_action = NoAction;
        direct_counter = gtp_counter;
    }

    apply {
        if (headers.gtpv1.isValid()) {
            if (gtp_table.apply().miss) {
                nv_send_to_port(DEFAULT_VPORT);
            }
        }
        drop();
    }
}

NvDocaPipeline(
    packet_parser(),
    gtp_tunnel()
) main;

Example P4Runtime shell entries:

te_tunnel_1 = table_entry["gtp_tunnel.gtp_table"](action="gtp_tunnel.send_to_port_gtp_table")
te_tunnel_1.match["std_meta.ingress_port"] = "0"
te_tunnel_1.match["headers.gtpv1.teid"] = "0x000000001"
te_tunnel_1.action["port"] = "1"
te_tunnel_1.insert()
te_tunnel_2 = table_entry["gtp_tunnel.gtp_table"](action="gtp_tunnel.send_to_port_gtp_table")
te_tunnel_2.match["std_meta.ingress_port"] = "1"
te_tunnel_2.match["headers.gtpv1.teid"] = "0x000000001"
te_tunnel_2.action["port"] = "0"
te_tunnel_2.insert()
te_tunnel_3 = table_entry["gtp_tunnel.gtp_table"](action="gtp_tunnel.drop_gtp_table")
te_tunnel_3.match["std_meta.ingress_port"] = "4"
te_tunnel_3.match["headers.gtpv1.teid"] = "0x000000001"
te_tunnel_3.insert()

te_tunnel_1.read(lambda te_tunnel_1: print(te_tunnel_1))
te_tunnel_2.read(lambda te_tunnel_2: print(te_tunnel_2))
te_tunnel_3.read(lambda te_tunnel_3: print(te_tunnel_3))

Example Scapy packets:

from scapy.contrib.gtp import GTP_U_Header

tunnel = Ether(src=""3C:6D:66:11:11:11",dst="ff:ff:ff:ff:ff:ff")/IP(src="100.100.100.1", dst="100.100.100.81")/UDP(sport=2152, dport=2152)
p_l3 = IP(dst='1.2.3.4', src='4.4.4.4') / TCP(sport=100,dport=100,seq=1001,flags='S') 

gtp = tunnel / GTP_U_Header(teid=1)/ p_l3
gtp2 = tunnel / GTP_U_Header(teid=2)/ p_l3

sendp(gtp, iface="p1")
sendp(gtp, iface="enp3s0f0s0")
sendp(gtp, iface="enp3s0f0s3")
sendp(gtp2, iface="p1")

Expected results:

  • Packet gtp is sent on port 0 and is received on port 1 with UDP src port of 1234

  • Packet gtp is sent on port 1 and is received on port 0 with UDP src port of 1234

  • Packet gtp is sent on port 4 and is dropped

  • Packet gtp2 is sent on port 0 and is received on port 4 unmodified


See below for the complete DPL example.

C
/*
 * SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 * SPDX-License-Identifier: LicenseRef-NvidiaProprietary
 *
 * NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
 * property and proprietary rights in and to this material, related
 * documentation and any modifications thereto. Any use, reproduction,
 * disclosure or distribution of this material and related documentation
 * without an express license agreement from NVIDIA CORPORATION or
 * its affiliates is strictly prohibited.
 */

#include <doca_model.p4>
#include <doca_headers.p4>
#include <doca_externs.p4>
#include <doca_parser.p4>

/*
 * This sample performs GTP matching using flex parser.
 * The GTP Tunnel Endpoint Identifier is used to selectively 
 * forward the packets. 
 */

const bit<32> WIRE_PORT = 32w00;
const bit<32> GTP_VPORT = 32w01;
const bit<32> DEFAULT_VPORT = 32w04;

struct metadata_t {
}

#define GTP_U_PORT 2152

header Gtp_v1_h {
    bit<3>        version;               /** For GTPv1, this has a value of 1. */
    bit           protocol_type;         /** GTP (value 1) from GTP' (value 0) */
    bit           reserved;
    bit           extension_header_flag; /** extension header optional field. */
    bit           seq_number_flag;       /** Sequence Number optional field */
    bit           n_pdu_number_flag;     /** N-PDU number optional field */
    bit<8>        message_type;          /** types of messages are defined in 3GPP TS 29.060 section 7.1 */
    bit<16>       message_length;        /** length of the payload in bytes */
    bit<32>       teid;                  /** Tunnel endpoint identifier */
    bit<16>       sequence_number;       /** optional */
    bit<8>        n_pdu_number;          /** optional */
    bit<8>        next_extension_hdr_type; /** optional if any of the E, S, or PN bits are on. The field must be interpreted only if the E bit is on */
}

struct headers_t {
    NV_FIXED_HEADERS
    Gtp_v1_h   gtpv1;
}

parser packet_parser(packet_in packet, out headers_t headers) {
    NV_FIXED_PARSER(packet, headers)

    @nv_transition_from("nv_parse_udp", GTP_U_PORT)
    state parse_gtp
    {
        packet.extract(headers.gtpv1);
        transition accept;
    }
}

/**
 * This control admits GTP packets only if the tunnel ID matches
 *
 */
 control gtp_tunnel(
    inout headers_t headers,
    in nv_standard_metadata_t std_meta,
    inout metadata_t user_meta,
    inout nv_empty_metadata_t pkt_out_meta
) {
    NvDirectCounter(NvCounterType.PACKETS_AND_BYTES) gtp_counter;

    action drop() {
        nv_drop();
    }

    action send_to_port_gtp_table(nv_logical_port_t port) {
        gtp_counter.count();
        nv_set_l4_src_port(headers, 1234);
        nv_send_to_port(port);
    }

    action drop_gtp_table() {
        gtp_counter.count();
        nv_drop();
    }

    table gtp_table {
        key = {
            std_meta.ingress_port: exact;
            headers.gtpv1.teid: exact;
        }
        actions = {
            send_to_port_gtp_table;
            drop_gtp_table;
            NoAction;
        }
        default_action = NoAction;
        direct_counter = gtp_counter;
    }

    apply {
        if (headers.gtpv1.isValid()) {
            if (gtp_table.apply().miss) {
                nv_send_to_port(DEFAULT_VPORT);
            }
        }
        drop();
    }
}

NvDocaPipeline(
    packet_parser(),
    gtp_tunnel()
) main;



Last updated: