From c14a58893ba5d24057b72e4bc2381541f4558fcc Mon Sep 17 00:00:00 2001 From: imarom Date: Tue, 22 Nov 2016 14:20:32 +0200 Subject: RX features - added PING (echo ICMP) feature Signed-off-by: imarom --- .../stl/trex_stl_lib/trex_stl_client.py | 98 +++++-- .../stl/trex_stl_lib/trex_stl_port.py | 308 ++++++++++++++------- .../stl/trex_stl_lib/utils/parsing_opts.py | 33 +++ 3 files changed, 313 insertions(+), 126 deletions(-) (limited to 'scripts/automation/trex_control_plane/stl') 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", -- cgit 1.2.3-korg