From 7b2ba21a2c436928ec287b9e1f79d63436e9932d Mon Sep 17 00:00:00 2001 From: Dor Marcous Date: Mon, 20 Mar 2017 13:43:52 +0200 Subject: [PATCH] IPv4 decapsulation test (#128) * decap test v1 * decap test added * checkout on fib_test * restoring ACL test * fixes * fix #2 * removed decap configuration in the test * added tcp port fields to simple_tcp_packet * removed some vars --- .../test/files/acstests/IP_decap_test.py | 189 ++++++++++++++++++ .../roles/test/files/acstests/router_utils.py | 26 ++- ansible/roles/test/tasks/decap.yml | 99 +++++++++ ansible/roles/test/tasks/sonic.yml | 5 + ansible/roles/test/templates/decap_conf.j2 | 13 ++ 5 files changed, 331 insertions(+), 1 deletion(-) create mode 100644 ansible/roles/test/files/acstests/IP_decap_test.py create mode 100644 ansible/roles/test/tasks/decap.yml create mode 100644 ansible/roles/test/templates/decap_conf.j2 diff --git a/ansible/roles/test/files/acstests/IP_decap_test.py b/ansible/roles/test/files/acstests/IP_decap_test.py new file mode 100644 index 0000000000..fff1e27bbd --- /dev/null +++ b/ansible/roles/test/files/acstests/IP_decap_test.py @@ -0,0 +1,189 @@ +''' +Owner: Dor Marcus +Created on: 12/09/2017 +Description: This file contains the Decapasulation test for SONIC, to test Decapsulation of IPv4 with double and triple encapsulated packets + + Design is available in https://github.com/Azure/SONiC/wiki/IPv4-Decapsulation-test + +Precondition: Before the test start, all routes need to be defined as in the route_info.txt file, in addition to the decap rule that need to be set as the dspc_mode +topology: The test need to run on non-lag systems with at least 31 active ports + +Usage: Examples of how to start the test + ptf --test-dir /root/dor/ ip_decap_test_red --platform remote -t "verbose=True;route_info='/tmp/route_info.txt';lo_ip='10.1.0.32';router_mac='00:02:03:04:05:00';dscp_mode='pipe'" --log-dir /tmp/logs --verbose +Parameters: route_info - The route_info file location + lo_ip - The loop_back IP that is configured in the decap rule + router_mac - The mac of the router_mac + dscp_mode - The rule for the dscp parameter in the decap packet that is configured in the JSON file ('pipe' for inner and 'uniform' for outer) + +''' + +#--------------------------------------------------------------------- +# Global imports +#--------------------------------------------------------------------- +import random +import time +import logging +import ptf.packet as scapy +import socket +import ptf.dataplane as dataplane + +from ptf.testutils import * +from ptf.mask import Mask +import ipaddress + +import os +import unittest + +import ptf +from ptf.base_tests import BaseTest +from ptf import config +import ptf.dataplane as dataplane +import ptf.testutils as testutils + +import pprint +from router_utils import * + + +#--------------------------------------------------------------------- +# Global variables +#--------------------------------------------------------------------- +PREFIX_AND_PORT_SPLITTER=" " +PORT_LIST_SPLITTER="," +PORT_COUNT = 31 + +class DecapPacketTest(BaseTest, RouterUtility): + def __init__(self): + BaseTest.__init__(self) + self.test_params = testutils.test_params_get() + #----------------------------------------------------------------- + def setUp(self): + ''' + @summary: Setup for the test + ''' + self.dataplane = ptf.dataplane_instance + self.router_mac = self.test_params['router_mac'] + #----------------------------------------------------------------- + + def send_and_verify(self, dst_ip, expected_ports, src_port, triple_encap = False): + ''' + @summary: This function builds encap packet, send and verify their arrival, When a packet will not arrived as expected an exeption will be throwen + @dst_ip: the destination ip for the inner IP header + @expected_ports: list of ports that a packet can arrived from + @src_port: the physical port that the packet will be sent from + @triple_encap: Bool if to send triple encapsulated packet + ''' + #setting parameters + src_mac = self.dataplane.get_mac(0, 0) + dst_mac = '00:11:22:33:44:55' + inner_src_ip = '2.2.2.2' + router_mac = self.test_params['router_mac'] + dscp_in = random.randint(0, 32) + tos_in = dscp_in << 2 + dscp_out = random.randint(0, 32) + tos_out = dscp_out << 2 + if ("pipe" == self.test_params['dscp_mode']): + exp_tos = tos_in + elif("uniform" == self.test_params['dscp_mode']): + exp_tos = tos_out + else: + print("ERROR: no dscp is configured") + exit() + + #building the packets and the expected packets + if (not triple_encap): + #for the double encap packet we will use IP header with TCP header without mac + inner_pkt = simple_ip_only_packet(ip_dst=dst_ip, ip_src=inner_src_ip, ip_ttl=64) + #after the decap process the retuning packet will be normal tcp packet, The TTL is taked from the inner layer and redused by one + exp_pkt = simple_tcp_packet(pktlen=114, + eth_dst=dst_mac, + eth_src=router_mac, + ip_dst=dst_ip, + ip_src=inner_src_ip, + ip_tos=exp_tos, + ip_ttl=63) + else: + #Building triple encap packet with SCAPY, because there is no PTF function for it, I use the defualt values for the TCP header + tcp_hdr = scapy.TCP(sport=1234, dport=80, flags="S", chksum=0) + inner_pkt2 = scapy.IP(src='4.4.4.4', dst='3.3.3.3', tos=0, ttl=64, id=1, ihl=None) / tcp_hdr + inner_pkt = scapy.IP(src=inner_src_ip, dst=dst_ip, tos=tos_in, ttl=64, id=1, ihl=None,proto =4) / inner_pkt2 + inner_pkt = inner_pkt/("".join([chr(x) for x in xrange(100 - len(inner_pkt))])) + #The expected packet is also built by scapy, and the TTL is taked from the inner layer and redused by one + exp_pkt = scapy.Ether(dst=dst_mac, src=router_mac)/inner_pkt + exp_pkt['IP'].tos = exp_tos #this parameter is taken by the decap rule configuration + exp_pkt['IP'].ttl = 63 + + pkt = simple_ipv4ip_packet( + eth_dst=router_mac, + eth_src=src_mac, + ip_src='1.1.1.1', + ip_dst=self.test_params['lo_ip'], + ip_tos=tos_out, + ip_ttl=random.randint(2, 63), + inner_frame=inner_pkt) + + #send and verify the return packets + masked_exp_pkt = Mask(exp_pkt) + masked_exp_pkt.set_do_not_care_scapy(scapy.Ether, "dst") + masked_exp_pkt.set_do_not_care_scapy(scapy.Ether, "src") + send_packet(self, src_port, pkt) + (match_index, rcv_pkt) = verify_packet_any_port(self, masked_exp_pkt, expected_ports) + #----------------------------------------------------------------- + + def runTest(self): + test_result = True + random.seed(1) + self.load_route_info(self.test_params["route_info"]) + default_route_ports =[] + unicast_ip = 'none' + unicast_dst_port = [] + print self.route_info.iteritems() + #running on the routes_info file and extractin ECMP route and unicast route + for prefix, port_index_list in self.route_info.iteritems(): + dest_ip_addr = prefix.split("/")[0] + + if (self.is_ipv6_address(dest_ip_addr)): + continue + + if (len(port_index_list) > 1): + for port_index in port_index_list: + if (len(port_index)> 0): + default_route_ports.append(int(port_index)) + default_route_dst_ip = dest_ip_addr + elif (len(port_index_list) == 1): + unicast_ip = dest_ip_addr + unicast_dst_port = [int(port_index_list[0])] + #when found unicast and ECMP routes stop + if ((unicast_ip != 'none') and (len(default_route_ports) != 0)): + break + + #Sending double and triple encapsulated packets from all ports with unicast and ECMP IP routes + for src_port in range(PORT_COUNT): + + try: + self.send_and_verify(default_route_dst_ip, default_route_ports, src_port) + except: + print("ERROR: failed to send encap packet with default route from port: " + str(src_port)) + test_result = False + + try: + self.send_and_verify(default_route_dst_ip, default_route_ports, src_port, True) + except: + print("ERROR: failed to send triple encap packet with default route from port: " + str(src_port)) + test_result = False + + try: + self.send_and_verify(unicast_ip, unicast_dst_port, src_port) + except: + print("ERROR: failed to send encap packet with unicast route from port: " + str(src_port)) + test_result = False + + try: + self.send_and_verify(unicast_ip, unicast_dst_port, src_port, True) + except: + print("ERROR: faield to send triple encap packet with unicast route from port: " + str(src_port)) + test_result = False + + assert(test_result) +#--------------------------------------------------------------------- + + diff --git a/ansible/roles/test/files/acstests/router_utils.py b/ansible/roles/test/files/acstests/router_utils.py index 9011a35732..222a598db3 100644 --- a/ansible/roles/test/files/acstests/router_utils.py +++ b/ansible/roles/test/files/acstests/router_utils.py @@ -19,6 +19,30 @@ PORT_COUNT = 32 class RouterUtility(): + route_info = {} + def load_route_info(self, route_info_path): + ''' + @summary: Load route_info file into self.route_info. For details see section 'Format of the route_info file' in the summary of the class. + @param route_info_path : Path to the file + ''' + with open(route_info_path, 'r') as route_info_file: + for line in route_info_file: + line = line.strip() + if (0==len(line)): + continue + prefix_ports_pair = line.split(PREFIX_AND_PORT_SPLITTER) + port_list = prefix_ports_pair[1].split(PORT_LIST_SPLITTER) + self.route_info[prefix_ports_pair[0]]=port_list + return + #--------------------------------------------------------------------- + + def print_route_info(self): + ''' + For diagnostic purposes only + ''' + pprint.pprint(self.route_info) + return + #--------------------------------------------------------------------- def is_ipv4_address(self, ipaddr): ''' @summary: Check address is valid IPv4 address. @@ -178,4 +202,4 @@ def check_route(self, pkt, masked_exp_pkt, source_port_index, dest_ip_addr, dest return received, match_index #--------------------------------------------------------------------- - \ No newline at end of file + diff --git a/ansible/roles/test/tasks/decap.yml b/ansible/roles/test/tasks/decap.yml new file mode 100644 index 0000000000..8bfb84e01c --- /dev/null +++ b/ansible/roles/test/tasks/decap.yml @@ -0,0 +1,99 @@ +#----------------------------------------- +# Run Decap test and Perform log analysis. +#----------------------------------------- + +- fail: msg="information about testbed missing." + when: (testbed_type is not defined) or + (lo_ip is not defined) or + (dscp_mode is not defined) + +- fail: msg="Invalid testbed_type value '{{testbed_type}}'" + when: testbed_type not in [ 'fib'] + +- fail: msg="Invalid testbed_type value '{{dscp_mode}}'" + when: dscp_mode not in [ 'pipe','uniform'] + +# Gather minigraph facts +- name: Gathering minigraph facts about the device + minigraph_facts: host={{ inventory_hostname }} + become: no + connection: local + +- name: Print neighbors in minigraph + debug: msg="{{ minigraph_neighbors }}" + +- name: Read port reverse alias mapping + set_fact: + alias_reverse_map: "{{ lookup('file', 'roles/sonicv2/files/ssw/{{ sonic_hwsku }}/alias_reverse_map.json') | from_json }}" + +- name: Print alias reverse mapping + debug: msg="{{ alias_reverse_map }}" + +# Generate file with BGP routes information +- template: src=fib.j2 dest=/tmp/fib.txt + connection: local + + + +- name: Copy route into file to the PTF host + copy: src="/tmp/fib.txt" dest="/tmp/route_info.txt" + delegate_to: "{{ ptf_host }}" + +- set_fact: + testname: decap + run_dir: /tmp + out_dir: /tmp/ansible-loganalyzer-results + test_match_file: decap_match_messages.txt + test_ignore_file: decap_ignore_messages.txt + test_expect_file: decap_expect_messages.txt + match_file: loganalyzer_common_match.txt + ignore_file: loganalyzer_common_ignore.txt + tests_location: "{{ 'roles/test/tasks' }}" + +# Separate set_fact is required to be able to use 'testname' fact. +- set_fact: + testname_unique: "{{ testname }}.{{ ansible_date_time.date}}.{{ ansible_date_time.hour}}-{{ ansible_date_time.minute}}-{{ ansible_date_time.second}}" + +# Separate set_fact is required to be able to use 'testname_unique' fact. +- set_fact: + test_out_dir: "{{ out_dir }}/{{testname_unique}}" + match_file_list: "{{ run_dir }}/{{test_match_file}},{{ run_dir }}/{{match_file}}" + ignore_file_list: "{{ run_dir }}/{{test_ignore_file}},{{ run_dir }}/{{ignore_file}}" + +- debug: msg="output directory for current test run {{ test_out_dir }}" +- debug: msg="generated run id:{{testname_unique}}" + +- include: roles/test/files/tools/loganalyzer/loganalyzer_init.yml + +- debug : msg="INVOKE DECAP TEST" + +- block: + + + + - name: copy the test to ptf container + copy: src=roles/test/files/acstests dest=/root + delegate_to: "{{ ptf_host }}" + + - name: "Running test {{ testname }}" + shell: ptf --test-dir acstests IP_decap_test --platform remote -t "verbose=True; router_mac='{{ ansible_Ethernet0['macaddress'] }}';lo_ip='{{lo_ip}}';dscp_mode='{{dscp_mode}}'; route_info='/tmp/route_info.txt'" --relax + args: + chdir: /root + delegate_to: "{{ ptf_host }}" + register: out + + - debug: var=out.stdout_lines + when: out.rc != 0 + + always: + + + + - include: roles/test/files/tools/loganalyzer/loganalyzer_analyze.yml + + # Output content of result files to ansible console + - shell: cat {{ test_out_dir }}/* + register: out + - debug: var=out.stdout_lines + + - include: roles/test/files/tools/loganalyzer/loganalyzer_end.yml diff --git a/ansible/roles/test/tasks/sonic.yml b/ansible/roles/test/tasks/sonic.yml index ea86e40412..0546bac2c3 100644 --- a/ansible/roles/test/tasks/sonic.yml +++ b/ansible/roles/test/tasks/sonic.yml @@ -69,6 +69,11 @@ include: arpall.yml tags: arp + +- name: Decap test + include: decap.yml + tags: decap + ### When calling this FIB test, please add command line of what testbed_type and which PTF docker to test against ### -e "testbed_type=t1-lag ptf_host=10.0.0.200" - name: Fib test diff --git a/ansible/roles/test/templates/decap_conf.j2 b/ansible/roles/test/templates/decap_conf.j2 new file mode 100644 index 0000000000..91ce121fc1 --- /dev/null +++ b/ansible/roles/test/templates/decap_conf.j2 @@ -0,0 +1,13 @@ +[ + { + "TUNNEL_DECAP_TABLE:NETBOUNCER" : { + "tunnel_type":"IPINIP", + "dst_ip":"{{lo_ip}}", + "src_ip":"1.1.1.1", + "dscp_mode":"{{dscp_mode}}", + "ecn_mode":"standard", + "ttl_mode":"pipe" + }, + "OP": "SET" + } +]