#! /usr/bin/env python # standard modules import logging import os import re import subprocess import sys import time from fabric.api import run, env, sudo from harness import Test, TestSuite, TestPreRequisiteError sys.path.insert(1, '../binding') from core import ost_pb, emul, DroneProxy from rpc import RpcError from protocols.mac_pb2 import mac, Mac from protocols.ip4_pb2 import ip4, Ip4 from protocols.vlan_pb2 import vlan use_defaults = False # initialize defaults - drone host_name = '127.0.0.1' tx_port_number = -1 rx_port_number = -1 if sys.platform == 'win32': tshark = r'C:\Program Files\Wireshark\tshark.exe' else: tshark = 'tshark' # initialize defaults - DUT env.use_shell = False env.user = 'tc' env.password = 'tc' env.host_string = 'localhost:50022' dut_rx_port = 'eth1' dut_tx_port = 'eth2' dut_dst_mac = 0x0800278df2b4 #FIXME: hardcoding # setup logging log = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) # command-line option/arg processing if len(sys.argv) > 1: if sys.argv[1] in ('-d', '--use-defaults'): use_defaults = True if sys.argv[1] in ('-h', '--help'): print('%s [OPTION]...' % (sys.argv[0])) print('Options:') print(' -d --use-defaults run using default values') print(' -h --help show this help') sys.exit(0) print('') print('This test uses the following topology -') print('') print(' +-------+ +-------+') print(' | |Tx--->---Rx|-+ |') print(' | Drone | | v DUT |') print(' | |Rx---<---Tx|-+ |') print(' +-------+ +-------+') print('') print('Drone has 2 ports connected to DUT. Packets sent on the Tx port') print('are expected to be forwarded by the DUT and received back on the') print('Rx port') print('') suite = TestSuite() if not use_defaults: s = raw_input('Drone\'s Hostname/IP [%s]: ' % (host_name)) host_name = s or host_name s = raw_input('DUT\'s Hostname/IP [%s]: ' % (env.host_string)) env.host_string = s or env.host_string drone = DroneProxy(host_name) try: # ----------------------------------------------------------------- # # Baseline Configuration for subsequent testcases # NOTES # * All test cases will emulate devices on both rx and tx ports # * Each test case will create traffic streams corresponding to # the devices to check # ----------------------------------------------------------------- # # FIXME: get inputs for dut rx/tx ports # Enable IP forwarding on the DUT (aka make it a router) sudo('sysctl -w net.ipv4.ip_forward=1') # connect to drone log.info('connecting to drone(%s:%d)' % (drone.hostName(), drone.portNumber())) drone.connect() # retreive port id list log.info('retreiving port list') port_id_list = drone.getPortIdList() # retreive port config list log.info('retreiving port config for all ports') port_config_list = drone.getPortConfig(port_id_list) if len(port_config_list.port) == 0: log.warning('drone has no ports!') sys.exit(1) # print port list and find default tx/rx ports print('Port List') print('---------') for port in port_config_list.port: print('%d.%s (%s)' % (port.port_id.id, port.name, port.description)) # use a vhost port as default tx/rx port if ('vhost' in port.name or 'sun' in port.description.lower()): if tx_port_number < 0: tx_port_number = port.port_id.id elif rx_port_number < 0: rx_port_number = port.port_id.id if ('eth1' in port.name): tx_port_number = port.port_id.id if ('eth2' in port.name): rx_port_number = port.port_id.id if not use_defaults: p = raw_input('Tx Port Id [%d]: ' % (tx_port_number)) if p: tx_port_number = int(p) p = raw_input('Rx Port Id [%d]: ' % (rx_port_number)) if p: rx_port_number = int(p) if tx_port_number < 0 or rx_port_number < 0: log.warning('invalid tx/rx port') sys.exit(1) print('Using port %d as tx port(s)' % tx_port_number) print('Using port %d as rx port(s)' % rx_port_number) tx_port = ost_pb.PortIdList() tx_port.port_id.add().id = tx_port_number; rx_port = ost_pb.PortIdList() rx_port.port_id.add().id = rx_port_number; # ----------------------------------------------------------------- # # create emulated device(s) on tx/rx ports - each test case will # modify and reuse these devices as per its needs # ----------------------------------------------------------------- # emul_ports = ost_pb.PortIdList() emul_ports.port_id.add().id = tx_port_number; emul_ports.port_id.add().id = rx_port_number; # delete existing devices, if any, on tx port tx_dgid_list = drone.getDeviceGroupIdList(tx_port.port_id[0]) drone.deleteDeviceGroup(tx_dgid_list) # add a emulated device on tx port tx_dgid_list = ost_pb.DeviceGroupIdList() tx_dgid_list.port_id.CopyFrom(tx_port.port_id[0]) tx_dgid_list.device_group_id.add().id = 1 log.info('adding tx device_group %d' % tx_dgid_list.device_group_id[0].id) drone.addDeviceGroup(tx_dgid_list) # delete existing devices, if any, on rx port rx_dgid_list = drone.getDeviceGroupIdList(rx_port.port_id[0]) drone.deleteDeviceGroup(rx_dgid_list) # add a emulated device on rx port rx_dgid_list = ost_pb.DeviceGroupIdList() rx_dgid_list.port_id.CopyFrom(rx_port.port_id[0]) rx_dgid_list.device_group_id.add().id = 1 log.info('adding rx device_group %d' % rx_dgid_list.device_group_id[0].id) drone.addDeviceGroup(rx_dgid_list) # ----------------------------------------------------------------- # # create stream on tx port - each test case will modify and reuse # this stream as per its needs # ----------------------------------------------------------------- # # delete existing streams, if any, on tx port sid_list = drone.getStreamIdList(tx_port.port_id[0]) drone.deleteStream(sid_list) # add a stream stream_id = ost_pb.StreamIdList() stream_id.port_id.CopyFrom(tx_port.port_id[0]) stream_id.stream_id.add().id = 1 log.info('adding tx_stream %d' % stream_id.stream_id[0].id) drone.addStream(stream_id) # ================================================================= # # ----------------------------------------------------------------- # # TEST CASES # ----------------------------------------------------------------- # # ================================================================= # # ----------------------------------------------------------------- # # TESTCASE: Emulate multiple IPv4 devices (no vlans) # DUT # /.1 \.1 # / \ # 10.10.1/24 10.10.2/24 # / \ # /.101-105 \.101-105 # Host1(s) Host2(s) # ----------------------------------------------------------------- # passed = False suite.test_begin('multiEmulDevNoVlan') num_devs = 5 try: # configure the DUT sudo('ip address add 10.10.1.1/24 dev ' + dut_rx_port) sudo('ip address add 10.10.2.1/24 dev ' + dut_tx_port) # configure the tx device(s) devgrp_cfg = ost_pb.DeviceGroupConfigList() devgrp_cfg.port_id.CopyFrom(tx_port.port_id[0]) dg = devgrp_cfg.device_group.add() dg.device_group_id.id = tx_dgid_list.device_group_id[0].id dg.core.name = "Host1" d = dg.Extensions[emul.device] d.count = num_devs d.mac.address = 0x000102030a01 d.ip4.address = 0x0a0a0165 d.ip4.prefix_length = 24 d.ip4.default_gateway = 0x0a0a0101 drone.modifyDeviceGroup(devgrp_cfg) # configure the rx device(s) devgrp_cfg = ost_pb.DeviceGroupConfigList() devgrp_cfg.port_id.CopyFrom(rx_port.port_id[0]) dg = devgrp_cfg.device_group.add() dg.device_group_id.id = rx_dgid_list.device_group_id[0].id dg.core.name = "Host1" d = dg.Extensions[emul.device] d.count = num_devs d.mac.address = 0x000102030b01 d.ip4.address = 0x0a0a0265 d.ip4.prefix_length = 24 d.ip4.default_gateway = 0x0a0a0201 drone.modifyDeviceGroup(devgrp_cfg) # configure the tx stream stream_cfg = ost_pb.StreamConfigList() stream_cfg.port_id.CopyFrom(tx_port.port_id[0]) s = stream_cfg.stream.add() s.stream_id.id = stream_id.stream_id[0].id s.core.is_enabled = True s.control.packets_per_sec = 20 s.control.num_packets = 10 # setup stream protocols as mac:eth2:ip4:udp:payload p = s.protocol.add() p.protocol_id.id = ost_pb.Protocol.kMacFieldNumber p.Extensions[mac].dst_mac_mode = Mac.e_mm_resolve p.Extensions[mac].src_mac_mode = Mac.e_mm_resolve p = s.protocol.add() p.protocol_id.id = ost_pb.Protocol.kEth2FieldNumber p = s.protocol.add() p.protocol_id.id = ost_pb.Protocol.kIp4FieldNumber ip = p.Extensions[ip4] ip.src_ip = 0x0a0a0165 ip.src_ip_mode = Ip4.e_im_inc_host ip.src_ip_count = num_devs ip.dst_ip = 0x0a0a0265 ip.dst_ip_mode = Ip4.e_im_inc_host ip.dst_ip_count = num_devs s.protocol.add().protocol_id.id = ost_pb.Protocol.kUdpFieldNumber s.protocol.add().protocol_id.id = ost_pb.Protocol.kPayloadFieldNumber log.info('configuring tx_stream %d' % stream_id.stream_id[0].id) drone.modifyStream(stream_cfg) # clear tx/rx stats log.info('clearing tx/rx stats') drone.clearStats(tx_port) drone.clearStats(rx_port) # clear arp on DUT sudo('ip neigh flush all') arp_cache = run('ip neigh show') if re.search('10.10.2.10[1-5].*lladdr', arp_cache): raise TestPreRequisiteError('ARP cache not cleared') # resolve ARP on tx/rx ports log.info('resolving Neighbors on tx/rx ports ...') drone.startCapture(emul_ports) drone.clearDeviceNeighbors(emul_ports) drone.resolveDeviceNeighbors(emul_ports) time.sleep(10) drone.stopCapture(emul_ports) fail = 0 # verify ARP Requests sent out from tx port buff = drone.getCaptureBuffer(emul_ports.port_id[0]) drone.saveCaptureBuffer(buff, 'capture.pcap') log.info('dumping Tx capture buffer (all)') cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) print(cap_pkts) log.info('dumping Tx capture buffer (filtered)') for i in range(num_devs): cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', '-R', '(arp.opcode == 1)' ' && (arp.src.proto_ipv4 == 10.10.1.'+str(101+i)+')' ' && (arp.dst.proto_ipv4 == 10.10.1.1)']) print(cap_pkts) if cap_pkts.count('\n') != 1: fail = fail + 1 # verify *no* ARP Requests sent out from rx port buff = drone.getCaptureBuffer(emul_ports.port_id[1]) drone.saveCaptureBuffer(buff, 'capture.pcap') log.info('dumping Rx capture buffer (all)') cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) print(cap_pkts) log.info('dumping Rx capture buffer (filtered)') for i in range(num_devs): cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', '-R', '(arp.opcode == 1)' ' && (arp.src.proto_ipv4 == 10.10.2.'+str(101+i)+')' ' && (arp.dst.proto_ipv4 == 10.10.2.1)']) print(cap_pkts) if cap_pkts.count('\n') != 0: fail = fail + 1 # retrieve and verify ARP Table on tx/rx ports log.info('retrieving ARP entries on tx port') neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[0]) devices = neigh_list.Extensions[emul.devices] # TODO: verify gateway IP is resolved for each device # FIXME: needs device ip as part of neigh_list log.info('ARP Table on tx port') for device in devices: for arp in device.arp: # TODO: pretty print ip and mac print('%d: %08x %012x' % (device.device_index, arp.ip4, arp.mac)) log.info('retrieving ARP entries on rx port') neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[1]) devices = neigh_list.Extensions[emul.devices] log.info('ARP Table on rx port') for device in devices: # verify *no* ARPs learnt on rx port if len(device.arp): fail = fail + 1 for arp in device.arp: # TODO: pretty print ip and mac print('%d: %08x %012x' % (device.device_index, arp.ip4, arp.mac)) drone.startCapture(rx_port) drone.startTransmit(tx_port) log.info('waiting for transmit to finish ...') time.sleep(7) drone.stopTransmit(tx_port) drone.stopCapture(rx_port) buff = drone.getCaptureBuffer(rx_port.port_id[0]) drone.saveCaptureBuffer(buff, 'capture.pcap') log.info('dumping Rx capture buffer (all)') cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) print(cap_pkts) log.info('dumping Rx capture buffer (filtered)') for i in range(num_devs): cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', '-R', '(ip.src == 10.10.1.' + str(101+i) + ') ' ' && (ip.dst == 10.10.2.' + str(101+i) + ')' ' && (eth.dst == 00:01:02:03:0b:' + format(1+i, '02x')+')']) print(cap_pkts) if cap_pkts.count('\n') != s.control.num_packets/ip.src_ip_count: fail = fail + 1 if fail == 0: passed = True else: log.info('failed checks: %d' % fail) os.remove('capture.pcap') except RpcError as e: raise finally: drone.stopTransmit(tx_port) run('ip neigh show') # unconfigure DUT sudo('ip address delete 10.10.1.1/24 dev ' + dut_rx_port) sudo('ip address delete 10.10.2.1/24 dev ' + dut_tx_port) suite.test_end(passed) sys.exit(1) # FIXME: update the below test cases to resolve Neighbors and streams # to derive src/dst mac from device # ----------------------------------------------------------------- # # TESTCASE: Emulate multiple IPv4 device per multiple single-tag VLANs # # +- DUT -+ # .1/ \.1 # / \ # 10.1.1/24 10.1.2/24 # v[201-205] v[201-205] # / \ # /.101-103 \.101-103 # Host1(s) Host2(s) # ----------------------------------------------------------------- # passed = False suite.test_begin('multiEmulDevPerVlan') num_vlans = 5 vlan_base = 201 num_devs = 3 dev_ip_base = 101 try: # configure the DUT for i in range(num_vlans): vlan_id = vlan_base+i vrf = 'v' + str(vlan_id) vlan_rx_dev = dut_rx_port + '.' + str(vlan_id) vlan_tx_dev = dut_tx_port + '.' + str(vlan_id) sudo('ip netns add ' + vrf) sudo('ip link add link ' + dut_rx_port + ' name ' + vlan_rx_dev + ' type vlan id ' + str(vlan_id)) sudo('ip link set ' + vlan_rx_dev + ' netns ' + vrf) sudo('ip netns exec ' + vrf + ' ip addr add 10.1.1.1/24' + ' dev ' + vlan_rx_dev) sudo('ip netns exec ' + vrf + ' ip link set ' + vlan_rx_dev + ' up') sudo('ip link add link ' + dut_tx_port + ' name ' + vlan_tx_dev + ' type vlan id ' + str(vlan_id)) sudo('ip link set ' + vlan_tx_dev + ' netns ' + vrf) sudo('ip netns exec ' + vrf + ' ip addr add 10.1.2.1/24' + ' dev ' + vlan_tx_dev) sudo('ip netns exec ' + vrf + ' ip link set ' + vlan_tx_dev + ' up') # configure the tx device(s) devgrp_cfg = ost_pb.DeviceGroupConfigList() devgrp_cfg.port_id.CopyFrom(tx_port.port_id[0]) dg = devgrp_cfg.device_group.add() dg.device_group_id.id = tx_dgid_list.device_group_id[0].id dg.core.name = "Host1" v = dg.Extensions[emul.vlan].stack.add() v.vlan_tag = vlan_base v.count = num_vlans d = dg.Extensions[emul.device] d.count = num_devs d.mac.address = 0x000102030a01 d.ip4.address = 0x0a010165 d.ip4.prefix_length = 24 d.ip4.default_gateway = 0x0a0a0101 drone.modifyDeviceGroup(devgrp_cfg) # configure the rx device(s) devgrp_cfg = ost_pb.DeviceGroupConfigList() devgrp_cfg.port_id.CopyFrom(rx_port.port_id[0]) dg = devgrp_cfg.device_group.add() dg.device_group_id.id = rx_dgid_list.device_group_id[0].id dg.core.name = "Host1" v = dg.Extensions[emul.vlan].stack.add() v.vlan_tag = vlan_base v.count = num_vlans d = dg.Extensions[emul.device] d.count = num_devs #d.mode = emul.Device.kNoRepeat d.mac.address = 0x000102030b01 d.ip4.address = 0x0a010265 d.ip4.prefix_length = 24 d.ip4.default_gateway = 0x0a0a0201 drone.modifyDeviceGroup(devgrp_cfg) # configure the tx stream(s) # we need more than one stream, so delete old one # and create as many as we need # FIXME: restore the single stream at end? log.info('deleting tx_stream %d' % stream_id.stream_id[0].id) drone.deleteStream(stream_id) stream_id = ost_pb.StreamIdList() stream_id.port_id.CopyFrom(tx_port.port_id[0]) for i in range(num_vlans): stream_id.stream_id.add().id = i log.info('adding tx_stream %d' % stream_id.stream_id[i].id) drone.addStream(stream_id) stream_cfg = ost_pb.StreamConfigList() stream_cfg.port_id.CopyFrom(tx_port.port_id[0]) for i in range(num_vlans): s = stream_cfg.stream.add() s.stream_id.id = stream_id.stream_id[i].id s.core.is_enabled = True s.core.ordinal = i s.control.packets_per_sec = 10 s.control.num_packets = num_devs # setup stream protocols as mac:vlan:eth2:ip4:udp:payload p = s.protocol.add() p.protocol_id.id = ost_pb.Protocol.kMacFieldNumber p.Extensions[mac].dst_mac = dut_dst_mac p.Extensions[mac].src_mac = 0x00aabbccddee p = s.protocol.add() p.protocol_id.id = ost_pb.Protocol.kVlanFieldNumber p.Extensions[vlan].vlan_tag = vlan_base+i p = s.protocol.add() p.protocol_id.id = ost_pb.Protocol.kEth2FieldNumber p = s.protocol.add() p.protocol_id.id = ost_pb.Protocol.kIp4FieldNumber ip = p.Extensions[ip4] ip.src_ip = 0x0a010165 ip.src_ip_mode = Ip4.e_im_inc_host ip.dst_ip = 0x0a010265 ip.dst_ip_mode = Ip4.e_im_inc_host p = s.protocol.add() p.protocol_id.id = ost_pb.Protocol.kUdpFieldNumber p = s.protocol.add() p.protocol_id.id = ost_pb.Protocol.kPayloadFieldNumber log.info('configuring tx_stream %d' % stream_id.stream_id[0].id) drone.modifyStream(stream_cfg) # clear tx/rx stats log.info('clearing tx/rx stats') drone.clearStats(tx_port) drone.clearStats(rx_port) # clear arp on DUT for i in range(num_vlans): vlan_id = vlan_base + i vrf = 'v' + str(vlan_id) vlan_rx_dev = dut_rx_port + '.' + str(vlan_id) vlan_tx_dev = dut_tx_port + '.' + str(vlan_id) sudo('ip netns exec ' + vrf + ' ip neigh flush dev ' + vlan_rx_dev) sudo('ip netns exec ' + vrf + ' ip neigh flush dev ' + vlan_tx_dev) sudo('ip netns exec ' + vrf + ' ip neigh show') drone.startCapture(rx_port) drone.startTransmit(tx_port) log.info('waiting for transmit to finish ...') time.sleep(5) drone.stopTransmit(tx_port) drone.stopCapture(rx_port) buff = drone.getCaptureBuffer(rx_port.port_id[0]) drone.saveCaptureBuffer(buff, 'capture.pcap') log.info('dumping Rx capture buffer (all)') cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) print(cap_pkts) log.info('dumping Rx capture buffer (filtered)') fail = 0 for i in range(num_vlans): for j in range(num_devs): cap_pkts = subprocess.check_output( [tshark, '-nr', 'capture.pcap', '-R', '(vlan.id == ' + str(201+i) + ')' ' && (ip.src == 10.1.1.' + str(101+j) + ') ' ' && (ip.dst == 10.1.2.' + str(101+j) + ')' ' && (eth.dst == 00:01:02:03:0b:' + format(1+j, '02x')+')']) print(cap_pkts) if cap_pkts.count('\n') != 1: fail = fail + 1 if fail == 0: passed = True os.remove('capture.pcap') except RpcError as e: raise finally: drone.stopTransmit(tx_port) # show arp on DUT for i in range(num_vlans): vrf = 'v' + str(vlan_base + i) sudo('ip netns exec ' + vrf + ' ip neigh show') # un-configure the DUT for i in range(num_vlans): vlan_id = vlan_base + i vrf = 'v' + str(vlan_id) sudo('ip netns delete ' + vrf) suite.test_end(passed) # TODO: # ----------------------------------------------------------------- # # TESTCASE: Emulate one IPv4 device per multiple double-tag VLANs # vlanMode: repeat (default) # TESTCASE: Emulate multiple IPv4 devices per multiple double-tag VLANs # vlanMode: no-repeat; ip4Mode: repeat (default) # TESTCASE: Emulate multiple IPv4 devices per multiple quad-tag VLANs # vlanMode: repeat (default); ip4Mode: repeat (default) # TESTCASE: Emulate multiple IPv4 devices per multiple quad-tag VLANs # vlanMode: no-repeat; ip4Mode: no-repeat # ----------------------------------------------------------------- # suite.complete() # delete stream(s) log.info('deleting tx_stream %d' % stream_id.stream_id[0].id) drone.deleteStream(stream_id) # delete device(s) dgid_list = drone.getDeviceGroupIdList(tx_port.port_id[0]) drone.deleteDeviceGroup(dgid_list) dgid_list = drone.getDeviceGroupIdList(rx_port.port_id[0]) drone.deleteDeviceGroup(dgid_list) # bye for now drone.disconnect() #disconnect_all() except Exception as ex: log.exception(ex) finally: suite.report() if not suite.passed: sys.exit(2);