summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorimarom <imarom@cisco.com>2016-11-22 14:20:32 +0200
committerimarom <imarom@cisco.com>2016-11-22 14:20:32 +0200
commitc14a58893ba5d24057b72e4bc2381541f4558fcc (patch)
tree7e96d8ad64ef7aef5d835041c8904af662814707
parent11c216470c30e4c200e46e1b51d721a549f440d6 (diff)
RX features - added PING (echo ICMP) feature
Signed-off-by: imarom <imarom@cisco.com>
-rwxr-xr-xscripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py98
-rw-r--r--scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py308
-rwxr-xr-xscripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py33
3 files changed, 313 insertions, 126 deletions
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 cf328d2e..1ddf359b 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
@@ -34,9 +34,10 @@ import os.path
# logger API for the client
class LoggerApi(object):
# verbose levels
- VERBOSE_QUIET = 0
- VERBOSE_REGULAR = 1
- VERBOSE_HIGH = 2
+ VERBOSE_QUIET = 0
+ VERBOSE_REGULAR_SYNC = 1
+ VERBOSE_REGULAR = 2
+ VERBOSE_HIGH = 3
def __init__(self):
self.level = LoggerApi.VERBOSE_REGULAR
@@ -64,7 +65,7 @@ class LoggerApi(object):
# simple log message with verbose
- def log (self, msg, level = VERBOSE_REGULAR, newline = True):
+ def log (self, msg, level = VERBOSE_REGULAR_SYNC, newline = True):
if not self.check_verbose(level):
return
@@ -92,19 +93,20 @@ class LoggerApi(object):
# supress object getter
- def supress (self):
+ def supress (self, level = VERBOSE_QUIET):
class Supress(object):
- def __init__ (self, logger):
+ def __init__ (self, logger, level):
self.logger = logger
+ self.level = level
def __enter__ (self):
self.saved_level = self.logger.get_verbose()
- self.logger.set_verbose(LoggerApi.VERBOSE_QUIET)
+ self.logger.set_verbose(self.level)
def __exit__ (self, type, value, traceback):
self.logger.set_verbose(self.saved_level)
- return Supress(self)
+ return Supress(self, level)
@@ -812,7 +814,7 @@ class STLClient(object):
rc = RC()
for port_id in port_id_list:
- rc.add(self.ports[port_id].resolve(retries))
+ rc.add(self.ports[port_id].arp_resolve(retries))
return rc
@@ -1836,26 +1838,57 @@ class STLClient(object):
Pings the server
:parameters:
- None
-
+ none
:raises:
+ :exc:`STLError`
"""
- self.resolve()
- return
-
+
self.logger.pre_cmd("Pinging the server on '{0}' port '{1}': ".format(self.connection_info['server'],
self.connection_info['sync_port']))
rc = self._transmit("ping", api_class = None)
-
+
self.logger.post_cmd(rc)
if not rc:
raise STLError(rc)
+
+ @__api_check(True)
+ def ip_ping (self, src_port, dst_ipv4, pkt_size = 64, count = 5):
+ """
+ Pings an IP address
+
+ :parameters:
+ src_port - on which port_id to send the ICMP PING request
+ dst_ipv4 - which IP to ping
+ pkt_size - packet size to use
+ count - how many times to ping
+ :raises:
+ + :exc:`STLError`
+ """
+ self._validate_port_list(src_port)
+
+ self.logger.pre_cmd("Pinging {0} bytes from port {1} to IPv4 {2}:".format(pkt_size,
+ src_port,
+ dst_ipv4))
+
+ # no async messages
+ with self.logger.supress(level = LoggerApi.VERBOSE_REGULAR_SYNC):
+ self.logger.log('')
+ for i in range(count):
+ rc = self.ports[src_port].ping(ping_ipv4 = dst_ipv4, pkt_size = pkt_size, retries = 0)
+ if rc:
+ self.logger.log(rc.data())
+ else:
+ raise STLError(rc)
+ if i != (count - 1):
+ time.sleep(1)
+
+
+
@__api_check(True)
def server_shutdown (self, force = False):
"""
@@ -2128,7 +2161,7 @@ class STLClient(object):
# warn if ports are not resolved
unresolved_ports = [port_id for port_id in ports if not self.ports[port_id].is_resolved()]
if unresolved_ports and not force:
- raise STLError("Port(s) {0} are unresolved - please resolve them or specify 'force'".format(unresolved_ports))
+ raise STLError("Port(s) {0} have unresolved destination addresses - please resolve them or specify 'force'".format(unresolved_ports))
@__api_check(True)
@@ -2862,13 +2895,13 @@ class STLClient(object):
"""
# by default - resolve all the ports that are configured with IPv4 dest
if ports is None:
- ports = [port_id for port_id in self.get_acquired_ports() if self.ports[port_id].get_dest()['type'] == 'ipv4']
+ ports = [port_id for port_id in self.get_acquired_ports() if self.ports[port_id].get_dst_addr()['ipv4'] is not None]
if not ports:
- raise STLError('No ports configured with destination as IPv4')
+ raise STLError('resolve - No ports configured with destination as IPv4')
active_ports = list(set(self.get_active_ports()).intersection(ports))
if active_ports:
- raise STLError('Port(s) {0} are active'.format(active_ports))
+ raise STLError('resolve - Port(s) {0} are active, please stop them before resolving'.format(active_ports))
ports = self._validate_port_list(ports)
@@ -3062,10 +3095,29 @@ class STLClient(object):
@__console
def ping_line (self, line):
- '''pings the server'''
- self.ping()
- return RC_OK()
+ '''pings the server / specific IP'''
+
+ # no parameters - so ping server
+ if not line:
+ self.ping()
+ return True
+
+ parser = parsing_opts.gen_parser(self,
+ "ping",
+ self.ping_line.__doc__,
+ parsing_opts.SOURCE_PORT,
+ parsing_opts.PING_IPV4,
+ parsing_opts.PKT_SIZE,
+ parsing_opts.COUNT)
+
+ opts = parser.parse_args(line.split())
+ if not opts:
+ return opts
+
+ # IP ping
+ self.ip_ping(opts.source_port, opts.ping_ipv4, opts.pkt_size, opts.count)
+
@__console
def shutdown_line (self, line):
'''shutdown the server'''
@@ -3670,7 +3722,7 @@ class STLClient(object):
parsing_opts.PORT_LIST_WITH_ALL,
parsing_opts.RETRIES)
- resolvable_ports = [port_id for port_id in self.get_acquired_ports() if self.ports[port_id].get_dest()['type'] == 'ipv4']
+ resolvable_ports = [port_id for port_id in self.get_acquired_ports() if self.ports[port_id].get_dst_addr() is not None]
opts = parser.parse_args(line.split(), default_ports = resolvable_ports, verify_acquired = True)
if not opts:
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 f658b7fa..2e77b492 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
@@ -8,6 +8,7 @@ from . import trex_stl_stats
from .utils.constants import FLOW_CTRL_DICT_REVERSED
from scapy.layers.l2 import Ether, ARP
+from scapy.layers.inet import IP, ICMP
import base64
import copy
@@ -54,7 +55,7 @@ class Port(object):
def __init__ (self, port_id, user, comm_link, session_id, info):
self.port_id = port_id
-
+
self.state = self.STATE_IDLE
self.handler = None
@@ -523,7 +524,7 @@ class Port(object):
return self.ok()
-
+
@owned
def remove_rx_sniffer (self):
params = {"handler": self.handler,
@@ -737,25 +738,6 @@ class Port(object):
return self.ok()
- @writeable
- def add_arp_request (self):
- ipv4 = self.__attr['src_ipv4']
- dest = self.__attr['dest']
- mac = self.__attr['src_mac']
-
- if ipv4 == 'none':
- return self.err('port must have a configured IPv4')
-
- if dest['type'] == 'mac':
- return self.err('port must have an IPv4 destination')
-
-
- base_pkt = Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(psrc = ipv4, pdst = dest['addr'], hwsrc = mac)
- s1 = STLStream( packet = STLPktBuilder(pkt = base_pkt), mode = STLTXSingleBurst(total_pkts = 1) )
-
- return self.add_streams([s1])
-
-
def print_profile (self, mult, duration):
if not self.get_profile():
return
@@ -910,35 +892,47 @@ class Port(object):
def get_port_state_name(self):
return self.STATES_MAP.get(self.state, "Unknown")
- def get_src_ipv4 (self):
+ def get_src_addr (self):
+ src_mac = self.__attr['src_mac']
+
src_ipv4 = self.__attr['src_ipv4']
if src_ipv4 == 'none':
src_ipv4 = None
- return src_ipv4
-
- def get_dest (self):
- return self.__attr['dest']
+ return {'mac': src_mac, 'ipv4': src_ipv4}
- def get_src_mac (self):
- return self.__attr['src_mac']
-
- def is_resolved (self):
- dest = self.get_dest()
+ def get_dst_addr (self):
+ dest = self.__attr['dest']
+
+ dst_ipv4 = None
+ dst_mac = None
if dest['type'] == 'mac':
- return True
+ dst_mac = dest['addr']
elif dest['type'] == 'ipv4':
- return dest['arp'] != 'none'
+ dst_ipv4 = dest['addr']
+ dst_mac = dest['arp']
+ if dst_mac == 'none':
+ dst_mac = None
else:
- # unsupported type
assert(0)
+
+
+ return {'ipv4': dst_ipv4, 'mac' : dst_mac}
+
+
+ def is_resolved (self):
+ return (self.get_dst_addr()['mac'] != None)
- def resolve (self, retries):
+ @writeable
+ def arp_resolve (self, retries):
return ARPResolver(self).resolve(retries)
+ @writeable
+ def ping (self, ping_ipv4, pkt_size, retries):
+ return PingResolver(self, ping_ipv4, pkt_size).resolve(retries)
################# stats handler ######################
@@ -1079,83 +1073,81 @@ class Port(object):
def async_event_released (self):
self.owner = ''
-
-# a class to handle port ARP resolution
-class ARPResolver(object):
- def __init__ (self, port):
+
+# a generic abstract class for resolving using the server
+class Resolver(object):
+ def __init__ (self, port, queue_size = 100):
self.port = port
+
+ # code to execute before sending any request - return RC object
+ def pre_send (self):
+ raise NotImplementedError()
+
+ # return a list of streams for request
+ def generate_request (self):
+ raise NotImplementedError()
+
+ # return None for more packets otherwise RC object
+ def on_pkt_rx (self, pkt, dt):
+ raise NotImplementedError()
- # some sanity checks before resolving
- def sanity (self):
- if self.port.get_dest()['type'] == 'mac':
- return self.port.err('resolve - port does not have an IPv4 as destination')
-
- if self.port.get_src_ipv4() is None:
- return self.port.err('resolve - port does not have an IPv4 address configured')
-
- return self.port.ok()
-
+ # return value in case of timeout
+ def on_timeout_err (self, retries):
+ raise NotImplementedError()
- # safe call - make sure RX filter mode is restored
- def resolve (self, retries):
+ ##################### API ######################
+ def resolve (self, retries = 0):
+
+ # first cleanup
+ rc = self.port.remove_all_streams()
+ if not rc:
+ return rc
+
+ # call the specific class implementation
+ rc = self.pre_send()
+ if not rc:
+ return rc
+
+ # start the iteration
try:
+
+ # add the stream(s)
+ self.port.add_streams(self.generate_request())
+
rc = self.port.set_attr(rx_filter_mode = 'all')
if not rc:
return rc
+
rc = self.port.set_rx_queue(size = 100)
if not rc:
return rc
return self.resolve_wrapper(retries)
+
finally:
# best effort restore
self.port.set_attr(rx_filter_mode = 'hw')
self.port.remove_rx_queue()
-
-
+ self.port.remove_all_streams()
+
+
# main resolve function
def resolve_wrapper (self, retries):
- rc = self.sanity()
- if not rc:
- return rc
-
- # invalidate the current ARP resolution (if exists)
- rc = self.port.invalidate_arp()
- if not rc:
- return rc
-
-
- rc = self.port.remove_all_streams()
- if not rc:
- return rc
-
-
- rc = self.port.add_arp_request()
- if not rc:
- return rc
-
# retry for 'retries'
index = 0
while True:
- response = self.resolve_iteration()
- if response:
- break
+ rc = self.resolve_iteration()
+ if rc is not None:
+ return rc
if index >= retries:
- return self.port.err('failed to receive ARP response ({0} retries)'.format(retries))
+ return self.on_timeout_err(retries)
index += 1
time.sleep(0.1)
- # set ARP resolution result
- rc = self.port.set_arp_resolution(response['ipv4'], response['mac'])
- if not rc:
- return rc
-
- return self.port.ok()
-
def resolve_iteration (self):
@@ -1168,6 +1160,8 @@ class ARPResolver(object):
while self.port.is_active():
time.sleep(0.01)
+ self.tx_time = time.time()
+
return self.wait_for_rx_response()
@@ -1177,35 +1171,143 @@ class ARPResolver(object):
polling = 5
while polling > 0:
+ # fetch the queue
rx_pkts = self.port.get_rx_queue_pkts()
- response = self.find_arp_response(rx_pkts)
-
- if response:
- return response
-
+ dt = time.time() - self.tx_time
+ # for each packet - examine it
+ for pkt in rx_pkts:
+ rc = self.on_pkt_rx(pkt, dt)
+ if rc is not None:
+ return rc
+
if polling == 0:
return None
polling -= 1
time.sleep(0.1)
-
- # search in 'pkts' for an ARP response that matches the dest
- def find_arp_response (self, pkts):
+
+
+
+
+class ARPResolver(Resolver):
+ def __init__ (self, port_id):
+ super(ARPResolver, self).__init__(port_id)
+
+ # before resolve
+ def pre_send (self):
+ dst = self.port.get_dst_addr()
+ src = self.port.get_src_addr()
+
+ if dst['ipv4'] is None:
+ return self.port.err('ARP resolve - port does not have an IPv4 as destination')
+
+ if src['ipv4'] is None:
+ return self.port.err('ARP Resolve - port does not have an IPv4 address configured')
- for pkt in pkts:
- scapy_pkt = Ether(pkt)
- if not 'ARP' in scapy_pkt:
- continue
+ # invalidate the current ARP resolution (if exists)
+ return self.port.invalidate_arp()
+
- arp = scapy_pkt['ARP']
- dest = self.port.get_dest()
+ # return a list of streams for request
+ def generate_request (self):
+
+ dst = self.port.get_dst_addr()
+ src = self.port.get_src_addr()
+
+ base_pkt = Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(psrc = src['ipv4'], pdst = dst['ipv4'], hwsrc = src['mac'])
+ s1 = STLStream( packet = STLPktBuilder(pkt = base_pkt), mode = STLTXSingleBurst(total_pkts = 1) )
- # check this is the right ARP (ARP reply with the address)
- if (arp.op != 2) or (arp.psrc != dest['addr']):
- continue
+ return [s1]
+
+
+ # return None in case more packets are needed else the status rc
+ def on_pkt_rx (self, pkt, dt):
+ scapy_pkt = Ether(pkt)
+ if not 'ARP' in scapy_pkt:
+ return None
- return {'ipv4': arp.psrc, 'mac': arp.hwsrc}
+ arp = scapy_pkt['ARP']
+ dst = self.port.get_dst_addr()
- return None
-
+ # check this is the right ARP (ARP reply with the address)
+ if (arp.op != 2) or (arp.psrc != dst['ipv4']):
+ return None
+
+
+ rc = self.port.set_arp_resolution(arp.psrc, arp.hwsrc)
+ return rc
+
+
+ def on_timeout_err (self, retries):
+ return self.port.err('failed to receive ARP response ({0} retries)'.format(retries))
+
+
+
+
+ #################### ping resolver ####################
+
+class PingResolver(Resolver):
+ def __init__ (self, port, ping_ip, pkt_size):
+ super(PingResolver, self).__init__(port)
+ self.ping_ip = ping_ip
+ self.pkt_size = pkt_size
+
+ def pre_send (self):
+
+ src = self.port.get_src_addr()
+ dst = self.port.get_dst_addr()
+ if src['ipv4'] is None:
+ return self.port.err('Ping - port does not have an IPv4 address configured')
+
+ if dst['mac'] is None:
+ return self.port.err('Ping - port is not ARP resolved')
+
+ if self.ping_ip == src['ipv4']:
+ return self.port.err('Ping - cannot ping own IP')
+
+ return self.port.ok()
+
+
+ # return a list of streams for request
+ def generate_request (self):
+
+ src = self.port.get_src_addr()
+ dst = self.port.get_dst_addr()
+
+ base_pkt = Ether(dst = dst['mac'])/IP(src = src['ipv4'], dst = self.ping_ip)/ICMP(type = 8)
+ pad = max(0, self.pkt_size - len(base_pkt))
+
+ base_pkt = base_pkt / (pad * 'x')
+
+ #base_pkt.show2()
+ s1 = STLStream( packet = STLPktBuilder(pkt = base_pkt), mode = STLTXSingleBurst(total_pkts = 1) )
+
+ return [s1]
+
+ # return None for more packets otherwise RC object
+ def on_pkt_rx (self, pkt, dt):
+ scapy_pkt = Ether(pkt)
+ if not 'ICMP' in scapy_pkt:
+ return None
+
+ #scapy_pkt.show2()
+ ip = scapy_pkt['IP']
+
+ icmp = scapy_pkt['ICMP']
+ if icmp.type == 0:
+ # echo reply
+ return self.port.ok('Reply from {0}: bytes={1}, time={2:.2f}ms, TTL={3}'.format(ip.src, len(pkt), dt * 1000, ip.ttl))
+
+ # unreachable
+ elif icmp.type == 3:
+ return self.port.ok('destination {0} is unreachable'.format(icmp.dst))
+ else:
+ scapy_pkt.show2()
+ return self.port.err('unknown ICMP reply')
+
+
+
+ # return the str of a timeout err
+ def on_timeout_err (self, retries):
+ return self.port.ok('Request timed out.')
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 e7f04546..f9c416d8 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
@@ -55,6 +55,9 @@ DEST = 36
RETRIES = 37
RX_FILTER_MODE = 38
+SOURCE_PORT = 39
+PING_IPV4 = 40
+PKT_SIZE = 41
GLOBAL_STATS = 50
PORT_STATS = 51
@@ -235,6 +238,16 @@ def check_ipv4_addr (ipv4_str):
return ipv4_str
+def check_pkt_size (pkt_size):
+ try:
+ pkt_size = int(pkt_size)
+ except ValueError:
+ raise argparse.ArgumentTypeError("invalid packet size type: '{0}'".format(pkt_size))
+
+ if (pkt_size < 64) or (pkt_size > 9000):
+ raise argparse.ArgumentTypeError("invalid packet size: '{0}' - valid range is 64 to 9000".format(pkt_size))
+
+ return pkt_size
def check_dest_addr (addr):
if not (is_valid_ipv4(addr) or is_valid_mac(addr)):
@@ -405,6 +418,26 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'],
'help': "A list of ports on which to apply the command",
'default': []}),
+
+ SOURCE_PORT: ArgumentPack(['--port', '-p'],
+ {'dest':'source_port',
+ 'type': int,
+ 'help': 'source port for the action',
+ 'required': True}),
+
+ PING_IPV4: ArgumentPack(['-d'],
+ {'help': 'which IPv4 to ping',
+ 'dest': 'ping_ipv4',
+ 'required': True,
+ 'type': check_ipv4_addr}),
+
+ PKT_SIZE: ArgumentPack(['-s'],
+ {'dest':'pkt_size',
+ 'help': 'packet size to use',
+ 'default': 64,
+ 'type': check_pkt_size}),
+
+
ALL_PORTS: ArgumentPack(['-a'],
{"action": "store_true",
"dest": "all_ports",