#!/usr/bin/env python import socket import unittest import struct import StringIO import random from framework import VppTestCase, VppTestRunner, running_extended_tests 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 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 scapy.all import fragment6 from util import ppp from ipfix import IPFIX, Set, Template, Data, IPFIXDecoder from time import sleep from util import ip4_range from util import mactobinary class MethodHolder(VppTestCase): """ NAT create capture and verify method holder """ def clear_nat44(self): """ Clear NAT44 configuration. """ if hasattr(self, 'pg7') and hasattr(self, 'pg8'): # I found no elegant way to do this self.vapi.ip_add_del_route( dst_address=self.pg7.remote_ip4n, dst_address_length=32, next_hop_address=self.pg7.remote_ip4n, next_hop_sw_if_index=self.pg7.sw_if_index, is_add=0) self.vapi.ip_add_del_route( dst_address=self.pg8.remote_ip4n, dst_address_length=32, next_hop_address=self.pg8.remote_ip4n, next_hop_sw_if_index=self.pg8.sw_if_index, is_add=0) for intf in [self.pg7, self.pg8]: neighbors = self.vapi.ip_neighbor_dump(intf.sw_if_index) for n in neighbors: self.vapi.ip_neighbor_add_del(intf.sw_if_index, n.mac_address, n.ip_address, is_add=0) if self.pg7.has_ip4_config: self.pg7.unconfig_ip4() self.vapi.nat44_forwarding_enable_disable(0) interfaces = self.vapi.nat44_interface_addr_dump() for intf in interfaces: self.vapi.nat44_add_interface_addr(intf.sw_if_index, twice_nat=intf.twice_nat, is_add=0) self.vapi.nat_ipfix(enable=0, src_port=self.ipfix_src_port, domain_id=self.ipfix_domain_id) self.ipfix_src_port = 4739 self.ipfix_domain_id = 1 interfaces = self.vapi.nat44_interface_dump() for intf in interfaces: if intf.is_inside > 1: self.vapi.nat44_interface_add_del_feature(intf.sw_if_index, 0, is_add=0) self.vapi.nat44_interface_add_del_feature(intf.sw_if_index, intf.is_inside, is_add=0) interfaces = self.vapi.nat44_interface_output_feature_dump() for intf in interfaces: self.vapi.nat44_interface_add_del_output_feature(intf.sw_if_index, intf.is_inside, is_add=0) static_mappings = self.vapi.nat44_static_mapping_dump() for sm in static_mappings: self.vapi.nat44_add_del_static_mapping( sm.local_ip_address, sm.external_ip_address, local_port=sm.local_port, external_port=sm.external_port, addr_only=sm.addr_only, vrf_id=sm.vrf_id, protocol=sm.protocol, twice_nat=sm.twice_nat, self_twice_nat=sm.self_twice_nat, out2in_only=sm.out2in_only, tag=sm.tag, external_sw_if_index=sm.external_sw_if_index, is_add=0) 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( lb_sm.external_addr, lb_sm.external_port, lb_sm.protocol, twice_nat=lb_sm.twice_nat, self_twice_nat=lb_sm.self_twice_nat, out2in_only=lb_sm.out2in_only, tag=lb_sm.tag, is_add=0, local_num=0, locals=[]) identity_mappings = self.vapi.nat44_identity_mapping_dump() for id_m in identity_mappings: self.vapi.nat44_add_del_identity_mapping( addr_only=id_m.addr_only, ip=id_m.ip_address, port=id_m.port, sw_if_index=id_m.sw_if_index, vrf_id=id_m.vrf_id, protocol=id_m.protocol, is_add=0) adresses = self.vapi.nat44_address_dump() for addr in adresses: self.vapi.nat44_add_del_address_range(addr.ip_address, addr.ip_address, twice_nat=addr.twice_nat, is_add=0) self.vapi.nat_set_reass() self.vapi.nat_set_reass(is_ip6=1) self.verify_no_nat44_user() self.vapi.nat_set_timeouts() self.vapi.nat_set_addr_and_port_alloc_alg() 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, twice_nat=0, self_twice_nat=0, out2in_only=0, tag=""): """ 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 twice_nat: 1 if translate external host address and port :param self_twice_nat: 1 if translate external host address and port whenever external host address equals local address of internal host :param out2in_only: if 1 rule is matching only out2in direction :param tag: Opaque string tag """ addr_only = 1 if local_port and external_port: addr_only = 0 l_ip = socket.inet_pton(socket.AF_INET, local_ip) e_ip = socket.inet_pton(socket.AF_INET, external_ip) self.vapi.nat44_add_del_static_mapping( l_ip, e_ip, external_sw_if_index, local_port, external_port, addr_only, vrf_id, proto, twice_nat, self_twice_nat, out2in_only, tag, is_add) 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 extenal hosts """ nat_addr = socket.inet_pton(socket.AF_INET, ip) self.vapi.nat44_add_del_address_range(nat_addr, nat_addr, is_add, vrf_id=vrf_id, twice_nat=twice_nat) 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] return socket.inet_ntop(socket.AF_INET6, ''.join(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): """ Verify captured packets on outside network :param capture: Captured packets :param nat_ip: Translated IP address (Default use global NAT address) :param same_port: Sorce port number is not translated (Default False) :param dst_ip: Destination IP address (Default do not verify) :param is_ip6: If L3 protocol is IPv6 (Default False) """ if is_ip6: IP46 = IPv6 ICMP46 = ICMPv6EchoRequest else: IP46 = IP ICMP46 = ICMP if nat_ip is None: nat_ip = self.nat_addr for packet in capture: try: if not is_ip6: self.assert_packet_checksums_valid(packet) self.assertEqual(packet[IP46].src, nat_ip) if dst_ip is not None: self.assertEqual(packet[IP46].dst, dst_ip) if packet.haslayer(TCP): if same_port: self.assertEqual(packet[TCP].sport, self.tcp_port_in) else: self.assertNotEqual( packet[TCP].sport, self.tcp_port_in) self.tcp_port_out = packet[TCP].sport self.assert_packet_checksums_valid(packet) elif packet.haslayer(UDP): if same_port: self.assertEqual(packet[UDP].sport, self.udp_port_in) else: self.assertNotEqual( packet[UDP].sport, self.udp_port_in) self.udp_port_out = packet[UDP].sport else: if same_port: self.assertEqual(packet[ICMP46].id, self.icmp_id_in) else: self.assertNotEqual(packet[ICMP46].id, self.icmp_id_in) self.icmp_id_out = packet[ICMP46].id self.assert_packet_checksums_valid(packet) except: self.logger.error(ppp("Unexpected or invalid packet " "(outside network):", packet)) raise def verify_capture_out_ip6(self, capture, nat_ip, same_port=False, dst_ip=None): """ Verify captured packets on outside network :param capture: Captured packets :param nat_ip: Translated IP address :param same_port: Sorce port number is not translated (Default False) :param dst_ip: Destination IP address (Default do not verify) """ return self.verify_capture_out(capture, nat_ip, same_port, dst_ip, True) def verify_capture_in(self, capture, in_if): """ Verify captured packets on inside network :param capture: Captured packets :param in_if: Inside interface """ for packet in capture: try: self.assert_packet_checksums_valid(packet) self.assertEqual(packet[IP].dst, in_if.remote_ip4) if packet.haslayer(TCP): self.assertEqual(packet[TCP].dport, self.tcp_port_in) elif packet.haslayer(UDP): self.assertEqual(packet[UDP].dport, self.udp_port_in) else: self.assertEqual(packet[ICMP].id, self.icmp_id_in) except: self.logger.error(ppp("Unexpected or invalid packet " "(inside network):", packet)) raise def verify_capture_in_ip6(self, capture, src_ip, dst_ip): """ Verify captured IPv6 packets on inside network :param capture: Captured packets :param src_ip: Source IP :param dst_ip: Destination IP address """ for packet in capture: try: self.assertEqual(packet[IPv6].src, src_ip) self.assertEqual(packet[IPv6].dst, dst_ip) self.assert_packet_checksums_valid(packet) if packet.haslayer(TCP): self.assertEqual(packet[TCP].dport, self.tcp_port_in) elif packet.haslayer(UDP): self.assertEqual(packet[UDP].dport, self.udp_port_in) else: self.assertEqual(packet[ICMPv6EchoReply].id, self.icmp_id_in) except: self.logger.error(ppp("Unexpected or invalid packet " "(inside network):", packet)) raise def verify_capture_no_translation(self, capture, ingress_if, egress_if): """ Verify captured packet that don't have to be translated :param capture: Captured packets :param ingress_if: Ingress interface :param egress_if: Egress interface """ for packet in capture: try: self.assertEqual(packet[IP].src, ingress_if.remote_ip4) self.assertEqual(packet[IP].dst, egress_if.remote_ip4) if packet.haslayer(TCP): self.assertEqual(packet[TCP].sport, self.tcp_port_in) elif packet.haslayer(UDP): self.assertEqual(packet[UDP].sport, self.udp_port_in) else: self.assertEqual(packet[ICMP].id, self.icmp_id_in) except: self.logger.error(ppp("Unexpected or invalid packet " "(inside network):", packet)) raise def verify_capture_out_with_icmp_errors(self, capture, src_ip=None, icmp_type=11): """ Verify captured packets with ICMP errors on outside network :param capture: Captured packets :param src_ip: Translated IP address or IP address of VPP (Default use global NAT address) :param icmp_type: Type of error ICMP packet we are expecting (Default 11) """ if src_ip is None: src_ip = self.nat_addr for packet in capture: try: self.assertEqual(packet[IP].src, src_ip) self.assertTrue(packet.haslayer(ICMP)) icmp = packet[ICMP] self.assertEqual(icmp.type, icmp_type) self.assertTrue(icmp.haslayer(IPerror)) inner_ip = icmp[IPerror] if inner_ip.haslayer(TCPerror): self.assertEqual(inner_ip[TCPerror].dport, self.tcp_port_out) elif inner_ip.haslayer(UDPerror): self.assertEqual(inner_ip[UDPerror].dport, self.udp_port_out) else: self.assertEqual(inner_ip[ICMPerror].id, self.icmp_id_out) except: self.logger.error(ppp("Unexpected or invalid packet " "(outside network):", packet)) raise def verify_capture_in_with_icmp_errors(self, capture, in_if, icmp_type=11): """ Verify captured packets with ICMP errors on inside network :param capture: Captured packets :param in_if: Inside interface :param icmp_type: Type of error ICMP packet we are expecting (Default 11) """ for packet in capture: try: self.assertEqual(packet[IP].dst, in_if.remote_ip4) self.assertTrue(packet.haslayer(ICMP)) icmp = packet[ICMP] self.assertEqual(icmp.type, icmp_type) self.assertTrue(icmp.haslayer(IPerror)) inner_ip = icmp[IPerror] if inner_ip.haslayer(TCPerror): self.assertEqual(inner_ip[TCPerror].sport, self.tcp_port_in) elif inner_ip.haslayer(UDPerror): self.assertEqual(inner_ip[UDPerror].sport, self.udp_port_in) else: self.assertEqual(inner_ip[ICMPerror].id, self.icmp_id_in) except: self.logger.error(ppp("Unexpected or invalid packet " "(inside network):", packet)) raise def create_stream_frag(self, src_if, dst, sport, dport, data): """ Create fragmented packet stream :param src_if: Source interface :param dst: Destination IPv4 address :param sport: Source TCP port :param dport: Destination TCP port :param data: Payload data :returns: Fragmets """ id = random.randint(0, 65535) p = (IP(src=src_if.remote_ip4, dst=dst) / TCP(sport=sport, dport=dport) / Raw(data)) p = p.__class__(str(p)) chksum = p['TCP'].chksum pkts = [] p = (Ether(src=src_if.remote_mac, dst=src_if.local_mac) / IP(src=src_if.remote_ip4, dst=dst, flags="MF", frag=0, id=id) / TCP(sport=sport, dport=dport, chksum=chksum) / Raw(data[0:4])) pkts.append(p) p = (Ether(src=src_if.remote_mac, dst=src_if.local_mac) / IP(src=src_if.remote_ip4, dst=dst, flags="MF", frag=3, id=id, proto=IP_PROTOS.tcp) / Raw(data[4:20])) pkts.append(p) p = (Ether(src=src_if.remote_mac, dst=src_if.local_mac) / IP(src=src_if.remote_ip4, dst=dst, frag=5, proto=IP_PROTOS.tcp, id=id) / Raw(data[20:])) pkts.append(p) return pkts def create_stream_frag_ip6(self, src_if, dst, sport, dport, data, pref=None, plen=0, frag_size=128): """ Create fragmented packet stream :param src_if: Source interface :param dst: Destination IPv4 address :param sport: Source TCP port :param dport: Destination TCP port :param data: Payload data :param pref: NAT64 prefix :param plen: NAT64 prefix length :param fragsize: size of fragments :returns: Fragmets """ if pref is None: dst_ip6 = ''.join(['64:ff9b::', dst]) else: dst_ip6 = self.compose_ip6(dst, pref, plen) p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / IPv6(src=src_if.remote_ip6, dst=dst_ip6) / IPv6ExtHdrFragment(id=random.randint(0, 65535)) / TCP(sport=sport, dport=dport) / Raw(data)) return fragment6(p, frag_size) def reass_frags_and_verify(self, frags, src, dst): """ Reassemble and verify fragmented packet :param frags: Captured fragments :param src: Source IPv4 address to verify :param dst: Destination IPv4 address to verify :returns: Reassembled IPv4 packet """ buffer = StringIO.StringIO() for p in frags: self.assertEqual(p[IP].src, src) self.assertEqual(p[IP].dst, dst) self.assert_ip_checksum_valid(p) buffer.seek(p[IP].frag * 8) buffer.write(p[IP].payload) ip = frags[0].getlayer(IP) ip = IP(src=frags[0][IP].src, dst=frags[0][IP].dst, proto=frags[0][IP].proto) if ip.proto == IP_PROTOS.tcp: p = (ip / TCP(buffer.getvalue())) self.assert_tcp_checksum_valid(p) elif ip.proto == IP_PROTOS.udp: p = (ip / UDP(buffer.getvalue())) return p def reass_frags_and_verify_ip6(self, frags, src, dst): """ Reassemble and verify fragmented packet :param frags: Captured fragments :param src: Source IPv6 address to verify :param dst: Destination IPv6 address to verify :returns: Reassembled IPv6 packet """ buffer = StringIO.StringIO() for p in frags: self.assertEqual(p[IPv6].src, src) self.assertEqual(p[IPv6].dst, dst) buffer.seek(p[IPv6ExtHdrFragment].offset * 8) buffer.write(p[IPv6ExtHdrFragment].payload) ip = IPv6(src=frags[0][IPv6].src, dst=frags[0][IPv6].dst, nh=frags[0][IPv6ExtHdrFragment].nh) if ip.nh == IP_PROTOS.tcp: p = (ip / TCP(buffer.getvalue())) elif ip.nh == IP_PROTOS.udp: p = (ip / UDP(buffer.getvalue())) self.assert_packet_checksums_valid(p) return p def initiate_tcp_session(self, in_if, out_if): """ Initiates TCP session :param in_if: Inside interface :param out_if: Outside interface """ try: # SYN packet in->out p = (Ether(src=in_if.remote_mac, dst=in_if.local_mac) / IP(src=in_if.remote_ip4, dst=out_if.remote_ip4) / TCP(sport=self.tcp_port_in, dport=self.tcp_external_port, flags="S")) in_if.add_stream(p) self.pg_enable_capture(self.pg_interfaces) self.pg_start() capture = out_if.get_capture(1) p = capture[0] self.tcp_port_out = p[TCP].sport # SYN + ACK packet out->in p = (Ether(src=out_if.remote_mac, dst=out_if.local_mac) / IP(src=out_if.remote_ip4, dst=self.nat_addr) / TCP(sport=self.tcp_external_port, dport=self.tcp_port_out, flags="SA")) out_if.add_stream(p) self.pg_enable_capture(self.pg_interfaces) self.pg_start() in_if.get_capture(1) # ACK packet in->out p = (Ether(src=in_if.remote_mac, dst=in_if.local_mac) / IP(src=in_if.remote_ip4, dst=out_if.remote_ip4) / TCP(sport=self.tcp_port_in, dport=self.tcp_external_port, flags="A")) in_if.add_stream(p) self.pg_enable_capture(self.pg_interfaces) self.pg_start() out_if.get_capture(1) except: self.logger.error("TCP 3 way handshake failed") raise def verify_ipfix_nat44_ses(self, data): """ Verify IPFIX NAT44 session create/delete event :param data: Decoded IPFIX data records """ nat44_ses_create_num = 0 nat44_ses_delete_num = 0 self.assertEqual(6, len(data))
/*
* Copyright (c) 2016 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.
*/
#ifndef __FIB_TABLE_H__
#define __FIB_TABLE_H__
#include <vnet/ip/ip.h>
#include <vnet/adj/adj.h>
#include <vnet/fib/fib_entry.h>
#include <vnet/mpls/mpls.h>
#include <vnet/mpls/packet.h>
/**
* @brief
* A protocol Independent FIB table
*/
typedef struct fib_table_t_
{
/**
* Which protocol this table serves. Used to switch on the union above.
*/
fib_protocol_t ft_proto;
/**
* number of locks on the table
*/
u16 ft_locks;
/**
* Table ID (hash key) for this FIB.
*/
u32 ft_table_id;
/**
* Index into FIB vector.
*/
fib_node_index_t ft_index;
/**
* flow hash configuration
*/
u32 ft_flow_hash_config;
/**
* Per-source route counters
*/
u32 ft_src_route_counts[FIB_SOURCE_MAX];
/**
* Total route counters
*/
u32 ft_total_route_counts;
/**
* Table description
*/
u8* ft_desc;
} fib_table_t;
/**
* @brief
* Format the description/name of the table
*/
extern u8* format_fib_table_name(u8* s, va_list ap);
/**
* @brief
* Perfom a longest prefix match in the non-forwarding table
*
* @param fib_index
* The index of the FIB
*
* @param prefix
* The prefix to lookup
*
* @return
* The index of the fib_entry_t for the best match, which may be the default route
*/
extern fib_node_index_t fib_table_lookup(u32 fib_index,
const fib_prefix_t *prefix);
/**
* @brief
* Perfom an exact match in the non-forwarding table
*
* @param fib_index
* The index of the FIB
*
* @param prefix
* The prefix to lookup
*
* @return
* The index of the fib_entry_t for the exact match, or INVALID
* is there is no match.
*/
extern fib_node_index_t fib_table_lookup_exact_match(u32 fib_index,
const fib_prefix_t *prefix);
/**
* @brief
* Get the less specific (covering) prefix
*
* @param fib_index
* The index of the FIB
*
* @param prefix
* The prefix to lookup
*
* @return
* The index of the less specific fib_entry_t.
*/
extern fib_node_index_t fib_table_get_less_specific(u32 fib_index,
const fib_prefix_t *prefix);
/**
* @brief
* Add a 'special' entry to the FIB.
* A special entry is an entry that the FIB is not expect to resolve
* via the usual mechanisms (i.e. recurisve or neighbour adj DB lookup).
* Instead the will link to a DPO valid for the source and/or the flags.
* This add is reference counting per-source. So n 'removes' are required
* for n 'adds', if the entry is no longer required.
* If the source needs to provide non-default forwarding use:
* fib_table_entry_special_dpo_add()
*
* @param fib_index
* The index of the FIB
*
* @param prefix
* The prefix to add
*
* @param source
* The ID of the client/source adding the entry.
*
* @param flags
* Flags for the entry.
*
* @return
* the index of the fib_entry_t that is created (or exists already).
*/
extern fib_node_index_t fib_table_entry_special_add(u32 fib_index,
const fib_prefix_t *prefix,
fib_source_t source,
fib_entry_flag_t flags);
/**
* @brief
* Add a 'special' entry to the FIB that links to the DPO passed
* A special entry is an entry that the FIB is not expect to resolve
* via the usual mechanisms (i.e. recurisve or neighbour adj DB lookup).
* Instead the client/source provides the DPO to link to.
* This add is reference counting per-source. So n 'removes' are required
* for n 'adds', if the entry is no longer required.
*
* @param fib_index
* The index of the FIB
*
* @param prefix
* The prefix to add
*
* @param source
* The ID of the client/source adding the entry.
*
* @param flags
* Flags for the entry.
*
* @param dpo
* The DPO to link to.
*
* @return
* the index of the fib_entry_t that is created (or existed already).
*/
extern fib_node_index_t fib_table_entry_special_dpo_add(u32 fib_index,
const fib_prefix_t *prefix,
fib_source_t source,
fib_entry_flag_t stype,
const dpo_id_t *dpo);
/**
* @brief
* Update a 'special' entry to the FIB that links to the DPO passed
* A special entry is an entry that the FIB is not expect to resolve
* via the usual mechanisms (i.e. recurisve or neighbour adj DB lookup).
* Instead the client/source provides the DPO to link to.
* Special entries are add/remove reference counted per-source. So n
* 'removes' are required for n 'adds', if the entry is no longer required.
* An 'update' is an 'add' if no 'add' has already been called, otherwise an 'add'
* is therefore assumed to act on the reference instance of that add.
*
* @param fib_entry_index
* The index of the FIB entry to update
*
* @param source
* The ID of the client/source adding the entry.
*
* @param flags
* Flags for the entry.
*
* @param dpo
* The DPO to link to.
*
* @return
* the index of the fib_entry_t that is created (or existed already).
*/
extern fib_node_index_t fib_table_entry_special_dpo_update (u32 fib_index,
const fib_prefix_t *prefix,
fib_source_t source,
fib_entry_flag_t stype,
const dpo_id_t *dpo);
/**
* @brief
* Remove a 'special' entry from the FIB.
* This add is reference counting per-source. So n 'removes' are required
* for n 'adds', if the entry is no longer required.
*
* @param fib_index
* The index of the FIB
*
* @param prefix
* The prefix to remove
*
* @param source
* The ID of the client/source adding the entry.
*
*/
extern void fib_table_entry_special_remove(u32 fib_index,
const fib_prefix_t *prefix,
fib_source_t source);
/**
* @brief
* Add one path to an entry (aka route) in the FIB. If the entry does not
* exist, it will be created.
* See the documentation for fib_route_path_t for more descirptions of
* the path parameters.
*
* @param fib_index
* The index of the FIB
*
* @param prefix
* The prefix for the entry to add
*
* @param source
* The ID of the client/source adding the entry.
*
* @param flags
* Flags for the entry.
*
* @paran next_hop_proto
* The protocol of the next hop. This cannot be derived in the event that
* the next hop is all zeros.
*
* @param next_hop
* The address of the next-hop.
*
* @param sw_if_index
* The index of the interface.
*
* @param next_hop_fib_index,
* The fib index of the next-hop for recursive resolution
*
* @param next_hop_weight
* [un]equal cost path weight
*
* @param next_hop_label_stack
* The path's out-going label stack. NULL is there is none.
*
* @param pf
* Flags for the path
*
* @return
* the index of the fib_entry_t that is created (or existed already).
*/
extern fib_node_index_t fib_table_entry_path_add(u32 fib_index,
const fib_prefix_t *prefix,
fib_source_t source,
fib_entry_flag_t flags,
dpo_proto_t next_hop_proto,
const ip46_address_t *next_hop,
u32 next_hop_sw_if_index,
u32 next_hop_fib_index,
u32 next_hop_weight,
mpls_label_t *next_hop_label_stack,
fib_route_path_flags_t pf);
/**
* @brief
* Add n paths to an entry (aka route) in the FIB. If the entry does not
* exist, it will be created.
* See the documentation for fib_route_path_t for more descirptions of
* the path parameters.
*
* @param fib_index
* The index of the FIB
*
* @param prefix
* The prefix for the entry to add
*
* @param source
* The ID of the client/source adding the entry.
*
* @param flags
* Flags for the entry.
*
* @param rpaths
* A vector of paths. Not const since they may be modified.
*
* @return
* the index of the fib_entry_t that is created (or existed already).
*/
extern fib_node_index_t fib_table_entry_path_add2(u32 fib_index,
const fib_prefix_t *prefix,
fib_source_t source,
fib_entry_flag_t flags,
fib_route_path_t *rpath);
/**
* @brief
* remove one path to an entry (aka route) in the FIB. If this is the entry's
* last path, then the entry will be removed, unless it has other sources.
* See the documentation for fib_route_path_t for more descirptions of
* the path parameters.
*
* @param fib_index
* The index of the FIB
*
* @param prefix
* The prefix for the entry to add
*
* @param source
* The ID of the client/source adding the entry.
*
* @paran next_hop_proto
* The protocol of the next hop. This cannot be derived in the event that
* the next hop is all zeros.
*
* @param next_hop
* The address of the next-hop.
*
* @param sw_if_index
* The index of the interface.
*
* @param next_hop_fib_index,
* The fib index of the next-hop for recursive resolution
*
* @param next_hop_weight
* [un]equal cost path weight
*
* @param pf
* Flags for the path
*/
extern void fib_table_entry_path_remove(u32 fib_index,
const fib_prefix_t *prefix,
fib_source_t source,
dpo_proto_t next_hop_proto,
const ip46_address_t *next_hop,
u32 next_hop_sw_if_index,
u32 next_hop_fib_index,
u32 next_hop_weight,
fib_route_path_flags_t pf);
/**
* @brief
* Remove n paths to an entry (aka route) in the FIB. If this is the entry's
* last path, then the entry will be removed, unless it has other sources.
* See the documentation for fib_route_path_t for more descirptions of
* the path parameters.
*
* @param fib_index
* The index of the FIB
*
* @param prefix
* The prefix for the entry to add
*
* @param source
* The ID of the client/source adding the entry.
*
* @param rpaths
* A vector of paths.
*/
extern void fib_table_entry_path_remove2(u32 fib_index,
const fib_prefix_t *prefix,
fib_source_t source,
fib_route_path_t *paths);
/**
* @brief
* Update an entry to have a new set of paths. If the entry does not
* exist, it will be created.
* The difference between an 'path-add' and an update, is that path-add is
* an incremental addition of paths, whereas an update is a wholesale swap.
*
* @param fib_index
* The index of the FIB
*
* @param prefix
* The prefix for the entry to add
*
* @param source
* The ID of the client/source adding the entry.
*
* @param rpaths
* A vector of paths. Not const since they may be modified.
*
* @return
* the index of the fib_entry_t that is created (or existed already).
*/
extern fib_node_index_t fib_table_entry_update(u32 fib_index,
const fib_prefix_t *prefix,
fib_source_t source,
fib_entry_flag_t flags,
fib_route_path_t *paths);
/**
* @brief
* Update the entry to have just one path. If the entry does not
* exist, it will be created.
* See the documentation for fib_route_path_t for more descirptions of
* the path parameters.
*
* @param fib_index
* The index of the FIB
*
* @param prefix
* The prefix for the entry to add
*
* @param source
* The ID of the client/source adding the entry.
*
* @param flags
* Flags for the entry.
*
* @paran next_hop_proto
* The protocol of the next hop. This cannot be derived in the event that
* the next hop is all zeros.
*
* @param next_hop
* The address of the next-hop.
*
* @param sw_if_index
* The index of the interface.
*
* @param next_hop_fib_index,
* The fib index of the next-hop for recursive resolution
*
* @param next_hop_weight
* [un]equal cost path weight
*
* @param next_hop_label_stack
* The path's out-going label stack. NULL is there is none.
*
* @param pf
* Flags for the path
*
* @return
* the index of the fib_entry_t that is created (or existed already).
*/
extern fib_node_index_t fib_table_entry_update_one_path(u32 fib_index,
const fib_prefix_t *prefix,
fib_source_t source,
fib_entry_flag_t flags,
dpo_proto_t next_hop_proto,
const ip46_address_t *next_hop,
u32 next_hop_sw_if_index,
u32 next_hop_fib_index,
u32 next_hop_weight,
mpls_label_t *next_hop_label_stack,
fib_route_path_flags_t pf);
/**
* @brief
* Add a MPLS local label for the prefix/route. If the entry does not
* exist, it will be created. In theory more than one local label can be
* added, but this is not yet supported.
*
* @param fib_index
* The index of the FIB
*
* @param prefix
* The prefix for the entry to which to add the label
*
* @param label
* The MPLS label to add
*
* @return
* the index of the fib_entry_t that is created (or existed already).
*/
extern fib_node_index_t fib_table_entry_local_label_add(u32 fib_index,
const fib_prefix_t *prefix,
mpls_label_t label);
/**
* @brief
* remove a MPLS local label for the prefix/route.
*
* @param fib_index
* The index of the FIB
*
* @param prefix
* The prefix for the entry to which to add the label
*
* @param label
* The MPLS label to add
*/
extern void fib_table_entry_local_label_remove(u32 fib_index,
const fib_prefix_t *prefix,
mpls_label_t label);
/**
* @brief
* Delete a FIB entry. If the entry has no more sources, then it is
* removed from the table.
*
* @param fib_index
* The index of the FIB
*
* @param prefix
* The prefix for the entry to remove
*
* @param source
* The ID of the client/source adding the entry.
*/
extern void fib_table_entry_delete(u32 fib_index,
const fib_prefix_t *prefix,
fib_source_t source);
/**
* @brief
* Delete a FIB entry. If the entry has no more sources, then it is
* removed from the table.
*
* @param entry_index
* The index of the FIB entry
*
* @param source
* The ID of the client/source adding the entry.
*/
extern void fib_table_entry_delete_index(fib_node_index_t entry_index,
fib_source_t source);
/**
* @brief
* Flush all entries from a table for the source
*
* @param fib_index
* The index of the FIB
*
* @paran proto
* The protocol of the entries in the table
*
* @param source
* the source to flush
*/
extern void fib_table_flush(u32 fib_index,
fib_protocol_t proto,
fib_source_t source);
/**
* @brief
* Get the index of the FIB bound to the interface
*
* @paran proto
* The protocol of the FIB (and thus the entries therein)
*
* @param sw_if_index
* The interface index
*
* @return fib_index
* The index of the FIB
*/
extern u32 fib_table_get_index_for_sw_if_index(fib_protocol_t proto,
u32 sw_if_index);
/**
* @brief
* Get the Table-ID of the FIB bound to the interface
*
* @paran proto
* The protocol of the FIB (and thus the entries therein)
*
* @param sw_if_index
* The interface index
*
* @return fib_index
* The tableID of the FIB
*/
extern u32 fib_table_get_table_id_for_sw_if_index(fib_protocol_t proto,
u32 sw_if_index);
/**
* @brief
* Get the index of the FIB for a Table-ID. This DOES NOT create the
* FIB if it does not exist.
*
* @paran proto
* The protocol of the FIB (and thus the entries therein)
*
* @param table-id
* The Table-ID
*
* @return fib_index
* The index of the FIB, which may be INVALID.
*/
extern u32 fib_table_find(fib_protocol_t proto, u32 table_id);
/**
* @brief
* Get the index of the FIB for a Table-ID. This DOES create the
* FIB if it does not exist.
*
* @paran proto
* The protocol of the FIB (and thus the entries therein)
*
* @param table-id
* The Table-ID
*
* @return fib_index
* The index of the FIB
*/
extern u32 fib_table_find_or_create_and_lock(fib_protocol_t proto,
u32 table_id);
/**
* @brief
* Create a new table with no table ID. This means it does not get
* added to the hash-table and so can only be found by using the index returned.
*
* @paran proto
* The protocol of the FIB (and thus the entries therein)
*
* @param fmt
* A string to describe the table
*
* @return fib_index
* The index of the FIB
*/
extern u32 fib_table_create_and_lock(fib_protocol_t proto,
const char *const fmt,
...);
/**
* @brief
* Get the flow hash configured used by the table
*
* @param fib_index
* The index of the FIB
*
* @paran proto
* The protocol of the FIB (and thus the entries therein)
*
* @return The flow hash config
*/
extern flow_hash_config_t fib_table_get_flow_hash_config(u32 fib_index,
fib_protocol_t proto);
/**
* @brief
* Get the flow hash configured used by the protocol
*
* @paran proto
* The protocol of the FIB (and thus the entries therein)
*
* @return The flow hash config
*/
extern flow_hash_config_t fib_table_get_default_flow_hash_config(fib_protocol_t proto);
/**
* @brief
* Set the flow hash configured used by the table
*
* @param fib_index
* The index of the FIB
*
* @paran proto
* The protocol of the FIB (and thus the entries therein)
*
* @param hash_config
* The flow-hash config to set
*
* @return none
*/
extern void fib_table_set_flow_hash_config(u32 fib_index,
fib_protocol_t proto,
flow_hash_config_t hash_config);
/**
* @brief
* Take a reference counting lock on the table
*
* @param fib_index
* The index of the FIB
*
* @paran proto
* The protocol of the FIB (and thus the entries therein)
*/
extern void fib_table_unlock(u32 fib_index,
fib_protocol_t proto);
/**
* @brief
* Release a reference counting lock on the table. When the last lock
* has gone. the FIB is deleted.
*
* @param fib_index
* The index of the FIB
*
* @paran proto
* The protocol of the FIB (and thus the entries therein)
*/
extern void fib_table_lock(u32 fib_index,
fib_protocol_t proto);
/**
* @brief
* Return the number of entries in the FIB added by a given source.
*
* @param fib_index
* The index of the FIB
*
* @paran proto
* The protocol of the FIB (and thus the entries therein)
*
* @return number of sourced entries.
*/
extern u32 fib_table_get_num_entries(u32 fib_index,
fib_protocol_t proto,
fib_source_t source);
/**
* @brief
* Get a pointer to a FIB table
*/
extern fib_table_t *fib_table_get(fib_node_index_t index,
fib_protocol_t proto);
/**
* @brief Call back function when walking entries in a FIB table
*/
typedef int (*fib_table_walk_fn_t)(fib_node_index_t fei,
void *ctx);
/**
* @brief Walk all entries in a FIB table
* N.B: This is NOT safe to deletes. If you need to delete walk the whole
* table and store elements in a vector, then delete the elements
*/
extern void fib_table_walk(u32 fib_index,
fib_protocol_t proto,
fib_table_walk_fn_t fn,
void *ctx);
#endif