/* * SPDX-FileCopyrightText: Copyright (c) 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 #include #include #include /* * This basic application demonstrates a NAT from IPv6 to IPv4 and IPv4 to IPv6. * Unlike RFC 6052, this version of NAT64 is dynamic in the sense that it varies * per entry depending on the user's policy. */ struct ip_data_t { bit<16> packet_len_field; bit<8> ttl; bit<6> tos_field; bit<2> ecn_field; bit<8> ip_protocol; } struct v4_headers_t { nv_ipv4_h nat_v4; } struct v6_headers_t { nv_ipv6_h nat_v6; } 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 ) { NvDirectCounter(NvCounterType.PACKETS_AND_BYTES) ip64_counter; @nv_header_data_fields( nat_v4 = { // WORD 1 version = 4, ihl = 5, diffserv = 0, ecn = 0, // WORD 2 total_len = 0, // WORD 3 identification = 0, // WORD 4 flags = 2, // Do not fragment frag_offset = 0, // WORD 5 ttl = 64, protocol = 6, // WORD 6 hdr_checksum = 0xf93a, // Prealculated CRC based on this pattern data // WORD 7 src_addr = 0, // must be zero for TCP pseudo-header CRC // WORD 9 dst_addr = 0, // must be zero for TCP pseudo-header CRC } ) NvHeaderDataTemplate() v4DataTemplate; action drop() { nv_drop(); } 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); } 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); } } } } control nat46( 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 ) { NvDirectCounter(NvCounterType.PACKETS_AND_BYTES) ip46_counter; @nv_header_data_fields( nat_v6 = { // WORD 1 version = 6, diffserv = 5, ecn = 0, flow_label = 0, // WORD 3 payload_length = 0, // WORD 4 next_header = 6, hop_limit = 64, // WORD 5 src_addr = 0, // must be zero for TCP pseudo-header CRC // WORD 9 dst_addr = 0, // must be zero for TCP pseudo-header CRC } ) NvHeaderDataTemplate() v6DataTemplate; action drop() { nv_drop(); } action translate46(nv_ipv6_addr_t src_ip, nv_ipv6_addr_t dst_ip) { ip46_counter.count(); user_meta.packet_len_field = headers.ipv4.total_len; user_meta.packet_len_field -= 20; user_meta.ip_protocol = headers.ipv4.protocol; user_meta.ttl = headers.ipv4.ttl; user_meta.tos_field = headers.ipv4.diffserv; user_meta.ecn_field = headers.ipv4.ecn; // partial TCP pseudo-header CRC calculation headers.ipv4.src_addr = 0; headers.ipv4.dst_addr = 0; // push a mostly empty IPv6 header and with the partial checksum nv_push_headers(headers, v6DataTemplate, { }, NvAnchor.IP_START_OF_HEADER, 0, false, false); // remove IPv4 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 IPv6 nv_set_l2_ethertype(headers, 16w0x86dd); // Trigger reparse nv_force_reparse(headers); // restore the remaining IP fields headers.ipv6.src_addr = src_ip; headers.ipv6.dst_addr = dst_ip; headers.ipv6.hop_limit = user_meta.ttl; headers.ipv6.payload_length = 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); } table ipv4_to_ipv6 { key = { headers.ethernet.src_addr : exact; } actions = { translate46; NoAction; } default_action = NoAction; direct_counter = ip46_counter; } apply { if (std_meta.is_l4_ok == 1) { if (ipv4_to_ipv6.apply().hit) { nv_send_to_port(3); } } } } control nat(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) { nat64() v6_to_v4; nat46() v4_to_v6; apply { if (headers.ipv6.isValid()) { v6_to_v4.apply(headers, std_meta, user_meta, pkt_out_meta); nv_send_to_port(2); } else if (headers.ipv4.isValid()) { v4_to_v6.apply(headers, std_meta, user_meta, pkt_out_meta); nv_send_to_port(2); } // unknown traffic nv_send_to_port(1); } } NvDocaPipeline( nv_fixed_parser(), nat() ) main;