RX features - added PING (echo ICMP) feature
authorimarom <[email protected]>
Tue, 22 Nov 2016 12:20:32 +0000 (14:20 +0200)
committerimarom <[email protected]>
Tue, 22 Nov 2016 12:20:32 +0000 (14:20 +0200)
Signed-off-by: imarom <[email protected]>
scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py
scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py
scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py

index cf328d2..1ddf359 100755 (executable)
@@ -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:
index f658b7f..2e77b49 100644 (file)
@@ -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.')
index e7f0454..f9c416d 100755 (executable)
@@ -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",