What can I help you with?
DOCA Documentation v3.0.0

NAT64 Example

Network Address Translation (NAT64) is a network address translation mechanism designed to enable connections between IPv6 clients and IPv4 servers. NAT64 serves as a gateway, allowing devices on IPv6-only networks to access resources that are available over IPv4. Unlike RFC 6052, which uses an algorithmic mapping of IPv6 prefix to embed IPv4 addresses within IPv6 addresses, this example demonstrates Stateful NAT64 where the gateway maintains a mapping table for active flows. The mapping table maintains a per entry user policy.

When translating an IP packet, various packet fields can be saved between IPv6 and IPv4. To accommodate this, create a metadata structure to hold these temporary values:

Copy
Copied!
            

struct ip_data_t { bit<16> packet_len_field; bit<8> ttl; bit<6> tos_field; bit<2> ecn_field; bit<8> ip_protocol; }

Next, instantiate a NvHeaderDataTemplateobject. This object is used to define the header and field values that will be pushed into the packet. The template requires two items:

  • A struct containing the set of headers that will be pushed. The headers are pushed in the order in which they appear in the struct, and can be natively supported headers for custom headers.

  • An annotation @nv_header_data_fields on the NvHeaderDataTemplateobject that serves as an initializer to the struct of headers. Each header must be initialized in the order in which they appear in the structure, and each header field must be initialized in the order in which they appear in the header.

Copy
Copied!
            

struct v4_headers_t { nv_ipv4_h nat_v4; }   control nat64( inout nv_headers_t headers, in nv_standard_metadata_t std_meta, inout ip_data_t user_meta, inout nv_empty_metadata_t pkt_out_meta ) { @nv_header_data_fields( nat_v4 = { version = 4, ihl = 5, diffserv = 0, ecn = 0, total_len = 0, identification = 0, flags = 2, // Do not fragment frag_offset = 0, ttl = 64, protocol = 6, hdr_checksum = 0xf93a, // Prealculated CRC based on this pattern data src_addr = 0,          // must be zero for TCP pseudo-header CRC             dst_addr = 0, } ) NvHeaderDataTemplate<v4_headers_t>() v4DataTemplate;

The P4 action contains the logical steps to translate the incoming IPv6 packet to an IPv4 packet. The steps are:

  1. Save the necessary IPv6 fields in the user_metadata struct, such as the TTL and DSCP fields. These will be restored later.

  2. Calculate the new packet length field.

  3. Push the new IPv4 header, with mostly empty fields. The IPv4 addresses are set to zero for the pseudo-header CRC calculation. Since the push operation leaves the packet in an intermediate state, set the do_reparse = false so that the offsets for IP_START_OF_HEADER are not updated yet.

  4. Pop the IPv6 header with packet updates and reparse disabled.

  5. Change the L2 ethertype to IPv4 and trigger a reparse now the packet header rearrangement is complete.

  6. Copy the user_metadata to the new IPv4 header, and set the source and destination IP addresses.

Copy
Copied!
            

action translate64(nv_ipv4_addr_t src_ip, nv_ipv4_addr_t dst_ip) { ip64_counter.count(); user_meta.packet_len_field = headers.ipv6.payload_length; user_meta.packet_len_field += 20; user_meta.ip_protocol = headers.ipv6.next_header; user_meta.ttl = headers.ipv6.hop_limit; user_meta.tos_field = headers.ipv6.diffserv; user_meta.ecn_field = headers.ipv6.ecn; // partial TCP pseudo-header CRC calculation headers.ipv6.src_addr = 0; headers.ipv6.dst_addr = 0; // push a mostly empty IPv4 header with the partial checksum nv_push_headers(headers, v4DataTemplate, { }, NvAnchor.IP_START_OF_HEADER, 0, false, false); // remove IPv6 header without updates or reparse as packet is in mid-modification nv_pop_headers(headers, NvAnchor.IP_START_OF_HEADER, NvAnchor.L4_START_OF_HEADER, false, false); // change the ethertype to IPv4 nv_set_l2_ethertype(headers, 16w0x0800); // Trigger reparse nv_force_reparse(headers); // restore the remaining IP fields     headers.ipv4.src_addr = src_ip; headers.ipv4.dst_addr = dst_ip; headers.ipv4.ttl = user_meta.ttl; headers.ipv4.total_len = user_meta.packet_len_field; nv_set_ip_protocol(headers, user_meta.ip_protocol); nv_set_ip_ecn(headers, user_meta.ecn_field); nv_set_ip_dscp(headers, user_meta.tos_field); }

Based on the desired policy, a P4 table with the desired keyset is defined. In the apply block, the packet is determined to be a valid IPv6 packet. The table is applied during the final step; when a match is found, NAT64 is performed, and the packet is sent to a specific port.

Copy
Copied!
            

table ipv6_to_ipv4 { key = { headers.ethernet.src_addr : exact; } actions = { translate64; NoAction; } default_action = NoAction; direct_counter = ip64_counter; }   apply { if (std_meta.is_l4_ok == 1) { if (ipv6_to_ipv4.apply().hit) { nv_send_to_port(3); } } }

Info

For more information, refer to the full DPL example, nat64.p4.

© Copyright 2025, NVIDIA. Last updated on May 5, 2025.