DOCA Documentation v3.0.0 July 2025 VGT Update

Geneve TLV Parsing Example

This example illustrates how to write a basic VXLAN tunnel gateway using the DOCA target architecture. A tunnel gateway allows programmatic control over how VXLAN traffic can be "stitched" packets across tenant domains. In this example, end point traffic destined to local bare metal hosts can be decapsulated and forwarded to a VF, while gateway traffic can be decapsulated, re-encapsulated and sent back to the wire. For example, this program can be easily extended to be a gateway connecting legacy NVGRE networks to a VXLAN-GPE network.

Tunnel_Gateway_Pipeline_(1)-version-1-modificationdate-1751919096493-api-v2.png

This example users the native parser and 2 tables, of size 32K each. The wire port is configured with P4 port ID 0. A single bit in the user metadata structure is used to keep the decapsulation state.

Copy
Copied!
            

#include <doca_model.p4> #include <doca_headers.p4> #include <doca_externs.p4> #include <doca_parser.p4>   /* * Table sizes. */ const bit<32> DECAP_TABLE_SIZE = 32768; const bit<32> ENCAP_TABLE_SIZE = 32768;   /* The directionality is based on network to host * The user will configure the P4 port IDs in the OVS configuration */ const bit<32> WIRE_PORT = 32w0;   struct metadata_t { bit<1> was_decapped; }   struct headers_t { NV_FIXED_HEADERS }   parser packet_parser(packet_in packet, out headers_t headers) { NV_FIXED_PARSER(packet, headers) }

The encapsulation control has a single table, matching on IPv4 destination address. If an entry matches, the packet is VXLAN encapsulated and forwarded to the specified port. If the packet does not hit any entry, then the packet is dropped. It is simple to add more complex policy rules such as a 5 tuple ACL.

Copy
Copied!
            

/* * This control performs the overlay policy including L2 encap with VXLAN */ control overlay_encap( 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) encap_counter;   action deny() { nv_drop(); }   action vxlan_v4_encap(nv_mac_addr_t underlay_src_mac, nv_mac_addr_t underlay_dst_mac, nv_ipv4_addr_t underlay_sip, nv_ipv4_addr_t underlay_dip, bit<24> vni, nv_logical_port_t port) {        encap_counter.count();         nv_set_vxlan_v4_underlay(headers, false, underlay_dst_mac, underlay_src_mac, 0, underlay_sip, underlay_dip, vni); nv_send_to_port(port); } table encap_v4_table { key = { headers.ipv4.dst_addr : exact; } actions = { vxlan_v4_encap; deny; } size = ENCAP_TABLE_SIZE; default_action = deny; direct_counter = encap_counter; }   apply { if (headers.ipv4.isValid() && (user_meta.was_decapped == 1)) { encap_v4_table.apply(); } } }

The decapsulation control simply checks if the packet is VXLAN, and decapsulates it. From there the packet can be sent directly to a port, or hair-pinned back to the wire.

Copy
Copied!
            

/* * This control is for packets from wire to host (RX) * and includes policy for L2 decap */ control decap_flow( 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) decap_counter;   action deny() { nv_drop(); }   action decap() {        decap_counter.count();         nv_l2_decap(headers); user_meta.was_decapped = 1; }   action to_port(nv_logical_port_t port) { nv_send_to_port(port); }   action decap_to_port(nv_logical_port_t port) {        decap_counter.count();         user_meta.was_decapped = 1; nv_l2_decap(headers); nv_send_to_port(port); }   table decap_v4_table { key = { headers.vxlan.vni : exact; } actions = { decap; to_port; decap_to_port; deny; NoAction; } size = DECAP_TABLE_SIZE; direct_counter = decap_counter; default_action = deny; }   apply { if (headers.vxlan.isValid()) { decap_v4_table.apply(); } } }

The main control checks the ingress port to determine if the packet is Network to Host, or Host to Network. Depending on the direction, it applies the decap_flow control, or performs an overlay encapsulation.

Copy
Copied!
            

control gateway( inout headers_t headers, in nv_standard_metadata_t std_meta, inout metadata_t user_meta, inout nv_empty_metadata_t pkt_out_meta ) { overlay_encap() over; decap_flow() decap;   /* user should add entries that correspond to the wire ports * A hit means this is an RX packet, miss means a TX packet */ table direction_table { key = { std_meta.ingress_port : exact; } actions = { NoAction; } default_action = NoAction; const entries = { (WIRE_PORT) : NoAction(); } }   apply { user_meta.was_decapped = 0; if (direction_table.apply().hit) { decap.apply(headers, std_meta, user_meta, pkt_out_meta); } over.apply(headers, std_meta, user_meta, pkt_out_meta); } }

See below for the complete DPL example.

Copy
Copied!
            

/* * 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>   /* * VxLAN tunnel gateway * This application allows the user have a customized tunnel gateway, that can stitch VxLAN * packets across tenant domains. End point traffic destined to local bare metal hosts can * be decapsulated and forwarded, while gateway traffic can be decapsulated, then * encapsulated back to the wire. This program can be easily extended to be a gateway across * different tunnel types as well. */   /* * Table sizes. */ const bit<32> DECAP_TABLE_SIZE = 32768; const bit<32> ENCAP_TABLE_SIZE = 32768;   /* The directionality is based on network to host * The user will configure the DPL port IDs in the DPL RT configuration */ const bit<32> WIRE_PORT = 32w0;   struct metadata_t { bit<1> was_decapped; }   struct headers_t { NV_FIXED_HEADERS }   parser packet_parser(packet_in packet, out headers_t headers) { NV_FIXED_PARSER(packet, headers) }   /** * This control performs the overlay policy including L2 encap with VxLAN */ control overlay_encap( 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) encap_counter;   action deny_encap_v4_table() { encap_counter.count(); nv_drop(); }   action vxlan_v4_encap_encap_v4_table(nv_mac_addr_t underlay_src_mac, nv_mac_addr_t underlay_dst_mac, nv_ipv4_addr_t underlay_sip, nv_ipv4_addr_t underlay_dip, bit<24> vni, nv_logical_port_t port) { nv_set_vxlan_v4_underlay(headers, false, underlay_dst_mac, underlay_src_mac, 0, underlay_sip, underlay_dip, vni); encap_counter.count(); nv_send_to_port(port); } table encap_v4_table { key = { headers.ipv4.dst_addr : exact; } actions = { vxlan_v4_encap_encap_v4_table; deny_encap_v4_table; } size = ENCAP_TABLE_SIZE; default_action = deny_encap_v4_table; direct_counter = encap_counter; }   apply { if (headers.ipv4.isValid() && (user_meta.was_decapped == 1)) { encap_v4_table.apply(); } } }     /** * This control is for packets from wire to host (RX) * and includes policy for L2 decap */ control decap_flow( 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) decap_counter;   action deny_decap_v4_table() { decap_counter.count(); nv_drop(); }   action decap_decap_v4_table() { decap_counter.count(); nv_l2_decap(headers); user_meta.was_decapped = 1; }   action to_port_decap_v4_table(nv_logical_port_t port) { decap_counter.count(); nv_send_to_port(port); }   action decap_to_port_decap_v4_table(nv_logical_port_t port) { decap_counter.count(); user_meta.was_decapped = 1; nv_l2_decap(headers); nv_send_to_port(port); }   action no_action_decap_v4_table() { decap_counter.count(); }   table decap_v4_table { key = { headers.vxlan.vni : exact; } actions = { decap_decap_v4_table; to_port_decap_v4_table; decap_to_port_decap_v4_table; deny_decap_v4_table; no_action_decap_v4_table; } size = DECAP_TABLE_SIZE; direct_counter = decap_counter; default_action = deny_decap_v4_table; }   apply { if (headers.vxlan.isValid()) { decap_v4_table.apply(); } } }   control gateway( inout headers_t headers, in nv_standard_metadata_t std_meta, inout metadata_t user_meta, inout nv_empty_metadata_t pkt_out_meta ) { overlay_encap() over; decap_flow() decap;   /* user should add entries that correspond to the wire ports * A hit means this is an RX packet, miss means a TX packet */ table direction_table { key = { std_meta.ingress_port : exact; } actions = { NoAction; } default_action = NoAction; const entries = { (WIRE_PORT) : NoAction(); } }   apply { user_meta.was_decapped = 0; if (direction_table.apply().hit) { decap.apply(headers, std_meta, user_meta, pkt_out_meta); } else { over.apply(headers, std_meta, user_meta, pkt_out_meta); } } }   NvDocaPipeline( packet_parser(), gateway() ) main;

© Copyright 2025, NVIDIA. Last updated on Jul 15, 2025.