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:
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 NvHeaderDataTemplate
object. 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 thestruct
, and can be natively supported headers for custom headers.An annotation
@nv_header_data_fields
on theNvHeaderDataTemplate
object that serves as an initializer to thestruct
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.
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:
Save the necessary IPv6 fields in the
user_metadata
struct
, such as the TTL and DSCP fields. These will be restored later.Calculate the new packet length field.
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 forIP_START_OF_HEADER
are not updated yet.Pop the IPv6 header with packet updates and reparse disabled.
Change the L2 ethertype to IPv4 and trigger a reparse now the packet header rearrangement is complete.
Copy the
user_metadata
to the new IPv4 header, and set the source and destination IP addresses.
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.
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);
}
}
}
For more information, refer to the full DPL example, nat64.p4.