diff --git a/ansible/README.test.md b/ansible/README.test.md index 7774b4e5b49..871a161a47e 100644 --- a/ansible/README.test.md +++ b/ansible/README.test.md @@ -210,6 +210,12 @@ ansible-playbook test_sonic.yml -i {INVENTORY} --limit {DUT_NAME} -e testcase_na ``` - Requires switch connected to a VM set or PTF testbed +##### Network fuzz test +``` +ansible-playbook test_sonic.yml -i {INVENTORY} --limit {DUT_NAME} -e testcase_name=network_fuzz -e testbed_name={TESTBED_NAME} +``` +- Requires the switch connected to a PTF testbed + ##### NTP test ``` ansible-playbook test_sonic.yml -i {INVENTORY} --limit {DUT_NAME} -e testcase_name=ntp -e testbed_name={TESTBED_NAME} diff --git a/ansible/roles/test/files/ptftests/network_fuzz_test.py b/ansible/roles/test/files/ptftests/network_fuzz_test.py new file mode 100644 index 00000000000..c1a600801d8 --- /dev/null +++ b/ansible/roles/test/files/ptftests/network_fuzz_test.py @@ -0,0 +1,89 @@ +import logging +import ptf + +import random +from scapy.all import fuzz, RandIP, Raw +from scapy.layers.inet import IP, UDP, TCP +from ptf.base_tests import BaseTest +from ptf.testutils import * +from pprint import pformat + + +class NetworkFuzzTest(BaseTest): + def __init__(self): + BaseTest.__init__(self) + + def log(self, message): + logging.info(message) + + def setUp(self): + BaseTest.setUp(self) + self.dataplane = ptf.dataplane_instance + self.test_params = test_params_get() + self.dataplane.flush() + + def build_fuzz_ip_packet(self, src_ip, dest_ip): + return fuzz(IP(src=src_ip, dst=dest_ip, version=4)) + + def build_fuzz_tcp_packet(self, src_ip, dest_ip, dport=None): + self.log("Building TCP packet for {}->{} with dport={}".format( + src_ip, dest_ip, dport + )) + if dport: + layer4 = fuzz(TCP(dport=int(dport))) + else: + layer4 = fuzz(TCP()) + return IP(src=src_ip, dst=dest_ip)/layer4/fuzz(Raw()) + + def build_fuzz_udp_packet(self, src_ip, dest_ip, dport=None): + self.log("Building UDP packet for {}->{} with dport={}".format( + src_ip, dest_ip, dport + )) + if dport: + layer4 = fuzz(UDP(dport=int(dport))) + else: + layer4 = fuzz(UDP()) + return IP(src=src_ip, dst=dest_ip)/layer4/fuzz(Raw()) + + def check_param(self, param, default, required): + if param not in self.test_params: + if required: + raise Exception("Test parameter '%s' is required" % param) + self.test_params[param] = default + + def runTest(self): + self.log('test_params:\n' + pformat(self.test_params)) + self.check_param('port_list', '', required=True) + self.test_params['port_list'] = self.test_params['port_list'].split(',') + self.check_param('packet_type', 'ip', required=False) + self.check_param('packet_count', 1, required=False) + self.check_param('src_ip', None, required=False) + self.check_param('dest_ip', None, required=False) + self.check_param('dest_port', None, required=False) + + if not self.test_params['src_ip']: + self.test_params['src_ip'] = RandIP() + if not self.test_params['dest_ip']: + self.test_params['dest_ip'] = RandIP() + + common_params = { + 'src_ip': self.test_params['src_ip'], + 'dest_ip': self.test_params['dest_ip'], + } + if self.test_params['packet_type'] == 'ip': + pkt = self.build_fuzz_ip_packet(**common_params) + elif self.test_params['packet_type'] == 'tcp': + pkt = self.build_fuzz_tcp_packet(dport=self.test_params['dest_port'], + **common_params) + elif self.test_params['packet_type'] == 'udp': + pkt = self.build_fuzz_udp_packet(dport=self.test_params['dest_port'], + **common_params) + else: + raise ValueError('Unknown packet type: ' + self.test_params['packet_type']) + + for i in range(int(self.test_params['packet_count'])): + # Choose randomly from the port list + port = random.choice(self.test_params['port_list']) + self.log("Sending test packet #{} to device port {}: {}".format(i, port, pkt.summary())) + send(self, port, pkt) + self.dataplane.flush() diff --git a/ansible/roles/test/tasks/network_fuzz.yml b/ansible/roles/test/tasks/network_fuzz.yml new file mode 100644 index 00000000000..2236658d888 --- /dev/null +++ b/ansible/roles/test/tasks/network_fuzz.yml @@ -0,0 +1,77 @@ +- block: + - name: Gather interface information if not available + interface_facts: + when: ansible_interface_facts is not defined + + - name: Read the current interfaces status + shell: show interfaces status | head + register: show_interface_status + + - name: Store the indices for the ports that operationally 'up' + set_fact: candidate_ports="{{ candidate_ports|default([]) + [minigraph_port_indices[item.device]] }}" + with_items: ansible_interface_facts.values() + when: item.active and (item.device | search('Ethernet')) + + - name: Get the interface name that is connect to the first BGP neighbor + set_fact: + bgp_neighbor_iface: "{{ item['attachto'] }}" + with_items: "{{ minigraph_interfaces + minigraph_portchannel_interfaces}}" + when: minigraph_bgp[0]['peer_addr'] == item['addr'] + + - name: If bgp_neighbor_iface is a portchannel, get the port index of the first member + set_fact: + bgp_neighbor_port: "{{ minigraph_port_indices[minigraph_portchannels[bgp_neighbor_iface]['members'][0]] }}" + when: "'PortChannel' in bgp_neighbor_iface" + + - name: If bgp_neighbor_iface is not a portchannel, get the port index + set_fact: + bgp_neighbor_port: "{{ minigraph_port_indices[bgp_neighbor_iface] }}" + when: "'PortChannel' not in bgp_neighbor_iface" + + - name: Copy tests to the PTF container + copy: src=roles/test/files/ptftests dest=/root + delegate_to: "{{ ptf_host }}" + + - name: Spoof random TCP packets from the BGP neighbor to the BGP port on the SONiC switch + include: roles/test/tasks/network_fuzz/tcp_fuzz_random_port.yml + vars: + test_ports: "{{ [bgp_neighbor_port] }}" + src_ip: "{{ minigraph_bgp[0]['addr'] }}" + dest_ip: "{{ minigraph_bgp[0]['peer_addr'] }}" + dest_port: 179 + packet_count: 1000 + + - name: Run IP packet fuzz test to random ports + include: roles/test/tasks/network_fuzz/ip_fuzz_random_port.yml + vars: + packet_count: 10000 + test_ports: "{{ candidate_ports }}" + + - name: Run TCP packet fuzz test to random ports + include: roles/test/tasks/network_fuzz/tcp_fuzz_random_port.yml + vars: + packet_count: 10000 + test_ports: "{{ candidate_ports }}" + + - name: Run UDP packet fuzz test to random ports + include: roles/test/tasks/network_fuzz/udp_fuzz_random_port.yml + vars: + packet_count: 10000 + test_ports: "{{ candidate_ports }}" + + - name: Run IP packet fuzz test to a single port + include: roles/test/tasks/network_fuzz/ip_fuzz_random_port.yml + vars: + packet_count: 30000 + test_ports: "{{ candidate_ports[0] }}" + + rescue: + - name: Do basic sanity check after a failure and allow recovery + include: base_sanity.yml + vars: + recover: "{{ allow_recover }}" + + - name: Validate all interfaces are up and allow recovery + include: interface.yml + vars: + recover: "{{ allow_recover }}" diff --git a/ansible/roles/test/tasks/network_fuzz/ip_fuzz_random_port.yml b/ansible/roles/test/tasks/network_fuzz/ip_fuzz_random_port.yml new file mode 100644 index 00000000000..5e966e692e7 --- /dev/null +++ b/ansible/roles/test/tasks/network_fuzz/ip_fuzz_random_port.yml @@ -0,0 +1,34 @@ +- block: + - set_fact: + testname: ip_fuzz_random_port + run_dir: /tmp + out_dir: /tmp/ansible-loganalyzer-results + tests_location: "{{ 'roles/test/tasks' }}" + + - name: Initialize loganalyzer + include: roles/test/files/tools/loganalyzer/loganalyzer_init.yml + + - include: roles/test/tasks/ptf_runner.yml + vars: + ptf_test_name: IP packet fuzz test + ptf_test_dir: ptftests + ptf_test_path: network_fuzz_test.NetworkFuzzTest + ptf_platform: remote + ptf_platform_dir: ptftests + ptf_test_params: + - port_list='{{ test_ports | join(",") }}' + - packet_type='ip' + - packet_count='{{ packet_count | default(1) }}' + - src_ip='{{ src_ip | default(None) }}' + - dest_ip='{{ dest_ip | default(None) }}' + ptf_extra_options: "--relax --debug info --log-file /tmp/network_fuzz.NetworkFuzzTest.IP.{{lookup('pipe','date +%Y-%m-%d-%H:%M:%S')}}.log" + + - name: Check if logs contain any error messages + include: roles/test/files/tools/loganalyzer/loganalyzer_analyze.yml + - include: roles/test/files/tools/loganalyzer/loganalyzer_end.yml + + - name: Do a basic sanity check + include: roles/test/tasks/base_sanity.yml + + - name: Validate all interfaces are still up + include: roles/test/tasks/interface.yml diff --git a/ansible/roles/test/tasks/network_fuzz/tcp_fuzz_random_port.yml b/ansible/roles/test/tasks/network_fuzz/tcp_fuzz_random_port.yml new file mode 100644 index 00000000000..71dc8daa029 --- /dev/null +++ b/ansible/roles/test/tasks/network_fuzz/tcp_fuzz_random_port.yml @@ -0,0 +1,35 @@ +- block: + - set_fact: + testname: tcp_fuzz_random_port + run_dir: /tmp + out_dir: /tmp/ansible-loganalyzer-results + tests_location: "{{ 'roles/test/tasks' }}" + + - name: Initialize loganalyzer + include: roles/test/files/tools/loganalyzer/loganalyzer_init.yml + + - include: roles/test/tasks/ptf_runner.yml + vars: + ptf_test_name: TCP packet fuzz test + ptf_test_dir: ptftests + ptf_test_path: network_fuzz_test.NetworkFuzzTest + ptf_platform: remote + ptf_platform_dir: ptftests + ptf_test_params: + - port_list='{{ test_ports | join(",") }}' + - packet_type='tcp' + - packet_count='{{ packet_count | default(1) }}' + - src_ip='{{ src_ip | default(None) }}' + - dest_ip='{{ dest_ip | default(None) }}' + - dest_port='{{ dest_port | default(None) }}' + ptf_extra_options: "--relax --debug info --log-file /tmp/network_fuzz.NetworkFuzzTest.TCP.{{lookup('pipe','date +%Y-%m-%d-%H:%M:%S')}}.log" + + - name: Check if logs contain any error messages + include: roles/test/files/tools/loganalyzer/loganalyzer_analyze.yml + - include: roles/test/files/tools/loganalyzer/loganalyzer_end.yml + + - name: Do a basic sanity check + include: roles/test/tasks/base_sanity.yml + + - name: Validate all interfaces are still up + include: roles/test/tasks/interface.yml diff --git a/ansible/roles/test/tasks/network_fuzz/udp_fuzz_random_port.yml b/ansible/roles/test/tasks/network_fuzz/udp_fuzz_random_port.yml new file mode 100644 index 00000000000..4449003ae79 --- /dev/null +++ b/ansible/roles/test/tasks/network_fuzz/udp_fuzz_random_port.yml @@ -0,0 +1,35 @@ +- block: + - set_fact: + testname: udp_fuzz_random_port + run_dir: /tmp + out_dir: /tmp/ansible-loganalyzer-results + tests_location: "{{ 'roles/test/tasks' }}" + + - name: Initialize loganalyzer + include: roles/test/files/tools/loganalyzer/loganalyzer_init.yml + + - include: roles/test/tasks/ptf_runner.yml + vars: + ptf_test_name: UDP packet fuzz test + ptf_test_dir: ptftests + ptf_test_path: network_fuzz_test.NetworkFuzzTest + ptf_platform: remote + ptf_platform_dir: ptftests + ptf_test_params: + - port_list='{{ test_ports | join(",") }}' + - packet_type='udp' + - packet_count='{{ packet_count | default(1) }}' + - src_ip='{{ src_ip | default(None) }}' + - dest_ip='{{ dest_ip | default(None) }}' + - dest_port='{{ dest_port | default(None) }}' + ptf_extra_options: "--relax --debug info --log-file /tmp/network_fuzz.NetworkFuzzTest.UDP.{{lookup('pipe','date +%Y-%m-%d-%H:%M:%S')}}.log" + + - name: Check if logs contain any error messages + include: roles/test/files/tools/loganalyzer/loganalyzer_analyze.yml + - include: roles/test/files/tools/loganalyzer/loganalyzer_end.yml + + - name: Do a basic sanity check + include: roles/test/tasks/base_sanity.yml + + - name: Validate all interfaces are still up + include: roles/test/tasks/interface.yml diff --git a/ansible/roles/test/vars/testcases.yml b/ansible/roles/test/vars/testcases.yml index ee80eb25424..a757a0deb8b 100644 --- a/ansible/roles/test/vars/testcases.yml +++ b/ansible/roles/test/vars/testcases.yml @@ -177,6 +177,10 @@ testcases: filename: neighbour-mac-noptf.yml topologies: [t0, t0-64, t0-64-32, t0-116, t1, t1-lag, t1-64-lag, ptf32, ptf64] + network_fuzz: + filename: network_fuzz.yml + topologies: [t0, t0-16, t0-56, t0-64, t0-116, t1] + ntp: filename: ntp.yml topologies: [t0, t0-56, t0-64, t0-64-32, t0-116, t1, t1-lag, t1-64-lag, ptf32, ptf64]