#!/usr/bin/env python import socket import unittest import struct import random from framework import VppTestCase, VppTestRunner, running_extended_tests import scapy.compat from scapy.layers.inet import IP, TCP, UDP, ICMP from scapy.layers.inet import IPerror, TCPerror, UDPerror, ICMPerror from scapy.layers.inet6 import IPv6, ICMPv6EchoRequest, ICMPv6EchoReply, \ ICMPv6ND_NS, ICMPv6ND_NA, ICMPv6NDOptDstLLAddr, fragment6 from scapy.layers.inet6 import ICMPv6DestUnreach, IPerror6, IPv6ExtHdrFragment from scapy.layers.l2 import Ether, ARP, GRE from scapy.data import IP_PROTOS from scapy.packet import bind_layers, Raw from util import ppp from ipfix import IPFIX, Set, Template, Data, IPFIXDecoder from time import sleep from util import ip4_range from vpp_papi import mac_pton from syslog_rfc5424_parser import SyslogMessage, ParseError from syslog_rfc5424_parser.constants import SyslogFacility, SyslogSeverity from io import BytesIO from vpp_papi import VppEnum from vpp_ip_route import VppIpRoute, VppRoutePath, FibPathType from vpp_neighbor import VppNeighbor from scapy.all import bind_layers, Packet, ByteEnumField, ShortField, \ IPField, IntField, LongField, XByteField, FlagsField, FieldLenField, \ PacketListField from ipaddress import IPv6Network # NAT HA protocol event data class Event(Packet): name = "Event" fields_desc = [ByteEnumField("event_type", None, {1: "add", 2: "del", 3: "refresh"}), ByteEnumField("protocol", None, {0: "udp", 1: "tcp", 2: "icmp"}), ShortField("flags", 0), IPField("in_addr", None), IPField("out_addr", None), ShortField("in_port", None), ShortField("out_port", None), IPField("eh_addr", None), IPField("ehn_addr", None), ShortField("eh_port", None), ShortField("ehn_port", None), IntField("fib_index", None), IntField("total_pkts", 0), LongField("total_bytes", 0)] def extract_padding(self, s): return "", s # NAT HA protocol header class HANATStateSync(Packet): name = "HA NAT state sync" fields_desc = [XByteField("version", 1), FlagsField("flags", 0, 8, ['ACK']), FieldLenField("count", None, count_of="events"), IntField("sequence_number", 1), IntField("thread_index", 0), PacketListField("events", [], Event, count_from=lambda pkt: pkt.count)] class MethodHolder(VppTestCase): """ NAT create capture and verify method holder """ @property def config_flags(self): return VppEnum.vl_api_nat_config_flags_t @property def SYSLOG_SEVERITY(self): return VppEnum.vl_api_syslog_severity_t def clear_nat44(self): """ Clear NAT44 configuration. """ if hasattr(self, 'pg7') and hasattr(self, 'pg8'): if self.pg7.has_ip4_config: self.pg7.unconfig_ip4() self.vapi.nat44_forwarding_enable_disable(enable=0) interfaces = self.vapi.nat44_interface_addr_dump() for intf in interfaces: self.vapi.nat44_add_del_interface_addr( is_add=0, sw_if_index=intf.sw_if_index, flags=intf.flags) self.vapi.nat_ipfix_enable_disable(domain_id=self.ipfix_domain_id, src_port=self.ipfix_src_port, enable=0) self.ipfix_src_port = 4739 self.ipfix_domain_id = 1 self.vapi.syslog_set_filter( self.SYSLOG_SEVERITY.SYSLOG_API_SEVERITY_EMERG) self.vapi.nat_ha_set_listener(ip_address='0.0.0.0', port=0, path_mtu=512) self.vapi.nat_ha_set_failover(ip_address='0.0.0.0', port=0, session_refresh_interval=10) interfaces = self.vapi.nat44_interface_dump() for intf in interfaces: if intf.flags & self.config_flags.NAT_IS_INSIDE and \ intf.flags & self.config_flags.NAT_IS_OUTSIDE: self.vapi.nat44_interface_add_del_feature( sw_if_index=intf.sw_if_index) self.vapi.nat44_interface_add_del_feature( sw_if_index=intf.sw_if_index, flags=intf.flags) interfaces = self.vapi.nat44_interface_output_feature_dump() for intf in interfaces: self.vapi.nat44_interface_add_del_output_feature( is_add=0, flags=intf.flags, sw_if_index=intf.sw_if_index) static_mappings = self.vapi.nat44_static_mapping_dump() for sm in static_mappings: self.vapi.nat44_add_del_static_mapping( is_add=0, local_ip_address=sm.local_ip_address, external_ip_address=sm.external_ip_address, external_sw_if_index=sm.external_sw_if_index, local_port=sm.local_port, external_port=sm.external_port, vrf_id=sm.vrf_id, protocol=sm.protocol, flags=sm.flags, tag=sm.tag) lb_static_mappings = self.vapi.nat44_lb_static_mapping_dump() for lb_sm in lb_static_mappings: self.vapi.nat44_add_del_lb_static_mapping( is_add=0, flags=lb_sm.flags, external_addr=lb_sm.external_addr, external_port=lb_sm.external_port, protocol=lb_sm.protocol, local_num=0, locals=[], tag=lb_sm.tag) identity_mappings = self.vapi.nat44_identity_mapping_dump() for id_m in identity_mappings: self.vapi.nat44_add_del_identity_mapping( ip_address=id_m.ip_address, sw_if_index=id_m.sw_if_index, port=id_m.port, flags=id_m.flags, vrf_id=id_m.vrf_id, protocol=id_m.protocol) addresses = self.vapi.nat44_address_dump() for addr in addresses: self.vapi.nat44_add_del_address_range( first_ip_address=addr.ip_address, last_ip_address=addr.ip_address, vrf_id=0xFFFFFFFF, flags=addr.flags) self.vapi.nat_set_reass(timeout=2, max_reass=1024, max_frag=5, drop_frag=0) self.vapi.nat_set_reass(timeout=2, max_reass=1024, max_frag=5, drop_frag=0, is_ip6=1) self.verify_no_nat44_user() self.vapi.nat_set_timeouts(udp=300, tcp_established=7440, tcp_transitory=240, icmp=60) self.vapi.nat_set_addr_and_port_alloc_alg() self.vapi.nat_set_mss_clamping(enable=0, mss_value=1500) def nat44_add_static_mapping(self, local_ip, external_ip='0.0.0.0', local_port=0, external_port=0, vrf_id=0, is_add=1, external_sw_if_index=0xFFFFFFFF, proto=0, tag="", flags=0): """ Add/delete NAT44 static mapping :param local_ip: Local IP address :param external_ip: External IP address :param local_port: Local port number (Optional) :param external_port: External port number (Optional) :param vrf_id: VRF ID (Default 0) :param is_add: 1 if add, 0 if delete (Default add) :param external_sw_if_index: External interface instead of IP address :param proto: IP protocol (Mandatory if port specified) :param tag: Opaque string tag :param flags: NAT configuration flags """ if not (local_port and external_port): flags |= self.config_flags.NAT_IS_ADDR_ONLY self.vapi.nat44_add_del_static_mapping( is_add=is_add, local_ip_address=local_ip, external_ip_address=external_ip, external_sw_if_index=external_sw_if_index, local_port=local_port, external_port=external_port, vrf_id=vrf_id, protocol=proto, flags=flags, tag=tag) def nat44_add_address(self, ip, is_add=1, vrf_id=0xFFFFFFFF, twice_nat=0): """ Add/delete NAT44 address :param ip: IP address :param is_add: 1 if add, 0 if delete (Default add) :param twice_nat: twice NAT address for external hosts """ flags = self.config_flags.NAT_IS_TWICE_NAT if twice_nat else 0 self.vapi.nat44_add_del_address_range(first_ip_address=ip, last_ip_address=ip, vrf_id=vrf_id, is_add=is_add, flags=flags) def create_stream_in(self, in_if, out_if, dst_ip=None, ttl=64): """ Create packet stream for inside network :param in_if: Inside interface :param out_if: Outside interface :param dst_ip: Destination address :param ttl: TTL of generated packets """ if dst_ip is None: dst_ip = out_if.remote_ip4 pkts = [] # TCP p = (Ether(dst=in_if.local_mac, src=in_if.remote_mac) / IP(src=in_if.remote_ip4, dst=dst_ip, ttl=ttl) / TCP(sport=self.tcp_port_in, dport=20)) pkts.append(p) # UDP p = (Ether(dst=in_if.local_mac, src=in_if.remote_mac) / IP(src=in_if.remote_ip4, dst=dst_ip, ttl=ttl) / UDP(sport=self.udp_port_in, dport=20)) pkts.append(p) # ICMP p = (Ether(dst=in_if.local_mac, src=in_if.remote_mac) / IP(src=in_if.remote_ip4, dst=dst_ip, ttl=ttl) / ICMP(id=self.icmp_id_in, type='echo-request')) pkts.append(p) return pkts def compose_ip6(self, ip4, pref, plen): """ Compose IPv4-embedded IPv6 addresses :param ip4: IPv4 address :param pref: IPv6 prefix :param plen: IPv6 prefix length :returns: IPv4-embedded IPv6 addresses """ pref_n = list(socket.inet_pton(socket.AF_INET6, pref)) ip4_n = list(socket.inet_pton(socket.AF_INET, ip4)) if plen == 32: pref_n[4] = ip4_n[0] pref_n[5] = ip4_n[1] pref_n[6] = ip4_n[2] pref_n[7] = ip4_n[3] elif plen == 40: pref_n[5] = ip4_n[0] pref_n[6] = ip4_n[1] pref_n[7] = ip4_n[2] pref_n[9] = ip4_n[3] elif plen == 48: pref_n[6] = ip4_n[0] pref_n[7] = ip4_n[1] pref_n[9] = ip4_n[2] pref_n[10] = ip4_n[3] elif plen == 56: pref_n[7] = ip4_n[0] pref_n[9] = ip4_n[1] pref_n[10] = ip4_n[2] pref_n[11] = ip4_n[3] elif plen == 64: pref_n[9] = ip4_n[0] pref_n[10] = ip4_n[1] pref_n[11] = ip4_n[2] pref_n[12] = ip4_n[3] elif plen == 96: pref_n[12] = ip4_n[0] pref_n[13] = ip4_n[1] pref_n[14] = ip4_n[2] pref_n[15] = ip4_n[3] packed_pref_n = b''.join([scapy.compat.chb(x) for x in pref_n]) return socket.inet_ntop(socket.AF_INET6, packed_pref_n) def extract_ip4(self, ip6, plen): """ Extract IPv4 address embedded in IPv6 addresses :param ip6: IPv6 address :param plen: IPv6 prefix length :returns: extracted IPv4 address """ ip6_n = list(socket.inet_pton(socket.AF_INET6, ip6)) ip4_n = [None] * 4 if plen == 32: ip4_n[0] = ip6_n[4] ip4_n[1] = ip6_n[5] ip4_n[2] = ip6_n[6] ip4_n[3] = ip6_n[7] elif plen == 40: ip4_n[0] = ip6_n[5] ip4_n[1] = ip6_n[6] ip4_n[2] = ip6_n[7] ip4_n[3] = ip6_n[9] elif plen == 48: ip4_n[0] = ip6_n[6] ip4_n[1] = ip6_n[7] ip4_n[2] = ip6_n[9] ip4_n[3] = ip6_n[10] elif plen == 56: ip4_n[0] = ip6_n[7] ip4_n[1] = ip6_n[9] ip4_n[2] = ip6_n[10] ip4_n[3] = ip6_n[11] elif plen == 64: ip4_n[0] = ip6_n[9] ip4_n[1] = ip6_n[10] ip4_n[2] = ip6_n[11] ip4_n[3] = ip6_n[12] elif plen == 96: ip4_n[0] = ip6_n[12] ip4_n[1] = ip6_n[13] ip4_n[2] = ip6_n[14] ip4_n[3] = ip6_n[15] return socket.inet_ntop(socket.AF_INET, ''.join(ip4_n)) def create_stream_in_ip6(self, in_if, out_if, hlim=64, pref=None, plen=0): """ Create IPv6 packet stream for inside network :param in_if: Inside interface :param out_if: Outside interface :param ttl: Hop Limit of generated packets :param pref: NAT64 prefix :param plen: NAT64 prefix length """ pkts = [] if pref is None: dst = ''.join(['64:ff9b::', out_if.remote_ip4]) else: dst = self.compose_ip6(out_if.remote_ip4, pref, plen) # TCP p = (Ether(dst=in_if.local_mac, src=in_if.remote_mac) / IPv6(src=in_if.remote_ip6, dst=dst, hlim=hlim) / TCP(sport=self.tcp_port_in, dport=20)) pkts.append(p) # UDP p = (Ether(dst=in_if.local_mac, src=in_if.remote_mac) / IPv6(src=in_if.remote_ip6, dst=dst, hlim=hlim) / UDP(sport=self.udp_port_in, dport=20)) pkts.append(p) # ICMP p = (Ether(dst=in_if.local_mac, src=in_if.remote_mac) / IPv6(src=in_if.remote_ip6, dst=dst, hlim=hlim) / ICMPv6EchoRequest(id=self.icmp_id_in)) pkts.append(p) return pkts def create_stream_out(self, out_if, dst_ip=None, ttl=64, use_inside_ports=False): """ Create packet stream for outside network :param out_if: Outside interface :param dst_ip: Destination IP address (Default use global NAT address) :param ttl: TTL of generated packets :param use_inside_ports: Use inside NAT ports as destination ports instead of outside ports """ if dst_ip is None: dst_ip = self.nat_addr if not use_inside_ports: tcp_port = self.tcp_port_out udp_port = self.udp_port_out icmp_id = self.icmp_id_out else: tcp_port = self.tcp_port_in udp_port = self.udp_port_in icmp_id = self.icmp_id_in pkts = [] # TCP p = (Ether(dst=out_if.local_mac, src=out_if.remote_mac) / IP(src=out_if.remote_ip4, dst=dst_ip, ttl=ttl) / TCP(dport=tcp_port, sport=20)) pkts.append(p) # UDP p = (Ether(dst=out_if.local_mac, src=out_if.remote_mac) / IP(src=out_if.remote_ip4, dst=dst_ip, ttl=ttl) / UDP(dport=udp_port, sport=20)) pkts.append(p) # ICMP p = (Ether(dst=out_if.local_mac, src=out_if.remote_mac) / IP(src=out_if.remote_ip4, dst=dst_ip, ttl=ttl) / ICMP(id=icmp_id, type='echo-reply')) pkts.append(p) return pkts def create_stream_out_ip6(self, out_if, src_ip, dst_ip, hl=64): """ Create packet stream for outside network :param out_if: Outside interface :param dst_ip: Destination IP address (Default use global NAT address) :param hl: HL of generated packets """ pkts = [] # TCP p = (Ether(dst=out_if.local_mac, src=out_if.remote_mac) / IPv6(src=src_ip, dst=dst_ip, hlim=hl) / TCP(dport=self.tcp_port_out, sport=20)) pkts.append(p) # UDP p = (Ether(dst=out_if.local_mac, src=out_if.remote_mac) / IPv6(src=src_ip, dst=dst_ip, hlim=hl) / UDP(dport=self.udp_port_out, sport=20)) pkts.append(p) # ICMP p = (Ether(dst=out_if.local_mac, src=out_if.remote_mac) / IPv6(src=src_ip, dst=dst_ip, hlim=hl) / ICMPv6EchoReply(id=self.icmp_id_out)) pkts.append(p) return pkts def verify_capture_out(self, capture, nat_ip=None, same_port=False, dst_ip=None, is_ip6=False):
# Copyright (c) 2023 Cisco and/or its affiliates.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
*** Settings ***
| Resource | resources/libraries/robot/shared/default.robot
|
| Force Tags | 2_NODE_SINGLE_LINK_TOPO | PERFTEST | HW_ENV | NDRPDR
| ... | NIC_Intel-X710 | DOT1Q | L2BDMACLRN | BASE | VIRTIO | 1VM
| ... | VIRTIO_1024 | NF_VPPL2XC_VIRTIO | DRV_VFIO_PCI
| ... | RXQ_SIZE_0 | TXQ_SIZE_0
| ... | dot1q-l2bdbasemaclrn-eth-2virtiovr1024-1vm-vppl2xc
|
| Suite Setup | Setup suite topology interfaces | performance
| Suite Teardown | Tear down suite | performance
| Test Setup | Setup test | performance
| Test Teardown | Tear down test | performance | vhost
|
| Test Template | Local Template
|
| Documentation | **RFC2544: Pkt throughput L2BD with vhost and IEEE 802.1Q \
| ... | test cases**
| ... |
| ... | - **[Top] Network Topologies:** TG-DUT1-TG 2-node circular topology \
| ... | with single links between nodes.
| ... |
| ... | - **[Enc] Packet Encapsulations:** Eth-IPv4 for L2 switching of IPv4. \
| ... | IEEE 802.1Q tagging is applied on link between DUT1-if2 and TG-if2.
| ... |
| ... | - **[Cfg] DUT configuration:** DUT1 is configured with L2 bridge-\
| ... | domain and \
| ... | MAC learning enabled. Qemu VNFs are connected to VPP via \
| ... | vhost-user interfaces. Guest is running VPP l2xc interconnecting \
| ... | virtio interfaces, rxd/txd=1024. DUT1 is tested with ${nic_name}.
| ... |
| ... | - **[Ver] TG verification:** TG finds and reports throughput NDR (Non \
| ... | Drop Rate) with zero packet loss tolerance and throughput PDR \
| ... | (Partial Drop Rate) with non-zero packet loss tolerance (LT) \
| ... | expressed in percentage of packets transmitted. NDR and PDR are \
| ... | discovered for different Ethernet L2 frame sizes using MLRsearch \
| ... | library.
| ... | Test packets are generated by TG on \
| ... | links to DUTs. TG traffic profile contains two L3 flow-groups \
| ... | (flow-group per direction, 254 flows per flow-group) with all packets \
| ... | containing Ethernet header, IPv4 header with IP protocol=61 and static \
| ... | payload. MAC addresses are matching MAC addresses of NFs nodes \
| ... | interfaces.
| ... |
| ... | - **[Ref] Applicable standard specifications:** RFC2544.
*** Variables ***
| @{plugins_to_enable}= | dpdk_plugin.so | perfmon_plugin.so | vhost_plugin.so
| ${crypto_type}= | ${None}
| ${nic_name}= | Intel-X710
| ${nic_driver}= | vfio-pci
| ${nic_rxq_size}= | 0
| ${nic_txq_size}= | 0
| ${nic_pfs}= | 2
| ${nic_vfs}= | 0
| ${osi_layer}= | L2
| ${overhead}= | ${4}
| ${subid}= | 10
| ${tag_rewrite}= | pop-1
| ${bd_id1}= | 1
| ${bd_id2}= | 2
| ${nf_dtcr}= | ${1}
| ${nf_dtc}= | ${1}
| ${nf_chains}= | ${1}
| ${nf_nodes}= | ${1}
# Traffic profile:
| ${traffic_profile}= | trex-stl-2n-dot1qip4asym-ip4src254
*** Keywords ***
| Local Template
| | [Documentation]
| | ... | - **[Cfg]** Each DUT runs L2BD switching with VLAN and uses \
| | ... | ${phy_cores} physical core(s) for worker threads.
| | ... | - **[Ver]** Measure NDR and PDR values using MLRsearch algorithm.
| |
| | ... | *Arguments:*
| | ... | - frame_size - Framesize in Bytes in integer or string (IMIX_v4_1).
| | ... | Type: integer, string
| | ... | - phy_cores - Number of physical cores. Type: integer
| | ... | - rxq - Number of RX queues, default value: ${None}. Type: integer
| |
| | [Arguments] | ${frame_size} | ${phy_cores} | ${rxq}=${None}
| |
| | Set Test Variable | \${frame_size}
| |
| | Given Set Max Rate And Jumbo
| | And Add worker threads to all DUTs | ${phy_cores} | ${rxq}
| | And Pre-initialize layer driver | ${nic_driver}
| | And Apply startup configuration on all VPP DUTs
| | When Initialize layer driver | ${nic_driver}
| | And Initialize layer interface
| | And Initialize L2 bridge domains with Vhost-User and VLAN in circular topology
| | ... | ${bd_id1} | ${bd_id2} | ${subid} | ${tag_rewrite}
| | And Configure chains of NFs connected via vhost-user
| | ... | nf_chains=${nf_chains} | nf_nodes=${nf_nodes} | jumbo=${jumbo}
| | ... | use_tuned_cfs=${False} | auto_scale=${True}
| | ... | vnf=vppl2xc_2virtiovr1024
| | Then Find NDR and PDR intervals using optimized search
*** Test Cases ***
| 64B-1c-dot1q-l2bdbasemaclrn-eth-2virtiovr1024-1vm-vppl2xc-ndrpdr
| | [Tags] | 64B | 1C
| | frame_size=${64} | phy_cores=${1}
| 64B-2c-dot1q-l2bdbasemaclrn-eth-2virtiovr1024-1vm-vppl2xc-ndrpdr
| | [Tags] | 64B | 2C
| | frame_size=${64} | phy_cores=${2}
| 64B-4c-dot1q-l2bdbasemaclrn-eth-2virtiovr1024-1vm-vppl2xc-ndrpdr
| | [Tags] | 64B | 4C
| | frame_size=${64} | phy_cores=${4}
| 1518B-1c-dot1q-l2bdbasemaclrn-eth-2virtiovr1024-1vm-vppl2xc-ndrpdr
| | [Tags] | 1518B | 1C
| | frame_size=${1518} | phy_cores=${1}
| 1518B-2c-dot1q-l2bdbasemaclrn-eth-2virtiovr1024-1vm-vppl2xc-ndrpdr
| | [Tags] | 1518B | 2C
| | frame_size=${1518} | phy_cores=${2}
| 1518B-4c-dot1q-l2bdbasemaclrn-eth-2virtiovr1024-1vm-vppl2xc-ndrpdr
| | [Tags] | 1518B | 4C
| | frame_size=${1518} | phy_cores=${4}
| 9000B-1c-dot1q-l2bdbasemaclrn-eth-2virtiovr1024-1vm-vppl2xc-ndrpdr
| | [Tags] | 9000B | 1C
| | frame_size=${9000} | phy_cores=${1}
| 9000B-2c-dot1q-l2bdbasemaclrn-eth-2virtiovr1024-1vm-vppl2xc-ndrpdr
| | [Tags] | 9000B | 2C
| | frame_size=${9000} | phy_cores=${2}
| 9000B-4c-dot1q-l2bdbasemaclrn-eth-2virtiovr1024-1vm-vppl2xc-ndrpdr
| | [Tags] | 9000B | 4C
| | frame_size=${9000} | phy_cores=${4}
| IMIX-1c-dot1q-l2bdbasemaclrn-eth-2virtiovr1024-1vm-vppl2xc-ndrpdr
| | [Tags] | IMIX | 1C
| | frame_size=IMIX_v4_1 | phy_cores=${1}
| IMIX-2c-dot1q-l2bdbasemaclrn-eth-2virtiovr1024-1vm-vppl2xc-ndrpdr
| | [Tags] | IMIX | 2C
| | frame_size=IMIX_v4_1 | phy_cores=${2}
| IMIX-4c-dot1q-l2bdbasemaclrn-eth-2virtiovr1024-1vm-vppl2xc-ndrpdr
| | [Tags] | IMIX | 4C
| | frame_size=IMIX_v4_1 | phy_cores=${4}