diff options
author | Yaroslav Brustinov <ybrustin@cisco.com> | 2017-01-29 17:14:41 +0200 |
---|---|---|
committer | Yaroslav Brustinov <ybrustin@cisco.com> | 2017-02-02 13:42:36 +0200 |
commit | 39000f461de6b85877db85488b1cc7f1fad9d359 (patch) | |
tree | 8ffa214f3876009bf8778881c63b6c245244ac41 /scripts | |
parent | 790059069915a700905f4746b22a9a4a6cadc6ad (diff) |
ipv6 scan & ping
Change-Id: I4f8112b4c942d149da5ea3f0ee01ac82d7fe32cc
Signed-off-by: Yaroslav Brustinov <ybrustin@cisco.com>
Diffstat (limited to 'scripts')
14 files changed, 522 insertions, 56 deletions
diff --git a/scripts/automation/trex_control_plane/stl/console/trex_console.py b/scripts/automation/trex_control_plane/stl/console/trex_console.py index 5e68cdf0..5d758fba 100755 --- a/scripts/automation/trex_control_plane/stl/console/trex_console.py +++ b/scripts/automation/trex_control_plane/stl/console/trex_console.py @@ -350,6 +350,11 @@ class TRexConsole(TRexGeneralCmd): '''Resolve ARP for ports''' self.stateless_client.resolve_line(line) + @verify_connected + def do_scan6(self, line): + '''Search for IPv6 neighbors''' + self.stateless_client.scan6_line(line) + def help_resolve (self): self.do_resolve("-h") diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/rx_services/trex_stl_rx_service_api.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/rx_services/trex_stl_rx_service_api.py index b0904382..d6a620aa 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/rx_services/trex_stl_rx_service_api.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/rx_services/trex_stl_rx_service_api.py @@ -9,10 +9,16 @@ class RXServiceAPI(object): LAYER_MODE_L2 = 1 LAYER_MODE_L3 = 2 - def __init__ (self, port, layer_mode = LAYER_MODE_ANY, queue_size = 100): + def __init__(self, port, layer_mode = LAYER_MODE_ANY, queue_size = 100, timeout = None, retries = None, retry_delay = 0.1): self.port = port self.queue_size = queue_size self.layer_mode = layer_mode + self.timeout = timeout + self.retries = retries + if retries is None and timeout is None: + self.retries = 0 + self.retry_delay = retry_delay + self.init_ts = time.time() ################### virtual methods ###################### @@ -47,7 +53,7 @@ class RXServiceAPI(object): """ raise NotImplementedError() - def on_pkt_rx (self, pkt, start_ts): + def on_pkt_rx(self, pkt, start_ts): """ called for each packet arriving on RX @@ -65,13 +71,10 @@ class RXServiceAPI(object): raise NotImplementedError() - def on_timeout_err (self, retries): + def on_timeout(self): """ called when a timeout occurs - :parameters: - retries - how many times was the service retring before failing - :returns: RC object @@ -80,7 +83,7 @@ class RXServiceAPI(object): ##################### API ###################### - def execute (self, retries = 0): + def execute(self, *a, **k): # sanity check rc = self.__sanity() @@ -97,12 +100,12 @@ class RXServiceAPI(object): try: # add the stream(s) - self.port.add_streams(self.generate_request()) + self.port.add_streams(self.generate_request(*a, **k)) rc = self.port.set_rx_queue(size = self.queue_size) if not rc: return rc - return self.__execute_internal(retries) + return self.__execute_internal() finally: # best effort restore @@ -137,20 +140,21 @@ class RXServiceAPI(object): # main resolve function - def __execute_internal (self, retries): + def __execute_internal (self): - # retry for 'retries' + # retry for 'retries' or until timeout index = 0 while True: rc = self.execute_iteration() if rc is not None: return rc - if index >= retries: - return self.on_timeout_err(retries) + if (self.retries is not None and index >= self.retries or + self.timeout is not None and time.time() - self.init_ts >= self.timeout): + return self.on_timeout() index += 1 - time.sleep(0.1) + time.sleep(self.retry_delay) diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/rx_services/trex_stl_rx_service_arp.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/rx_services/trex_stl_rx_service_arp.py index 3cf97045..a82c66d4 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/rx_services/trex_stl_rx_service_arp.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/rx_services/trex_stl_rx_service_arp.py @@ -8,8 +8,8 @@ from scapy.layers.l2 import Ether, ARP class RXServiceARP(RXServiceAPI): - def __init__ (self, port_id): - super(RXServiceARP, self).__init__(port_id, layer_mode = RXServiceAPI.LAYER_MODE_L3) + def __init__(self, port_id, *a, **k): + super(RXServiceARP, self).__init__(port_id, layer_mode = RXServiceAPI.LAYER_MODE_L3, *a, **k) def get_name (self): return "ARP" @@ -49,8 +49,8 @@ class RXServiceARP(RXServiceAPI): return self.port.ok({'psrc' : arp.psrc, 'hwsrc': arp.hwsrc}) - def on_timeout_err (self, retries): - return self.port.err('failed to receive ARP response ({0} retries)'.format(retries)) + def on_timeout(self): + return self.port.err('failed to receive ARP response ({0} retries)'.format(self.retries)) diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/rx_services/trex_stl_rx_service_icmp.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/rx_services/trex_stl_rx_service_icmp.py index ae57b161..612b6a2d 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/rx_services/trex_stl_rx_service_icmp.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/rx_services/trex_stl_rx_service_icmp.py @@ -9,9 +9,9 @@ from scapy.layers.inet import IP, ICMP class RXServiceICMP(RXServiceAPI): - def __init__ (self, port, ping_ip, pkt_size): + def __init__ (self, port, ping_ip, pkt_size, *a, **k): - super(RXServiceICMP, self).__init__(port, layer_mode = RXServiceAPI.LAYER_MODE_L3) + super(RXServiceICMP, self).__init__(port, layer_mode = RXServiceAPI.LAYER_MODE_L3, *a, **k) self.ping_ip = ping_ip self.pkt_size = pkt_size @@ -78,6 +78,6 @@ class RXServiceICMP(RXServiceAPI): # return the str of a timeout err - def on_timeout_err (self, retries): + def on_timeout(self): return self.port.ok('Request timed out.') diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/rx_services/trex_stl_rx_service_ipv6.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/rx_services/trex_stl_rx_service_ipv6.py new file mode 100755 index 00000000..c2c8ebc1 --- /dev/null +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/rx_services/trex_stl_rx_service_ipv6.py @@ -0,0 +1,179 @@ +from .trex_stl_rx_service_api import RXServiceAPI + +from ..trex_stl_streams import STLStream, STLTXSingleBurst +from ..trex_stl_packet_builder_scapy import * +from ..trex_stl_types import * + +from scapy.layers.l2 import Ether +from scapy.layers.inet6 import * +import time + + +class RXServiceIPv6(RXServiceAPI): + + def __init__(self, port, dst_ip, *a, **k): + RXServiceAPI.__init__(self, port, *a, **k) + self.attr = port.get_ts_attr() + self.src_mac = self.attr['layer_cfg']['ether']['src'] + mac_for_ip = port.info.get('hw_mac', self.src_mac) + self.src_ip = generate_ipv6(mac_for_ip) + self.mld_ip = generate_ipv6_solicited_node(mac_for_ip) + self.dst_ip = dst_ip + self.responses = {} + + def pre_execute(self): + return self.port.ok() + + def send_intermediate(self, streams): + self.port.remove_all_streams() + self.port.add_streams(streams) + mult = {'op': 'abs', 'type' : 'percentage', 'value': 100} + self.port.start(mul = mult, force = True, duration = -1, mask = 0xffffffff) + + def generate_ns(self, dst_mac, dst_ip): + pkt = (Ether(src = self.src_mac, dst = dst_mac) / + IPv6(src = self.src_ip, dst = dst_ip) / + ICMPv6ND_NS(tgt = dst_ip) / + ICMPv6NDOptSrcLLAddr(lladdr = self.src_mac)) + return STLStream(packet = STLPktBuilder(pkt = pkt), mode = STLTXSingleBurst(total_pkts = 2)) + + def generate_na(self, dst_mac, dst_ip): + pkt = (Ether(src = self.src_mac, dst = dst_mac) / + IPv6(src = self.src_ip, dst = dst_ip) / + ICMPv6ND_NA(tgt = self.src_ip, R = 0, S = 1, O = 1)) + return STLStream(packet = STLPktBuilder(pkt = pkt), mode = STLTXSingleBurst(total_pkts = 2)) + + def generate_ns_na(self, dst_mac, dst_ip): + return [self.generate_ns(dst_mac, dst_ip), self.generate_na(dst_mac, dst_ip)] + + def execute(self, *a, **k): + mult = self.attr['multicast']['enabled'] + try: + if mult != True: + self.port.set_attr(multicast = True) # response might be multicast + return RXServiceAPI.execute(self, *a, **k) + finally: + if mult != True: + self.port.set_attr(multicast = False) + + +class RXServiceIPv6Scan(RXServiceIPv6): + ''' Ping with given IPv6 (usually all nodes address) and wait for responses until timeout ''' + + def get_name(self): + return 'IPv6 scanning' + + def generate_request(self): + dst_mac = multicast_mac_from_ipv6(self.dst_ip) + dst_mld_ip = 'ff02::16' + dst_mld_mac = multicast_mac_from_ipv6(dst_mld_ip) + + mld_pkt = (Ether(src = self.src_mac, dst = dst_mld_mac) / + IPv6(src = self.src_ip, dst = dst_mld_ip, hlim = 1) / + IPv6ExtHdrHopByHop(options = [RouterAlert(), PadN()]) / + ICMPv6MLReportV2() / + MLDv2Addr(type = 4, len = 0, multicast_addr = 'ff02::2')) + ping_pkt = (Ether(src = self.src_mac, dst = dst_mac) / + IPv6(src = self.src_ip, dst = self.dst_ip, hlim = 1) / + ICMPv6EchoRequest()) + + mld_stream = STLStream(packet = STLPktBuilder(pkt = mld_pkt), + mode = STLTXSingleBurst(total_pkts = 2)) + ping_stream = STLStream(packet = STLPktBuilder(pkt = ping_pkt), + mode = STLTXSingleBurst(total_pkts = 2)) + return [mld_stream, ping_stream] + + + def on_pkt_rx(self, pkt, start_ts): + # convert to scapy + scapy_pkt = Ether(pkt['binary']) + + if scapy_pkt.haslayer('ICMPv6ND_NS') and scapy_pkt.haslayer('ICMPv6NDOptSrcLLAddr'): + node_mac = scapy_pkt.getlayer(ICMPv6NDOptSrcLLAddr).lladdr + node_ip = scapy_pkt.getlayer(IPv6).src + if node_ip not in self.responses: + self.send_intermediate(self.generate_ns_na(node_mac, node_ip)) + + elif scapy_pkt.haslayer('ICMPv6ND_NA'): + is_router = scapy_pkt.getlayer(ICMPv6ND_NA).R + node_ip = scapy_pkt.getlayer(ICMPv6ND_NA).tgt + dst_ip = scapy_pkt.getlayer(IPv6).dst + node_mac = scapy_pkt.src + if node_ip not in self.responses and dst_ip == self.src_ip: + self.responses[node_ip] = {'type': 'Router' if is_router else 'Host', 'mac': node_mac} + + elif scapy_pkt.haslayer('ICMPv6EchoReply'): + node_mac = scapy_pkt.src + node_ip = scapy_pkt.getlayer(IPv6).src + if node_ip == self.dst_ip: + return self.port.ok([{'ipv6': node_ip, 'mac': node_mac}]) + if node_ip not in self.responses: + self.send_intermediate(self.generate_ns_na(node_mac, node_ip)) + + + def on_timeout(self): + return self.port.ok([{'type': v['type'], 'mac': v['mac'], 'ipv6': k} for k, v in self.responses.items()]) + + +class RXServiceICMPv6(RXServiceIPv6): + ''' + Ping some IPv6 location. + If the dest MAC is found from scanning, use it. + Otherwise, send to default port dest. + ''' + + def __init__(self, port, pkt_size, dst_mac = None, *a, **k): + super(RXServiceICMPv6, self).__init__(port, *a, **k) + self.pkt_size = pkt_size + self.dst_mac = dst_mac + + def get_name(self): + return 'PING6' + + def pre_execute(self): + if self.dst_mac is None and not self.port.is_resolved(): + return self.port.err('ping - port has an unresolved destination, cannot determine next hop MAC address') + return self.port.ok() + + + # return a list of streams for request + def generate_request(self): + attrs = self.port.get_ts_attr() + + if self.dst_mac is None: + self.dst_mac = attrs['layer_cfg']['ether']['dst'] + + ping_pkt = (Ether(src = self.src_mac, dst = self.dst_mac) / + IPv6(src = self.src_ip, dst = self.dst_ip) / + ICMPv6EchoRequest()) + pad = max(0, self.pkt_size - len(ping_pkt)) + ping_pkt /= pad * 'x' + + return STLStream( packet = STLPktBuilder(pkt = ping_pkt), mode = STLTXSingleBurst(total_pkts = 2) ) + + + def on_pkt_rx(self, pkt, start_ts): + scapy_pkt = Ether(pkt['binary']) + + if scapy_pkt.haslayer('ICMPv6EchoReply'): + node_ip = scapy_pkt.getlayer(IPv6).src + hlim = scapy_pkt.getlayer(IPv6).hlim + dst_ip = scapy_pkt.getlayer(IPv6).dst + + if dst_ip != self.src_ip: # not our ping + return + + dt = pkt['ts'] - start_ts + return self.port.ok('Reply from {0}: bytes={1}, time={2:.2f}ms, hlim={3}'.format(node_ip, len(pkt['binary']), dt * 1000, hlim)) + + if scapy_pkt.haslayer('ICMPv6ND_NS') and scapy_pkt.haslayer('ICMPv6NDOptSrcLLAddr'): + node_mac = scapy_pkt.getlayer(ICMPv6NDOptSrcLLAddr).lladdr + node_ip = scapy_pkt.getlayer(IPv6).src + self.send_intermediate(self.generate_ns_na(node_mac, node_ip)) + + + # return the str of a timeout err + def on_timeout(self): + return self.port.ok('Request timed out.') + + diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py index c88a68b2..a6aa47aa 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py @@ -13,12 +13,12 @@ from .trex_stl_async_client import CTRexAsyncClient from .utils import parsing_opts, text_tables, common from .utils.common import * +from .utils.text_tables import TRexTextTable from .utils.text_opts import * from functools import wraps from texttable import ansi_len - -from collections import namedtuple +from collections import namedtuple, defaultdict from yaml import YAMLError import time import datetime @@ -547,7 +547,7 @@ class STLClient(object): self.connected = False # API classes - self.api_vers = [ {'type': 'core', 'major': 3, 'minor': 0 } ] + self.api_vers = [ {'type': 'core', 'major': 3, 'minor': 1 } ] self.api_h = {'core': None} # logger @@ -820,6 +820,17 @@ class STLClient(object): return rc + def __scan6(self, port_id_list = None, timeout = 5): + port_id_list = self.__ports(port_id_list) + + resp = {} + + for port_id in port_id_list: + resp[port_id] = self.ports[port_id].scan6(timeout) + + return resp + + def __set_port_attr (self, port_id_list = None, attr_dict = None): port_id_list = self.__ports(port_id_list) @@ -1893,13 +1904,13 @@ class STLClient(object): @__api_check(True) - def ping_ip (self, src_port, dst_ipv4, pkt_size = 64, count = 5, interval_sec = 1): + def ping_ip (self, src_port, dst_ip, pkt_size = 64, count = 5, interval_sec = 1): """ Pings an IP address through a port :parameters: src_port - on which port_id to send the ICMP PING request - dst_ipv4 - which IP to ping + dst_ip - which IP to ping pkt_size - packet size to use count - how many times to ping interval_sec - how much time to wait between pings @@ -1912,8 +1923,8 @@ class STLClient(object): if src_port not in self.get_all_ports(): raise STLError("src port is not a valid port id") - if not is_valid_ipv4(dst_ipv4): - raise STLError("dst_ipv4 is not a valid IPv4 address: '{0}'".format(dst_ipv4)) + if not (is_valid_ipv4(dst_ip) or is_valid_ipv6(dst_ip)): + raise STLError("dst_ip is not a valid IPv4/6 address: '{0}'".format(dst_ip)) if (pkt_size < 64) or (pkt_size > 9216): raise STLError("pkt_size should be a value between 64 and 9216: '{0}'".format(pkt_size)) @@ -1921,15 +1932,23 @@ class STLClient(object): validate_type('count', count, int) validate_type('interval_sec', interval_sec, (int, float)) - self.logger.pre_cmd("Pinging {0} from port {1} with {2} bytes of data:".format(dst_ipv4, + self.logger.pre_cmd("Pinging {0} from port {1} with {2} bytes of data:".format(dst_ip, src_port, pkt_size)) # no async messages with self.logger.supress(level = LoggerApi.VERBOSE_REGULAR_SYNC): self.logger.log('') + dst_mac = None + if ':' in dst_ip: # ipv6 + rc = self.ports[src_port].scan6(dst_ip = dst_ip) + if not rc: + raise STLError(rc) + replies = rc.data() + if len(replies) == 1: + dst_mac = replies[0]['mac'] for i in range(count): - rc = self.ports[src_port].ping(ping_ipv4 = dst_ipv4, pkt_size = pkt_size) + rc = self.ports[src_port].ping(ping_ip = dst_ip, pkt_size = pkt_size, dst_mac = dst_mac) if not rc: raise STLError(rc) @@ -2895,7 +2914,8 @@ class STLClient(object): link_up = None, led_on = None, flow_ctrl = None, - resolve = True): + resolve = True, + multicast = None): """ Set port attributes @@ -2905,6 +2925,7 @@ class STLClient(object): led_on - True or False flow_ctrl - 0: disable all, 1: enable tx side, 2: enable rx side, 3: full enable resolve - if true, in case a destination address is configured as IPv4 try to resolve it + multicast - enable receiving multicast, True or False :raises: + :exe:'STLError' @@ -2918,6 +2939,7 @@ class STLClient(object): validate_type('link_up', link_up, (bool, type(None))) validate_type('led_on', led_on, (bool, type(None))) validate_type('flow_ctrl', flow_ctrl, (int, type(None))) + validate_type('multicast', multicast, (bool, type(None))) # common attributes for all ports cmn_attr_dict = {} @@ -2926,6 +2948,7 @@ class STLClient(object): cmn_attr_dict['link_status'] = link_up cmn_attr_dict['led_status'] = led_on cmn_attr_dict['flow_ctrl_mode'] = flow_ctrl + cmn_attr_dict['multicast'] = multicast # each port starts with a set of the common attributes attr_dict = [dict(cmn_attr_dict) for _ in ports] @@ -3022,8 +3045,62 @@ class STLClient(object): # alias arp = resolve - - + + + @__api_check(True) + def scan6(self, ports = None, timeout = 5, verbose = False): + """ + Search for IPv6 devices on ports + + :parameters: + ports - for which ports to apply a unique sniffer (each port gets a unique file) + timeout - how much time to wait for responses + verbose - log for each request the response + :return: + list of dictionaries per neighbor: + type - type of device: 'Router' or 'Host' + mac - MAC address of device + ipv6 - IPv6 address of device + :raises: + + :exe:'STLError' + + """ + ports = ports if ports is not None else self.get_acquired_ports() + ports = self._validate_port_list(ports) + + self.logger.pre_cmd('Scanning network for IPv6 nodes on port(s) {0}:'.format(ports)) + + with self.logger.supress(level = LoggerApi.VERBOSE_REGULAR_SYNC): + rc_per_port = self.__scan6(ports, timeout) + + self.logger.post_cmd(rc_per_port) + + if verbose: + for port, rc in rc_per_port.items(): + if not rc: + self.logger.log(format_text(rc, 'bold')) + elif rc.data(): + scan_table = TRexTextTable() + scan_table.set_cols_align(['c', 'c', 'l']) + scan_table.header(['Device', 'MAC', 'IPv6 address']) + scan_table.set_cols_width([9, 19, 42]) + + resp = 'Port %s - IPv6 search result:' % port + self.logger.log(format_text(resp, 'bold')) + node_types = defaultdict(list) + for reply in rc.data(): + node_types[reply['type']].append(reply) + for key in sorted(node_types.keys()): + for reply in node_types[key]: + scan_table.add_row([key, reply['mac'], reply['ipv6']]) + self.logger.log(scan_table.draw()) + self.logger.log('') + else: + self.logger.log(format_text('Port %s: no replies! Try to ping with explicit address.' % port, 'bold')) + + return rc_per_port + + @__api_check(True) def start_capture (self, tx_ports = None, rx_ports = None, limit = 1000, mode = 'fixed'): """ @@ -3372,7 +3449,7 @@ class STLClient(object): "ping", self.ping_line.__doc__, parsing_opts.SINGLE_PORT, - parsing_opts.PING_IPV4, + parsing_opts.PING_IP, parsing_opts.PKT_SIZE, parsing_opts.PING_COUNT) @@ -3382,7 +3459,7 @@ class STLClient(object): # IP ping # source ports maps to ports as a single port - self.ping_ip(opts.ports[0], opts.ping_ipv4, opts.pkt_size, opts.count) + self.ping_ip(opts.ports[0], opts.ping_ip, opts.pkt_size, opts.count) @__console @@ -3918,6 +3995,7 @@ class STLClient(object): parsing_opts.LED_STATUS, parsing_opts.FLOW_CTRL, parsing_opts.SUPPORTED, + parsing_opts.MULTICAST, ) opts = parser.parse_args(line.split(), default_ports = self.get_acquired_ports()) @@ -3925,6 +4003,7 @@ class STLClient(object): return opts opts.prom = parsing_opts.ON_OFF_DICT.get(opts.prom) + opts.mult = parsing_opts.ON_OFF_DICT.get(opts.mult) opts.link = parsing_opts.UP_DOWN_DICT.get(opts.link) opts.led = parsing_opts.ON_OFF_DICT.get(opts.led) opts.flow_ctrl = parsing_opts.FLOW_CTRL_DICT.get(opts.flow_ctrl) @@ -3940,6 +4019,7 @@ class STLClient(object): print('') print('Supported attributes for current NICs:') print(' Promiscuous: yes') + print(' Multicast: yes') print(' Link status: %s' % info['link_change_supported']) print(' LED status: %s' % info['led_change_supported']) print(' Flow control: %s' % info['fc_supported']) @@ -3951,10 +4031,29 @@ class STLClient(object): opts.prom, opts.link, opts.led, - opts.flow_ctrl) - - - + opts.flow_ctrl, + multicast = opts.mult) + + + @__console + def set_rx_sniffer_line (self, line): + '''Sets a port sniffer on RX channel in form of a PCAP file''' + + parser = parsing_opts.gen_parser(self, + "set_rx_sniffer", + self.set_rx_sniffer_line.__doc__, + parsing_opts.PORT_LIST_WITH_ALL, + parsing_opts.OUTPUT_FILENAME, + parsing_opts.LIMIT) + + opts = parser.parse_args(line.split(), default_ports = self.get_acquired_ports(), verify_acquired = True) + if not opts: + return opts + + self.set_rx_sniffer(opts.ports, opts.output_filename, opts.limit) + + return RC_OK() + @__console def resolve_line (self, line): @@ -3977,6 +4076,27 @@ class STLClient(object): @__console + def scan6_line(self, line): + '''Search for IPv6 neighbors''' + + parser = parsing_opts.gen_parser(self, + "scan6", + self.scan6_line.__doc__, + parsing_opts.PORT_LIST_WITH_ALL, + parsing_opts.TIMEOUT) + + opts = parser.parse_args(line.split(), default_ports = self.get_acquired_ports(), verify_acquired = True) + if not opts: + return opts + + rc_per_port = self.scan6(ports = opts.ports, timeout = opts.timeout, verbose = True) + #for port, rc in rc_per_port.items(): + # if rc and len(rc.data()) == 1 and not self.ports[port].is_resolved(): + # self.ports[port].set_l2_mode(rc.data()[0][1]) + return RC_OK() + + + @__console def set_l2_mode_line (self, line): '''Configures a port in L2 mode''' diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_packet_builder_scapy.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_packet_builder_scapy.py index c5fbab90..679068ac 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_packet_builder_scapy.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_packet_builder_scapy.py @@ -57,6 +57,40 @@ def mac_str_to_num (mac_buffer): return _buffer_to_num(mac_buffer) +# RFC 3513 +def generate_ipv6(mac_str, prefix = 'fe80'): + mac_arr = mac_str.split(':') + assert len(mac_arr) == 6, 'mac should be in format of 11:22:33:44:55:66, got: %s' % mac_str + mac_arr[0] = '%x' % (int(mac_arr[0], 16) ^ 2) # invert second bit + return '%s::%s%s:%sff:fe%s:%s%s' % tuple([prefix] + mac_arr[:3] + mac_arr[3:]) + +# RFC 4291 +def generate_ipv6_solicited_node(mac_str): + mac_arr = mac_str.split(':') + assert len(mac_arr) == 6, 'mac should be in format of 11:22:33:44:55:66, got: %s' % mac_str + return 'ff02::1:ff%s:%s%s' % tuple(mac_arr[3:]) + + +# return full ipv6 ff02::1 -> ff02:0:0:0:0:0:0:1 +def expand_ipv6(addr): + addr_arr = addr.split(':') + if addr.startswith(':'): + addr_arr[0] = '0' + if addr.endswith(':'): + addr_arr[-1] = '0' + for i, e in enumerate(addr_arr): + if not e: + return ':'.join(addr_arr[:i] + ['0'] * (9 - len(addr_arr)) + addr_arr[i + 1:]) + return ':'.join(addr_arr) + + +# return multicast mac based on ipv6 ff02::1 -> 33:33:00:00:00:01 +def multicast_mac_from_ipv6(addr): + addr = expand_ipv6(addr) + addr_arr = addr.split(':') + return '33:33:%02x:%02x:%02x:%02x' % (divmod(int(addr_arr[-2], 16), 256) + divmod(int(addr_arr[-1], 16), 256)) + + def is_valid_ipv4_ret(ip_addr): """ Return buffer in network order diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py index 1ef3a8ff..b87a8a5a 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py @@ -7,6 +7,7 @@ from .trex_stl_types import * from .rx_services.trex_stl_rx_service_arp import RXServiceARP from .rx_services.trex_stl_rx_service_icmp import RXServiceICMP +from .rx_services.trex_stl_rx_service_ipv6 import * from . import trex_stl_stats from .utils.constants import FLOW_CTRL_DICT_REVERSED @@ -127,7 +128,7 @@ class Port(object): def err(self, msg): - return RC_ERR("port {0} : *** {1}".format(self.port_id, msg)) + return RC_ERR("Port {0} : *** {1}".format(self.port_id, msg)) def ok(self, data = ""): return RC_OK(data) @@ -662,7 +663,10 @@ class Port(object): if kwargs.get('promiscuous') is not None: json_attr['promiscuous'] = {'enabled': kwargs.get('promiscuous')} - + + if kwargs.get('multicast') is not None: + json_attr['multicast'] = {'enabled': kwargs.get('multicast')} + if kwargs.get('link_status') is not None: json_attr['link_status'] = {'up': kwargs.get('link_status')} @@ -812,6 +816,11 @@ class Port(object): else: info['prom'] = "N/A" + if 'multicast' in attr: + info['mult'] = "on" if attr['multicast']['enabled'] else "off" + else: + info['mult'] = "N/A" + if 'description' not in info: info['description'] = "N/A" @@ -909,10 +918,10 @@ class Port(object): @writeable - def arp_resolve (self, retries): + def arp_resolve(self, retries): # execute the ARP service - rc = RXServiceARP(self).execute(retries) + rc = RXServiceARP(self, retries = retries).execute() if not rc: return rc @@ -934,8 +943,18 @@ class Port(object): @writeable - def ping (self, ping_ipv4, pkt_size): - return RXServiceICMP(self, ping_ipv4, pkt_size).execute() + def scan6(self, timeout = None, dst_ip = 'ff02::1'): + if timeout is None: + timeout = 5 + return RXServiceIPv6Scan(self, timeout = timeout, dst_ip = dst_ip).execute() + + + @writeable + def ping(self, ping_ip, pkt_size, dst_mac = None): + if '.' in ping_ip: + return RXServiceICMP(self, ping_ip, pkt_size).execute() + else: + return RXServiceICMPv6(self, pkt_size, dst_mac, dst_ip = ping_ip).execute() ################# stats handler ###################### @@ -962,6 +981,7 @@ class Port(object): "port status": info['status'], "link status": info['link'], "promiscuous" : info['prom'], + "multicast" : info['mult'], "flow ctrl" : info['fc'], "layer mode": format_text(info['layer_mode'], 'green' if info['layer_mode'] == 'IPv4' else 'magenta'), diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_sim.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_sim.py index 540bba68..0c721cc5 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_sim.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_sim.py @@ -539,7 +539,7 @@ def test_multi_core (r, options): for core_count in range(1, 9): r.run(input_list = options.input_file, - outfile = '{0}.cap'.format(core_count), + outfile = 'generated/{0}.cap'.format(core_count), dp_core_count = core_count, is_debug = (not options.release), pkt_limit = options.limit, @@ -553,7 +553,7 @@ def test_multi_core (r, options): for core_count in range(1, 9): print(format_text("comparing {0} cores to 1 core:\n".format(core_count), 'underline')) - rc = compare_caps_strict('1.cap', '{0}.cap'.format(core_count)) + rc = compare_caps_strict('generated/1.cap', 'generated/{0}.cap'.format(core_count)) if rc: print("[Passed]\n") diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py index 21c9af87..fce37686 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py @@ -668,6 +668,7 @@ class CTRexInfoGenerator(object): ("link speed", []), ("port status", []), ("promiscuous", []), + ("multicast", []), ("flow ctrl", []), ("--", []), ("layer mode", []), diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/common.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/common.py index 7cb94b28..6b90a3b4 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/common.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/common.py @@ -99,7 +99,14 @@ def is_valid_ipv4 (addr): return True except (socket.error, TypeError): return False - + +def is_valid_ipv6(addr): + try: + socket.inet_pton(socket.AF_INET6, addr) + return True + except (socket.error, TypeError): + return False + def is_valid_mac (mac): return bool(re.match("[0-9a-f]{2}([-:])[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", mac.lower())) diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py index 53db533c..cb2c9814 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py @@ -1,6 +1,6 @@ import argparse from collections import namedtuple, OrderedDict -from .common import list_intersect, list_difference, is_valid_ipv4, is_valid_mac, list_remove_dup +from .common import list_intersect, list_difference, is_valid_ipv4, is_valid_ipv6, is_valid_mac, list_remove_dup from .text_opts import format_text from ..trex_stl_types import * from .constants import ON_OFF_DICT, UP_DOWN_DICT, FLOW_CTRL_DICT @@ -26,6 +26,7 @@ FILE_FROM_DB SERVER_IP STREAM_FROM_PATH_OR_FILE DURATION +TIMEOUT FORCE DRY_RUN XTERM @@ -36,6 +37,7 @@ MIN_IPG SPEEDUP COUNT PROMISCUOUS +MULTICAST LINK_STATUS LED_STATUS TUNABLES @@ -57,7 +59,7 @@ RETRIES SINGLE_PORT DST_MAC -PING_IPV4 +PING_IP PING_COUNT PKT_SIZE @@ -263,6 +265,12 @@ def check_ipv4_addr (ipv4_str): return ipv4_str +def check_ip_addr(addr): + if not (is_valid_ipv4(addr) or is_valid_ipv6(addr)): + raise argparse.ArgumentTypeError("invalid IPv4/6 address: '{0}'".format(addr)) + + return addr + def check_pkt_size (pkt_size): try: pkt_size = int(pkt_size) @@ -357,6 +365,10 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], {'help': "Set port promiscuous on/off", 'choices': ON_OFF_DICT}), + MULTICAST: ArgumentPack(['--mult'], + {'help': "Set port multicast on/off", + 'choices': ON_OFF_DICT}), + LINK_STATUS: ArgumentPack(['--link'], {'help': 'Set link status up/down', 'choices': UP_DOWN_DICT}), @@ -446,11 +458,11 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], 'help': 'source port for the action', 'required': True}), - PING_IPV4: ArgumentPack(['-d'], - {'help': 'which IPv4 to ping', - 'dest': 'ping_ipv4', + PING_IP: ArgumentPack(['-d'], + {'help': 'which IPv4/6 to ping', + 'dest': 'ping_ip', 'required': True, - 'type': check_ipv4_addr}), + 'type': check_ip_addr}), PING_COUNT: ArgumentPack(['-n', '--count'], {'help': 'How many times to ping [default is 5]', @@ -479,6 +491,14 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], 'default': -1.0, 'help': "Set duration time for job."}), + TIMEOUT: ArgumentPack(['-t'], + {'action': "store", + 'metavar': 'TIMEOUT', + 'dest': 'timeout', + 'type': int, + 'default': None, + 'help': "Timeout for operation in seconds."}), + FORCE: ArgumentPack(['--force'], {"action": "store_true", 'default': False, diff --git a/scripts/external_libs/scapy-2.3.1/python2/scapy/layers/inet6.py b/scripts/external_libs/scapy-2.3.1/python2/scapy/layers/inet6.py index 46cd85e5..b4202a76 100644 --- a/scripts/external_libs/scapy-2.3.1/python2/scapy/layers/inet6.py +++ b/scripts/external_libs/scapy-2.3.1/python2/scapy/layers/inet6.py @@ -1113,7 +1113,7 @@ icmp6typescls = { 1: "ICMPv6DestUnreach", 140: "ICMPv6NIReply", 141: "ICMPv6ND_INDSol", 142: "ICMPv6ND_INDAdv", - #143: Do Me - RFC 3810 + 143: "ICMPv6MLReportV2", 144: "ICMPv6HAADRequest", 145: "ICMPv6HAADReply", 146: "ICMPv6MPSol", @@ -1143,6 +1143,7 @@ icmp6typesminhdrlen = { 1: 8, #140 141: 8, 142: 8, + 143: 16, 144: 8, 145: 8, 146: 8, @@ -1186,6 +1187,14 @@ icmp6types = { 1 : "Destination unreachable", 200 : "Private Experimentation", 201 : "Private Experimentation" } +mldv2_group_types = { + 1: 'Mode is include (1)', + 2: 'Mode is exclude (2)', + 3: 'Change to include mode (3)', + 4: 'Change to exclude mode (4)', + 5: 'Alloc new sources (5)', + 6: 'Block old sources (6)', + } class _ICMPv6(Packet): name = "ICMPv6 dummy class" @@ -1345,6 +1354,35 @@ class ICMPv6MLDone(_ICMPv6ML): # RFC 2710 overload_fields = {IPv6: { "dst": "ff02::2", "hlim": 1, "nh": 58}} +class ICMPv6MLReportV2(_ICMPv6): # RFC 3810 + name = 'MLDv2 - Multicast Listener Report' + fields_desc = [ ByteEnumField('type', 143, icmp6types), + ByteField('code', 0), + XShortField('cksum', None), + ShortField('reserved', 0), + ShortField('records_count', 1) ] # for now it's fixed 1 record + overload_fields = {IPv6: { 'dst': 'ff02::16', 'hlim': 1, 'nh': 58 }} + + def default_payload_class(self, p): + return MLDv2Addr + + +# assumes empty aux +class MLDv2Addr(Packet): + name = 'MLDv2 - Address group' + fields_desc = [ + ByteEnumField('type', 3, mldv2_group_types), + ByteField('aux_len', 0), + ShortField('len', 0), + IP6Field('multicast_addr', '::'), + IP6ListField('addrlist', [], count_from = lambda pkt:pkt.len) + ] + + def default_payload_class(self, p): + return MLDv2Addr + + + ########## ICMPv6 MRD - Multicast Router Discovery (RFC 4286) ############### # TODO: diff --git a/scripts/external_libs/scapy-2.3.1/python3/scapy/layers/inet6.py b/scripts/external_libs/scapy-2.3.1/python3/scapy/layers/inet6.py index c2e4a037..ddfdc4ae 100644 --- a/scripts/external_libs/scapy-2.3.1/python3/scapy/layers/inet6.py +++ b/scripts/external_libs/scapy-2.3.1/python3/scapy/layers/inet6.py @@ -1121,7 +1121,7 @@ icmp6typescls = { 1: "ICMPv6DestUnreach", 140: "ICMPv6NIReply", 141: "ICMPv6ND_INDSol", 142: "ICMPv6ND_INDAdv", - #143: Do Me - RFC 3810 + 143: "ICMPv6MLReportV2", 144: "ICMPv6HAADRequest", 145: "ICMPv6HAADReply", 146: "ICMPv6MPSol", @@ -1151,6 +1151,7 @@ icmp6typesminhdrlen = { 1: 8, #140 141: 8, 142: 8, + 143: 16, 144: 8, 145: 8, 146: 8, @@ -1194,6 +1195,14 @@ icmp6types = { 1 : "Destination unreachable", 200 : "Private Experimentation", 201 : "Private Experimentation" } +mldv2_group_types = { + 1: 'Mode is include (1)', + 2: 'Mode is exclude (2)', + 3: 'Change to include mode (3)', + 4: 'Change to exclude mode (4)', + 5: 'Alloc new sources (5)', + 6: 'Block old sources (6)', + } class _ICMPv6(Packet): name = "ICMPv6 dummy class" @@ -1353,6 +1362,35 @@ class ICMPv6MLDone(_ICMPv6ML): # RFC 2710 overload_fields = {IPv6: { "dst": "ff02::2", "hlim": 1, "nh": 58}} +class ICMPv6MLReportV2(_ICMPv6): # RFC 3810 + name = 'MLDv2 - Multicast Listener Report' + fields_desc = [ ByteEnumField('type', 143, icmp6types), + ByteField('code', 0), + XShortField('cksum', None), + ShortField('reserved', 0), + ShortField('records_count', 1) ] # for now it's fixed 1 record + overload_fields = {IPv6: { 'dst': 'ff02::16', 'hlim': 1, 'nh': 58 }} + + def default_payload_class(self, p): + return MLDv2Addr + + +# assumes empty aux +class MLDv2Addr(Packet): + name = 'MLDv2 - Address group' + fields_desc = [ + ByteEnumField('type', 3, mldv2_group_types), + ByteField('aux_len', 0), + ShortField('len', 0), + IP6Field('multicast_addr', '::'), + IP6ListField('addrlist', [], count_from = lambda pkt:pkt.len) + ] + + def default_payload_class(self, p): + return MLDv2Addr + + + ########## ICMPv6 MRD - Multicast Router Discovery (RFC 4286) ############### # TODO: |