#!/usr/bin/env python import socket import unittest import struct 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, 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 vpp_papi_provider import SYSLOG_SEVERITY from io import BytesIO from vpp_papi import VppEnum 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]: self.vapi.ip_neighbor_add_del( intf.sw_if_index, intf.remote_mac, intf.remote_ip4, flags=(VppEnum.vl_api_ip_neighbor_flags_t. IP_API_NEIGHBOR_FLAG_STATIC), 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 self.vapi.syslog_set_filter(SYSLOG_SEVERITY.EMERG) 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() self.vapi.nat_set_mss_clamping() 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.assertEqual(packet.haslayer(ICMP), 1) 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.assertEqual(packet.haslayer(ICMP), 1) 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, proto=IP_PROTOS.tcp, echo_reply=False): """ Create fragmented packet stream :param src_if: Source interface :param dst: Destination IPv4 address :param sport: Source port :param dport: Destination port :param data: Payload data :param proto: protocol (TCP, UDP, ICMP) :param echo_reply: use echo_reply if protocol is ICMP :returns: Fragmets """ if proto == IP_PROTOS.tcp: 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 proto_header = TCP(sport=sport, dport=dport, chksum=chksum) elif proto == IP_PROTOS.udp: proto_header = UDP(sport=sport, dport=dport) elif proto == IP_PROTOS.icmp: if not echo_reply: proto_header = ICMP(id=sport, type='echo-request') else: proto_header = ICMP(id=sport, type='echo-reply') else: raise Exception("Unsupported protocol") id = random.randint(0, 65535) pkts = [] if proto == IP_PROTOS.tcp: raw = Raw(data[0:4]) else: raw = Raw(data[0:16]) 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) / proto_header / raw) pkts.append(p) if proto == IP_PROTOS.tcp: raw = Raw(data[4:20]) else: raw = Raw(data[16:32]) 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=proto) / raw) pkts.append(p) if proto == IP_PROTOS.tcp: raw = Raw(data[20:]) else: raw = Raw(data[32:]) p = (Ether(src=src_if.remote_mac, dst=src_if.local_mac) / IP(src=src_if.remote_ip4, dst=dst, frag=5, proto=proto, id=id) / raw) 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 = BytesIO() 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(bytes(p[IP].payload)) 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()[:8]) / Raw(buffer.getvalue()[8:])) elif ip.proto == IP_PROTOS.icmp: p = (ip / ICMP(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 = BytesIO() for p in frags: self.assertEqual(p[IPv6].src, src) self.assertEqual(p[IPv6].dst, dst) buffer.seek(p[IPv6ExtHdrFragment].offset * 8) buffer.write(bytes(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 s
#!/usr/bin/env python
import unittest
from framework import VppTestCase, VppTestRunner, running_extended_tests
from vpp_igmp import *
from scapy.layers.l2 import Ether
from scapy.layers.inet import IP
from scapy.contrib.igmpv3 import *
from scapy.contrib.igmp import *
from vpp_ip_route import find_mroute, VppIpTable
class IgmpMode:
HOST = 1
ROUTER = 0
class TestIgmp(VppTestCase):
""" IGMP Test Case """
def setUp(self):
super(TestIgmp, self).setUp()
self.create_pg_interfaces(range(4))
self.sg_list = []
self.config_list = []
self.ip_addr = []
self.ip_table = VppIpTable(self, 1)
self.ip_table.add_vpp_config()
for pg in self.pg_interfaces[2:]:
pg.set_table_ip4(1)
for pg in self.pg_interfaces:
pg.admin_up()
pg.config_ip4()
pg.resolve_arp()
def tearDown(self):
for pg in self.pg_interfaces:
self.vapi.igmp_clear_interface(pg.sw_if_index)
pg.unconfig_ip4()
pg.set_table_ip4(0)
pg.admin_down()
super(TestIgmp, self).tearDown()
def send(self, ti, pkts):
ti.add_stream(pkts)
self.pg_enable_capture(self.pg_interfaces)
self.pg_start()
def test_igmp_flush(self):
""" IGMP Link Up/down and Flush """
#
# FIX THIS. Link down.
#
def test_igmp_enable(self):
""" IGMP enable/disable on an interface
check for the addition/removal of the IGMP mroutes """
self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 1, IGMP_MODE.HOST)
self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 1, IGMP_MODE.HOST)
self.assertTrue(find_mroute(self, "224.0.0.1", "0.0.0.0", 32))
self.assertTrue(find_mroute(self, "224.0.0.22", "0.0.0.0", 32))
self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 1, IGMP_MODE.HOST)
self.vapi.igmp_enable_disable(self.pg3.sw_if_index, 1, IGMP_MODE.HOST)
self.assertTrue(find_mroute(self, "224.0.0.1", "0.0.0.0", 32,
table_id=1))
self.assertTrue(find_mroute(self, "224.0.0.22", "0.0.0.0", 32,
table_id=1))
self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 0, IGMP_MODE.HOST)
self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 0, IGMP_MODE.HOST)
self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 0, IGMP_MODE.HOST)
self.vapi.igmp_enable_disable(self.pg3.sw_if_index, 0, IGMP_MODE.HOST)
self.assertFalse(find_mroute(self, "224.0.0.1", "0.0.0.0", 32))
self.assertFalse(find_mroute(self, "224.0.0.22", "0.0.0.0", 32))
self.assertFalse(find_mroute(self, "224.0.0.1", "0.0.0.0", 32,
table_id=1))
self.assertFalse(find_mroute(self, "224.0.0.22", "0.0.0.0", 32,
table_id=1))
def verify_general_query(self, p):
ip = p[IP]
self.assertEqual(len(ip.options), 1)
self.assertEqual(ip.options[0].option, 20)
self.assertEqual(ip.dst, "224.0.0.1")
self.assertEqual(ip.proto, 2)
igmp = p[IGMPv3]
self.assertEqual(igmp.type, 0x11)
self.assertEqual(igmp.gaddr, "0.0.0.0")
def verify_group_query(self, p, grp, srcs):
ip = p[IP]
self.assertEqual(ip.dst, grp)
self.assertEqual(ip.proto, 2)
self.assertEqual(len(ip.options), 1)
self.assertEqual(ip.options[0].option, 20)
self.assertEqual(ip.proto, 2)
igmp = p[IGMPv3]
self.assertEqual(igmp.type, 0x11)
self.assertEqual(igmp.gaddr, grp)
def verify_report(self, rx, records):
ip = rx[IP]
self.assertEqual(rx[IP].dst, "224.0.0.22")
self.assertEqual(len(ip.options), 1)
self.assertEqual(ip.options[0].option, 20)
self.assertEqual(ip.proto, 2)
self.assertEqual(IGMPv3.igmpv3types[rx[IGMPv3].type],
"Version 3 Membership Report")
self.assertEqual(rx[IGMPv3mr].numgrp, len(records))
received = rx[IGMPv3mr].records
for ii in range(len(records)):
gr = received[ii]
r = records[ii]
self.assertEqual(IGMPv3gr.igmpv3grtypes[gr.rtype], r.type)
self.assertEqual(gr.numsrc, len(r.sg.saddrs))
self.assertEqual(gr.maddr, r.sg.gaddr)
self.assertEqual(len(gr.srcaddrs), len(r.sg.saddrs))
self.assertEqual(sorted(gr.srcaddrs),
sorted(r.sg.saddrs))
def add_group(self, itf, sg, n_pkts=2):
self.pg_enable_capture(self.pg_interfaces)
self.pg_start()
hs = VppHostState(self,
IGMP_FILTER.INCLUDE,
itf.sw_if_index,
sg)
hs.add_vpp_config()
capture = itf.get_capture(n_pkts, timeout=10)
# reports are transmitted twice due to default rebostness value=2
self.verify_report(capture[0],
[IgmpRecord(sg, "Allow New Sources")]),
self.verify_report(capture[1],
[IgmpRecord(sg, "Allow New Sources")]),
return hs
def remove_group(self, hs):
self.pg_enable_capture(self.pg_interfaces)
self.pg_start()
hs.remove_vpp_config()
capture = self.pg0.get_capture(1, timeout=10)
self.verify_report(capture[0],
[IgmpRecord(hs.sg, "Block Old Sources")])
def test_igmp_host(self):
""" IGMP Host functions """
#
# Enable interface for host functions
#
self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
1,
IGMP_MODE.HOST)
#
# Add one S,G of state and expect a state-change event report
# indicating the addition of the S,G
#
h1 = self.add_group(self.pg0, IgmpSG("239.1.1.1", ["1.1.1.1"]))
# search for the corresponding state created in VPP
dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
self.assertEqual(len(dump), 1)
self.assertTrue(find_igmp_state(dump, self.pg0,
"239.1.1.1", "1.1.1.1"))
#
# Send a general query (to the all router's address)
# expect VPP to respond with a membership report
#
p_g = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
IP(src=self.pg0.remote_ip4, dst='224.0.0.1', tos=0xc0) /
IGMPv3(type="Membership Query", mrcode=100) /
IGMPv3mq(gaddr="0.0.0.0"))
self.send(self.pg0, p_g)
capture = self.pg0.get_capture(1, timeout=10)
self.verify_report(capture[0],
[IgmpRecord(h1.sg, "Mode Is Include")])
#
# Group specific query
#
p_gs = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
options=[IPOption(copy_flag=1, optclass="control",
option="router_alert")]) /
IGMPv3(type="Membership Query", mrcode=100) /
IGMPv3mq(gaddr="239.1.1.1"))
self.send(self.pg0, p_gs)
capture = self.pg0.get_capture(1, timeout=10)
self.verify_report(capture[0],
[IgmpRecord(h1.sg, "Mode Is Include")])
#
# A group and source specific query, with the source matching
# the source VPP has
#
p_gs1 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
options=[IPOption(copy_flag=1, optclass="control",
option="router_alert")]) /
IGMPv3(type="Membership Query", mrcode=100) /
IGMPv3mq(gaddr="239.1.1.1", srcaddrs=["1.1.1.1"]))
self.send(self.pg0, p_gs1)
capture = self.pg0.get_capture(1, timeout=10)
self.verify_report(capture[0],
[IgmpRecord(h1.sg, "Mode Is Include")])
#
# A group and source specific query, with the source NOT matching
# the source VPP has. There should be no response.
#
p_gs2 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
options=[IPOption(copy_flag=1, optclass="control",
option="router_alert")]) /
IGMPv3(type="Membership Query", mrcode=100) /
IGMPv3mq(gaddr="239.1.1.1", srcaddrs=["1.1.1.2"]))
self.send_and_assert_no_replies(self.pg0, p_gs2, timeout=10)
#
# A group and source specific query, with the multiple sources
# one of which matches the source VPP has.
# The report should contain only the source VPP has.
#
p_gs3 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
options=[IPOption(copy_flag=1, optclass="control",
option="router_alert")]) /
IGMPv3(type="Membership Query", mrcode=100) /
IGMPv3mq(gaddr="239.1.1.1",
srcaddrs=["1.1.1.1", "1.1.1.2", "1.1.1.3"]))
self.send(self.pg0, p_gs3)
capture = self.pg0.get_capture(1, timeout=10)
self.verify_report(capture[0],
[IgmpRecord(h1.sg, "Mode Is Include")])
#
# Two source and group specific queires in qucik sucession, the
# first does not have VPPs source the second does. then vice-versa
#
self.send(self.pg0, [p_gs2, p_gs1])
capture = self.pg0.get_capture(1, timeout=10)
self.verify_report(capture[0],
[IgmpRecord(h1.sg, "Mode Is Include")])
self.send(self.pg0, [p_gs1, p_gs2])
capture = self.pg0.get_capture(1, timeout=10)
self.verify_report(capture[0],
[IgmpRecord(h1.sg, "Mode Is Include")])
#
# remove state, expect the report for the removal
#
self.remove_group(h1)
dump = self.vapi.igmp_dump()
self.assertFalse(dump)
#
# A group with multiple sources
#
h2 = self.add_group(self.pg0,
IgmpSG("239.1.1.1",
["1.1.1.1", "1.1.1.2", "1.1.1.3"]))
# search for the corresponding state created in VPP
dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
self.assertEqual(len(dump), 3)
for s in h2.sg.saddrs:
self.assertTrue(find_igmp_state(dump, self.pg0,
"239.1.1.1", s))
#
# Send a general query (to the all router's address)
# expect VPP to respond with a membership report will all sources
#
self.send(self.pg0, p_g)
capture = self.pg0.get_capture(1, timeout=10)
self.verify_report(capture[0],
[IgmpRecord(h2.sg, "Mode Is Include")])
#
# Group and source specific query; some present some not
#
p_gs = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
options=[IPOption(copy_flag=1, optclass="control",
option="router_alert")]) /
IGMPv3(type="Membership Query", mrcode=100) /
IGMPv3mq(gaddr="239.1.1.1",
srcaddrs=["1.1.1.1", "1.1.1.2", "1.1.1.4"]))
self.send(self.pg0, p_gs)
capture = self.pg0.get_capture(1, timeout=10)
self.verify_report(capture[0],
[IgmpRecord(
IgmpSG('239.1.1.1', ["1.1.1.1", "1.1.1.2"]),
"Mode Is Include")])
#
# add loads more groups
#
h3 = self.add_group(self.pg0,
IgmpSG("239.1.1.2",
["2.1.1.1", "2.1.1.2", "2.1.1.3"]))
h4 = self.add_group(self.pg0,
IgmpSG("239.1.1.3",
["3.1.1.1", "3.1.1.2", "3.1.1.3"]))
h5 = self.add_group(self.pg0,
IgmpSG("239.1.1.4",
["4.1.1.1", "4.1.1.2", "4.1.1.3"]))
h6 = self.add_group(self.pg0,
IgmpSG("239.1.1.5",
["5.1.1.1", "5.1.1.2", "5.1.1.3"]))
h7 = self.add_group(self.pg0,
IgmpSG("239.1.1.6",
["6.1.1.1", "6.1.1.2",
"6.1.1.3", "6.1.1.4",
"6.1.1.5", "6.1.1.6",
"6.1.1.7", "6.1.1.8",
"6.1.1.9", "6.1.1.10",
"6.1.1.11", "6.1.1.12",
"6.1.1.13", "6.1.1.14",
"6.1.1.15", "6.1.1.16"]))
#
# general query.
# the order the groups come in is not important, so what is
# checked for is what VPP is sending today.
#
self.send(self.pg0, p_g)
capture = self.pg0.get_capture(1, timeout=10)
self.verify_report(capture[0],
[IgmpRecord(h3.sg, "Mode Is Include"),
IgmpRecord(h2.sg, "Mode Is Include"),
IgmpRecord(h6.sg, "Mode Is Include"),
IgmpRecord(h4.sg, "Mode Is Include"),
IgmpRecord(h5.sg, "Mode Is Include"),
IgmpRecord(h7.sg, "Mode Is Include")])
#
# modify a group to add and remove some sources
#
h7.sg = IgmpSG("239.1.1.6",
["6.1.1.1", "6.1.1.2",
"6.1.1.5", "6.1.1.6",
"6.1.1.7", "6.1.1.8",
"6.1.1.9", "6.1.1.10",
"6.1.1.11", "6.1.1.12",
"6.1.1.13", "6.1.1.14",
"6.1.1.15", "6.1.1.16",
"6.1.1.17", "6.1.1.18"])
self.pg_enable_capture(self.pg_interfaces)
self.pg_start()
h7.add_vpp_config()
capture = self.pg0.get_capture(1, timeout=10)
self.verify_report(capture[0],
[IgmpRecord(IgmpSG("239.1.1.6",
["6.1.1.17", "6.1.1.18"]),
"Allow New Sources"),
IgmpRecord(IgmpSG("239.1.1.6",
["6.1.1.3", "6.1.1.4"]),
"Block Old Sources")])
#
# add an additional groups with many sources so that each group
# consumes the link MTU. We should therefore see multiple state
# state reports when queried.
#
self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [560, 0, 0, 0])
src_list = []
for i in range(128):
src_list.append("10.1.1.%d" % i)
h8 = self.add_group(self.pg0,
IgmpSG("238.1.1.1", src_list))
h9 = self.add_group(self.pg0,
IgmpSG("238.1.1.2", src_list))
self.send(self.pg0, p_g)
capture = self.pg0.get_capture(4, timeout=10)
self.verify_report(capture[0],
[IgmpRecord(h3.sg, "Mode Is Include"),
IgmpRecord(h2.sg, "Mode Is Include"),
IgmpRecord(h6.sg, "Mode Is Include"),
IgmpRecord(h4.sg, "Mode Is Include"),
IgmpRecord(h5.sg, "Mode Is Include")])
self.verify_report(capture[1],
[IgmpRecord(h8.sg, "Mode Is Include")])
self.verify_report(capture[2],
[IgmpRecord(h7.sg, "Mode Is Include")])
self.verify_report(capture[3],
[IgmpRecord(h9.sg, "Mode Is Include")])
#
# drop the MTU further (so a 128 sized group won't fit)
#
self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [512, 0, 0, 0])
self.pg_enable_capture(self.pg_interfaces)
self.pg_start()
h10 = VppHostState(self,
IGMP_FILTER.INCLUDE,
self.pg0.sw_if_index,
IgmpSG("238.1.1.3", src_list))
h10.add_vpp_config()
capture = self.pg0.get_capture(2, timeout=10)
#
# remove state, expect the report for the removal
# the dump should be empty
#
self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [600, 0, 0, 0])
self.remove_group(h8)
self.remove_group(h9)
self.remove_group(h2)
self.remove_group(h3)
self.remove_group(h4)
self.remove_group(h5)
self.remove_group(h6)
self.remove_group(h7)
self.remove_group(h10)
self.logger.info(self.vapi.cli("sh igmp config"))
self.assertFalse(self.vapi.igmp_dump())
#
# TODO
# ADD STATE ON MORE INTERFACES
#
self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
0,
IGMP_MODE.HOST)
def test_igmp_router(self):
""" IGMP Router Functions """
#
# Drop reports when not enabled
#
p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
options=[IPOption(copy_flag=1, optclass="control",
option="router_alert")]) /
IGMPv3(type="Version 3 Membership Report") /
IGMPv3mr(numgrp=1) /
IGMPv3gr(rtype="Allow New Sources",
maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0,
options=[IPOption(copy_flag=1, optclass="control",
option="router_alert")]) /
IGMPv3(type="Version 3 Membership Report") /
IGMPv3mr(numgrp=1) /
IGMPv3gr(rtype="Block Old Sources",
maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
self.send(self.pg0, p_j)
self.assertFalse(self.vapi.igmp_dump())
#
# drop the default timer values so these tests execute in a
# reasonable time frame
#
self.vapi.cli("test igmp timers query 1 src 3 leave 1")
#
# enable router functions on the interface
#
self.pg_enable_capture(self.pg_interfaces)
self.pg_start()
self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
1,
IGMP_MODE.ROUTER)
self.vapi.want_igmp_events(1)
#
# wait for router to send general query
#
for ii in range(3):
capture = self.pg0.get_capture(1, timeout=2)
self.verify_general_query(capture[0])
self.pg_enable_capture(self.pg_interfaces)
self.pg_start()
#
# re-send the report. VPP should now hold state for the new group
# VPP sends a notification that a new group has been joined
#
self.send(self.pg0, p_j)
self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
"239.1.1.1", "10.1.1.1", 1))
self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
"239.1.1.1", "10.1.1.2", 1))
dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
self.assertEqual(len(dump), 2)
self.assertTrue(find_igmp_state(dump, self.pg0,
"239.1.1.1", "10.1.1.1"))
self.assertTrue(find_igmp_state(dump, self.pg0,
"239.1.1.1", "10.1.1.2"))
#
# wait for the per-source timer to expire
# the state should be reaped
# VPP sends a notification that the group has been left
#
self.assertTrue(wait_for_igmp_event(self, 4, self.pg0,
"239.1.1.1", "10.1.1.1", 0))
self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
"239.1.1.1", "10.1.1.2", 0))
self.assertFalse(self.vapi.igmp_dump())
#
# resend the join. wait for two queries and then send a current-state
# record to include all sources. this should reset the exiry time
# on the sources and thus they will still be present in 2 seconds time.
# If the source timer was not refreshed, then the state would have
# expired in 3 seconds.
#
self.send(self.pg0, p_j)
self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
"239.1.1.1", "10.1.1.1", 1))
self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
"239.1.1.1", "10.1.1.2", 1))
dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
self.assertEqual(len(dump), 2)
capture = self.pg0.get_capture(2, timeout=3)
self.verify_general_query(capture[0])
self.verify_general_query(capture[1])
p_cs = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0,
options=[IPOption(copy_flag=1, optclass="control",
option="router_alert")]) /
IGMPv3(type="Version 3 Membership Report") /
IGMPv3mr(numgrp=1) /
IGMPv3gr(rtype="Mode Is Include",
maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
self.send(self.pg0, p_cs)
self.sleep(2)
dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
self.assertEqual(len(dump), 2)
self.assertTrue(find_igmp_state(dump, self.pg0,
"239.1.1.1", "10.1.1.1"))
self.assertTrue(find_igmp_state(dump, self.pg0,
"239.1.1.1", "10.1.1.2"))
#
# wait for the per-source timer to expire
# the state should be reaped
#
self.assertTrue(wait_for_igmp_event(self, 4, self.pg0,
"239.1.1.1", "10.1.1.1", 0))
self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
"239.1.1.1", "10.1.1.2", 0))
self.assertFalse(self.vapi.igmp_dump())
#
# resend the join, then a leave. Router sends a gruop+source
# specific query containing both sources
#
self.send(self.pg0, p_j)
self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
"239.1.1.1", "10.1.1.1", 1))
self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
"239.1.1.1", "10.1.1.2", 1))
dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
self.assertEqual(len(dump), 2)
self.send(self.pg0, p_l)
capture = self.pg0.get_capture(1, timeout=3)
self.verify_group_query(capture[0], "239.1.1.1",
["10.1.1.1", "10.1.1.2"])
#
# the group specific query drops the timeout to leave (=1) seconds
#
self.assertTrue(wait_for_igmp_event(self, 2, self.pg0,
"239.1.1.1", "10.1.1.1", 0))
self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
"239.1.1.1", "10.1.1.2", 0))
self.assertFalse(self.vapi.igmp_dump())
self.assertFalse(self.vapi.igmp_dump())
#
# a TO_EX({}) / IN_EX({}) is treated like a (*,G) join
#
p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
options=[IPOption(copy_flag=1, optclass="control",
option="router_alert")]) /
IGMPv3(type="Version 3 Membership Report") /
IGMPv3mr(numgrp=1) /
IGMPv3gr(rtype="Change To Exclude Mode", maddr="239.1.1.2"))
self.send(self.pg0, p_j)
self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
"239.1.1.2", "0.0.0.0", 1))
p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
options=[IPOption(copy_flag=1, optclass="control",
option="router_alert")]) /
IGMPv3(type="Version 3 Membership Report") /
IGMPv3mr(numgrp=1) /
IGMPv3gr(rtype="Mode Is Exclude", maddr="239.1.1.3"))
self.send(self.pg0, p_j)
self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
"239.1.1.3", "0.0.0.0", 1))
#
# A 'allow sourcees' for {} should be ignored as it should
# never be sent.
#
p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
options=[IPOption(copy_flag=1, optclass="control",
option="router_alert")]) /
IGMPv3(type="Version 3 Membership Report") /
IGMPv3mr(numgrp=1) /
IGMPv3gr(rtype="Allow New Sources", maddr="239.1.1.4"))
self.send(self.pg0, p_j)
dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
self.assertTrue(find_igmp_state(dump, self.pg0,
"239.1.1.2", "0.0.0.0"))
self.assertTrue(find_igmp_state(dump, self.pg0,
"239.1.1.3", "0.0.0.0"))
self.assertFalse(find_igmp_state(dump, self.pg0,
"239.1.1.4", "0.0.0.0"))
#
# a TO_IN({}) and IS_IN({}) are treated like a (*,G) leave
#
self.vapi.cli("set logging class igmp level debug")
p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
options=[IPOption(copy_flag=1, optclass="control",
option="router_alert")]) /
IGMPv3(type="Version 3 Membership Report") /
IGMPv3mr(numgrp=1) /
IGMPv3gr(rtype="Change To Include Mode", maddr="239.1.1.2"))
self.send(self.pg0, p_l)
self.assertTrue(wait_for_igmp_event(self, 2, self.pg0,
"239.1.1.2", "0.0.0.0", 0))
p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
options=[IPOption(copy_flag=1, optclass="control",
option="router_alert")]) /
IGMPv3(type="Version 3 Membership Report") /
IGMPv3mr(numgrp=1) /
IGMPv3gr(rtype="Mode Is Include", maddr="239.1.1.3"))
self.send(self.pg0, p_l)
self.assertTrue(wait_for_igmp_event(self, 2, self.pg0,
"239.1.1.3", "0.0.0.0", 0))
self.assertFalse(self.vapi.igmp_dump(self.pg0.sw_if_index))
#
# disable router config
#
self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
0,
IGMP_MODE.ROUTER)
def _create_igmpv3_pck(self, itf, rtype, maddr, srcaddrs):
p = (Ether(dst=itf.local_mac, src=itf.remote_mac) /
IP(src=itf.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
options=[IPOption(copy_flag=1, optclass="control",
option="router_alert")]) /
IGMPv3(type="Version 3 Membership Report") /
IGMPv3mr(numgrp=1) /
IGMPv3gr(rtype=rtype,
maddr=maddr, srcaddrs=srcaddrs))
return p
def test_igmp_proxy_device(self):
""" IGMP proxy device """
self.pg2.admin_down()
self.pg2.unconfig_ip4()
self.pg2.set_table_ip4(0)
self.pg2.config_ip4()
self.pg2.admin_up()
self.vapi.cli('test igmp timers query 10 src 3 leave 1')
# enable IGMP
self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 1, IGMP_MODE.HOST)
self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 1,
IGMP_MODE.ROUTER)
self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 1,
IGMP_MODE.ROUTER)
# create IGMP proxy device
self.vapi.igmp_proxy_device_add_del(0, self.pg0.sw_if_index, 1)
self.vapi.igmp_proxy_device_add_del_interface(0,
self.pg1.sw_if_index, 1)
self.vapi.igmp_proxy_device_add_del_interface(0,
self.pg2.sw_if_index, 1)
# send join on pg1. join should be proxied by pg0
p_j = self._create_igmpv3_pck(self.pg1, "Allow New Sources",
"239.1.1.1", ["10.1.1.1", "10.1.1.2"])
self.send(self.pg1, p_j)
capture = self.pg0.get_capture(1, timeout=1)
self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
["10.1.1.1", "10.1.1.2"]), "Allow New Sources")])
self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
# send join on pg2. join should be proxied by pg0.
# the group should contain only 10.1.1.3 as
# 10.1.1.1 was already reported
p_j = self._create_igmpv3_pck(self.pg2, "Allow New Sources",
"239.1.1.1", ["10.1.1.1", "10.1.1.3"])
self.send(self.pg2, p_j)
capture = self.pg0.get_capture(1, timeout=1)
self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
["10.1.1.3"]), "Allow New Sources")])
self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
# send leave on pg2. leave for 10.1.1.3 should be proxyed
# as pg2 was the only interface interested in 10.1.1.3
p_l = self._create_igmpv3_pck(self.pg2, "Block Old Sources",
"239.1.1.1", ["10.1.1.3"])
self.send(self.pg2, p_l)
capture = self.pg0.get_capture(1, timeout=2)
self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
["10.1.1.3"]), "Block Old Sources")])
self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
# disable igmp on pg1 (also removes interface from proxy device)
# proxy leave for 10.1.1.2. pg2 is still interested in 10.1.1.1
self.pg_enable_capture(self.pg_interfaces)
self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 0,
IGMP_MODE.ROUTER)
capture = self.pg0.get_capture(1, timeout=1)
self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
["10.1.1.2"]), "Block Old Sources")])
self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
# disable IGMP on pg0 and pg1.
# disabling IGMP on pg0 (proxy device upstream interface)
# removes this proxy device
self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 0, IGMP_MODE.HOST)
self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 0,
IGMP_MODE.ROUTER)
self.assertFalse(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
if __name__ == '__main__':
unittest.main(testRunner=VppTestRunner)