summaryrefslogtreecommitdiffstats
path: root/scripts
diff options
context:
space:
mode:
authorYaroslav Brustinov <ybrustin@cisco.com>2017-01-29 17:14:41 +0200
committerYaroslav Brustinov <ybrustin@cisco.com>2017-02-02 13:42:36 +0200
commit39000f461de6b85877db85488b1cc7f1fad9d359 (patch)
tree8ffa214f3876009bf8778881c63b6c245244ac41 /scripts
parent790059069915a700905f4746b22a9a4a6cadc6ad (diff)
ipv6 scan & ping
Change-Id: I4f8112b4c942d149da5ea3f0ee01ac82d7fe32cc Signed-off-by: Yaroslav Brustinov <ybrustin@cisco.com>
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/automation/trex_control_plane/stl/console/trex_console.py5
-rw-r--r--scripts/automation/trex_control_plane/stl/trex_stl_lib/rx_services/trex_stl_rx_service_api.py32
-rw-r--r--scripts/automation/trex_control_plane/stl/trex_stl_lib/rx_services/trex_stl_rx_service_arp.py8
-rw-r--r--scripts/automation/trex_control_plane/stl/trex_stl_lib/rx_services/trex_stl_rx_service_icmp.py6
-rwxr-xr-xscripts/automation/trex_control_plane/stl/trex_stl_lib/rx_services/trex_stl_rx_service_ipv6.py179
-rwxr-xr-xscripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py156
-rwxr-xr-xscripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_packet_builder_scapy.py34
-rw-r--r--scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py32
-rw-r--r--scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_sim.py4
-rw-r--r--scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py1
-rw-r--r--scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/common.py9
-rwxr-xr-xscripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py32
-rw-r--r--scripts/external_libs/scapy-2.3.1/python2/scapy/layers/inet6.py40
-rw-r--r--scripts/external_libs/scapy-2.3.1/python3/scapy/layers/inet6.py40
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: