scapy launcher 18/5318/1
authorYaroslav Brustinov <[email protected]>
Tue, 13 Dec 2016 23:19:31 +0000 (01:19 +0200)
committerYaroslav Brustinov <[email protected]>
Tue, 20 Dec 2016 10:08:30 +0000 (12:08 +0200)
Change-Id: Iafe0365e82c5386e87dbf7abd0f0982c3ff95d80
Signed-off-by: Yaroslav Brustinov <[email protected]>
scripts/dpdk_setup_ports.py
scripts/external_libs/netstat.py [new file with mode: 0644]
scripts/scapy_daemon_server [new file with mode: 0755]

index 59b113b..fce099b 100755 (executable)
@@ -242,9 +242,7 @@ class ConfigCreator(object):
 class map_driver(object):
     args=None;
     cfg_file='/etc/trex_cfg.yaml'
-    parent_cfg = None
-    dump_interfaces = None
-    no_ofed_check = None
+    parent_args = None
 
 class DpdkSetup(Exception):
     pass
@@ -409,7 +407,7 @@ Other network devices
             self.raise_error ("Configuration file %s is old, should include version field\n" % fcfg )
 
         if int(cfg_dict['version'])<2 :
-            self.raise_error ("Configuration file %s is old, should include version field with value greater than 2\n" % fcfg)
+            self.raise_error ("Configuration file %s is old, expected version 2, got: %s\n" % (fcfg, cfg_dict['version']))
 
         if 'interfaces' not in self.m_cfg_dict[0]:
             self.raise_error ("Configuration file %s is old, should include interfaces field even number of elemets" % fcfg)
@@ -461,11 +459,13 @@ Other network devices
 
     def do_run (self,only_check_all_mlx=False):
         self.run_dpdk_lspci ()
-        if map_driver.dump_interfaces is None or (map_driver.dump_interfaces == [] and map_driver.parent_cfg):
+        if (map_driver.parent_args.dump_interfaces is None or
+                    (map_driver.parent_args.dump_interfaces == [] and
+                            map_driver.parent_args.cfg)):
             self.load_config_file()
             if_list=self.m_cfg_dict[0]['interfaces']
         else:
-            if_list = map_driver.dump_interfaces
+            if_list = map_driver.parent_args.dump_interfaces
             if not if_list:
                 for dev in self.m_devices.values():
                     if dev.get('Driver_str') in dpdk_nic_bind.dpdk_drivers:
@@ -489,18 +489,18 @@ Other network devices
                 Mellanox_cnt=Mellanox_cnt+1
 
 
-        if not map_driver.dump_interfaces :
+        if not map_driver.parent_args.dump_interfaces:
             if  ((Mellanox_cnt>0) and (Mellanox_cnt!= len(if_list))):
                err=" All driver should be from one vendor. you have at least one driver from Mellanox but not all "; 
                raise DpdkSetup(err)
 
 
-        if not map_driver.dump_interfaces :
+        if not map_driver.parent_args.dump_interfaces:
             if  Mellanox_cnt>0 :
                 self.set_only_mellanox_nics()
 
         if self.get_only_mellanox_nics():
-            if not map_driver.no_ofed_check:
+            if not map_driver.parent_args.no_ofed_check:
                 self.verify_ofed_os()
                 self.check_ofed_version()
 
@@ -543,6 +543,11 @@ Other network devices
                         sys.exit(1)
                 else:
                        print('WARNING: Some other program is using DPDK driver.\nIf it is TRex and you did not configure it for dual run, current command will fail.')
+        if map_driver.parent_args.stl:
+            ret = os.system('%s scapy_daemon_server restart' % sys.executable)
+            if ret:
+                sys.exit(1)
+
 
     def do_return_to_linux(self):
         if not self.m_devices:
@@ -850,10 +855,11 @@ def parse_parent_cfg (parent_cfg):
     parent_parser.add_argument('--dump-interfaces', nargs='*', default=None)
     parent_parser.add_argument('--no-ofed-check', action = 'store_true')
     parent_parser.add_argument('--no-watchdog', action = 'store_true')
-    args, _ = parent_parser.parse_known_args(shlex.split(parent_cfg))
-    if args.help:
+    parent_parser.add_argument('-i', action = 'store_true', dest = 'stl', default = False)
+    map_driver.parent_args, _ = parent_parser.parse_known_args(shlex.split(parent_cfg))
+    if map_driver.parent_args.help:
         sys.exit(0)
-    return (args.cfg, args.dump_interfaces, args.no_ofed_check)
+
 
 def process_options ():
     parser = argparse.ArgumentParser(usage=""" 
@@ -970,9 +976,9 @@ To see more detailed info on interfaces (table):
 
     map_driver.args = parser.parse_args();
     if map_driver.args.parent :
-        map_driver.parent_cfg, map_driver.dump_interfaces, map_driver.no_ofed_check = parse_parent_cfg (map_driver.args.parent)
-        if map_driver.parent_cfg != '':
-            map_driver.cfg_file = map_driver.parent_cfg;
+        parse_parent_cfg (map_driver.args.parent)
+        if map_driver.parent_args.cfg:
+            map_driver.cfg_file = map_driver.parent_args.cfg;
     if  map_driver.args.cfg :
         map_driver.cfg_file = map_driver.args.cfg;
 
diff --git a/scripts/external_libs/netstat.py b/scripts/external_libs/netstat.py
new file mode 100644 (file)
index 0000000..0359857
--- /dev/null
@@ -0,0 +1,97 @@
+#!/usr/bin/python
+
+# based on http://voorloopnul.com/blog/a-python-netstat-in-less-than-100-lines-of-code/
+# added caching of glob for x10 speedup (additional x2 by omitting pid)
+
+import pwd
+import os
+import glob
+
+PROC_TCP = "/proc/net/tcp"
+STATE = {
+        '01':'ESTABLISHED',
+        '02':'SYN_SENT',
+        '03':'SYN_RECV',
+        '04':'FIN_WAIT1',
+        '05':'FIN_WAIT2',
+        '06':'TIME_WAIT',
+        '07':'CLOSE',
+        '08':'CLOSE_WAIT',
+        '09':'LAST_ACK',
+        '0A':'LISTEN',
+        '0B':'CLOSING'
+        }
+
+def _load():
+    ''' Read the table of tcp connections & remove header  '''
+    with open(PROC_TCP,'r') as f:
+        content = f.readlines()
+        content.pop(0)
+    return content
+
+def _hex2dec(s):
+    return str(int(s,16))
+
+def _ip(s):
+    ip = [(_hex2dec(s[6:8])),(_hex2dec(s[4:6])),(_hex2dec(s[2:4])),(_hex2dec(s[0:2]))]
+    return '.'.join(ip)
+
+def _remove_empty(array):
+    return [x for x in array if x]
+
+def _convert_ip_port(array):
+    host,port = array.split(':')
+    return _ip(host),_hex2dec(port)
+
+def netstat(with_pid = True):
+    '''
+    Function to return a list with status of tcp connections at linux systems
+    To get pid of all network process running on system, you must run this script
+    as superuser
+    '''
+
+    pid_cache.clear()
+    content=_load()
+    result = []
+    for line in content:
+        line_array = _remove_empty(line.split(' '))     # Split lines and remove empty spaces.
+        l_host,l_port = _convert_ip_port(line_array[1]) # Convert ipaddress and port from hex to decimal.
+        r_host,r_port = _convert_ip_port(line_array[2]) 
+        tcp_id = line_array[0]
+        state = STATE.get(line_array[3])
+        uid = pwd.getpwuid(int(line_array[7]))[0]       # Get user from UID.
+        inode = line_array[9]                           # Need the inode to get process pid.
+        if with_pid:
+            pid = _get_pid_of_inode(inode)                  # Get pid prom inode.
+            try:                                            # try read the process name.
+                exe = os.readlink('/proc/' + pid + '/exe')
+            except:
+                exe = None
+        else:
+            pid = exe = None
+        result.append([tcp_id, uid, l_host, l_port, r_host, r_port, state, pid, exe])
+    pid_cache.clear()
+    return result
+
+pid_cache = {}
+def _get_pid_of_inode(inode):
+    '''
+    To retrieve the process pid, check every running process and look for one using
+    the given inode.
+    '''
+    if not pid_cache:
+        for fd in glob.glob('/proc/[0-9]*/fd/[0-9]*'):
+            try:
+                pid_cache[fd] = os.readlink(fd)
+            except:
+                pass
+    inode = '[' + inode + ']'
+    for key, val in pid_cache.items():
+        if inode in val:
+            return key.split('/')[2]
+    return None
+
+if __name__ == '__main__':
+    for conn in netstat():
+        print(conn)
+
diff --git a/scripts/scapy_daemon_server b/scripts/scapy_daemon_server
new file mode 100755 (executable)
index 0000000..490e803
--- /dev/null
@@ -0,0 +1,163 @@
+#!/usr/bin/python
+
+import os, sys, getpass
+import tempfile
+import time
+import subprocess, shlex
+from argparse import ArgumentParser, RawTextHelpFormatter
+import errno
+
+def fail(msg):
+    print(msg)
+    sys.exit(-1)
+
+if getpass.getuser() != 'root':
+    fail('Please run this program as root/with sudo')
+
+pwd = os.path.abspath(os.path.dirname(__file__))
+ext_libs_path = os.path.join(pwd, 'external_libs')
+if ext_libs_path not in sys.path:
+    sys.path.append(ext_libs_path)
+
+import netstat
+try:
+    from termstyle import termstyle
+except ImportError:
+    import termstyle
+
+
+def inv(f):
+    return lambda *a, **k: not f(*a, **k)
+
+
+def progress(success_check, start_msg, success_msg, fail_msg, timeout = 10, poll_rate = 0.5):
+    sys.stdout.write('%s...' % start_msg)
+    sys.stdout.flush()
+    for i in range(int(timeout/poll_rate)):
+        if success_check():
+            print(termstyle.green(' ' + success_msg))
+            return 0
+        time.sleep(poll_rate)
+        sys.stdout.write('.')
+        sys.stdout.flush()
+    if success_check():
+        print(termstyle.green(' ' + success_msg))
+        return 0
+    print(termstyle.red(' ' + fail_msg))
+    return 1
+
+
+def run_command(command, timeout = 15, poll_rate = 0.1, cwd = None):
+    assert timeout > 0, 'Timeout should be positive'
+    assert poll_rate > 0, 'Poll rate should be positive'
+    try:
+        tempfile.TemporaryFile(bufsize=0)
+        temp_params = {'bufsize': 0}
+    except:
+        tempfile.TemporaryFile(buffering=0)
+        temp_params = {'buffering': 0}
+    with tempfile.TemporaryFile(**temp_params) as stdout_file:
+        proc = subprocess.Popen(shlex.split(command), stdout = stdout_file, stderr = subprocess.STDOUT, cwd = cwd, close_fds = True, universal_newlines = True)
+        for i in range(int(timeout/poll_rate)):
+            time.sleep(poll_rate)
+            if proc.poll() is not None: # process stopped
+                break
+        if proc.poll() is None:
+            proc.kill() # timeout
+            stdout_file.seek(0)
+            return (errno.ETIMEDOUT, '%s\n\n...Timeout of %s second(s) is reached!' % (stdout_file.read(), timeout))
+        stdout_file.seek(0)
+        return (proc.returncode, stdout_file.read())
+
+
+def get_daemon_pid():
+    pid = None
+    for conn in netstat.netstat():
+        if conn[2] == '0.0.0.0' and int(conn[3]) == args.daemon_port and conn[6] == 'LISTEN':
+            pid = conn[7]
+            if pid is None:
+                raise Exception('Found the connection, but could not determine pid: %s' % conn)
+            break
+    return pid
+
+
+# faster variant of get_daemon_pid
+def is_running():
+    for conn in netstat.netstat(with_pid = False):
+        if conn[2] == '0.0.0.0' and int(conn[3]) == args.daemon_port and conn[6] == 'LISTEN':
+            return True
+    return False
+
+
+def show_daemon_status():
+    if is_running():
+        print(termstyle.green('Scapy server is running'))
+    else:
+        print(termstyle.red('Scapy server is NOT running'))
+
+
+def start_daemon():
+    if is_running():
+        print(termstyle.red('Scapy server is already running'))
+        return
+    server_path = os.path.join(pwd, 'automation', 'trex_control_plane', 'stl', 'services', 'scapy_server')
+    with tempfile.TemporaryFile() as stdout_file:
+        subprocess.Popen(shlex.split('sudo -u nobody %s scapy_zmq_server.py -s %s' % (sys.executable, args.daemon_port)), stdout = stdout_file,
+                    stderr = subprocess.STDOUT, cwd = server_path, close_fds = True, universal_newlines = True)
+        ret = progress(is_running, 'Starting Scapy server', 'Scapy server is started', 'Scapy server failed to run')
+        if ret:
+            stdout_file.seek(0)
+            print('Output: %s' % stdout_file.read())
+            sys.exit(1)
+
+
+def restart_daemon():
+    if is_running():
+        kill_daemon()
+        time.sleep(0.5)
+    start_daemon()
+
+
+def kill_daemon():
+    pid = get_daemon_pid()
+    if not pid:
+        print(termstyle.red('Scapy server is NOT running'))
+        return True
+    run_command('kill %s' % pid) # usual kill
+    ret = progress(inv(is_running), 'Killing Scapy server', 'Scapy server is killed', 'failed')
+    if not ret:
+        return
+    _, out = run_command('kill -9 %s' % pid) # unconditional kill
+    ret = progress(inv(is_running), 'Killing Scapy server with -9', 'Scapy server is killed', 'failed')
+    if ret:
+        fail('Failed to kill Scapy server, even with -9. Please review manually.\nOutput: %s' % out)
+
+### Main ###
+
+if __name__ == '__main__':
+    actions_help = '''Specify action command to be applied on server.
+        (*) start      : start the application in as a daemon process.
+        (*) show       : prompt an updated status of daemon process (running/ not running).
+        (*) stop       : exit Scapy server daemon process.
+        (*) restart    : stop, then start again the application as daemon process
+        '''
+    action_funcs = {'start': start_daemon,
+                    'show': show_daemon_status,
+                    'stop': kill_daemon,
+                    'restart': restart_daemon,
+                    }
+    
+    parser = ArgumentParser(description = 'Runs Scapy server application.',
+        formatter_class = RawTextHelpFormatter,
+    )
+    
+    parser.add_argument('-p', '--port', type = int, default = 4507, metavar="PORT", dest="daemon_port", 
+            help="Select tcp port on which Scapy server listens.\nDefault port is 4507.", action="store")
+    parser.add_argument('action', choices=action_funcs.keys(),
+                            action='store', help=actions_help)
+    parser.usage = None
+    args = parser.parse_args()
+    
+    action_funcs[args.action]()
+
+