18384612bSimarom#!/usr/bin/env python
25abe21ffSDan Klein# -*- coding: utf-8 -*-
35abe21ffSDan Klein
45abe21ffSDan Klein"""
55abe21ffSDan KleinDan Klein, Itay Marom
65abe21ffSDan KleinCisco Systems, Inc.
75abe21ffSDan Klein
85abe21ffSDan KleinCopyright (c) 2015-2015 Cisco Systems, Inc.
95abe21ffSDan KleinLicensed under the Apache License, Version 2.0 (the "License");
105abe21ffSDan Kleinyou may not use this file except in compliance with the License.
115abe21ffSDan KleinYou may obtain a copy of the License at
125abe21ffSDan Klein    http://www.apache.org/licenses/LICENSE-2.0
135abe21ffSDan KleinUnless required by applicable law or agreed to in writing, software
145abe21ffSDan Kleindistributed under the License is distributed on an "AS IS" BASIS,
155abe21ffSDan KleinWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
165abe21ffSDan KleinSee the License for the specific language governing permissions and
175abe21ffSDan Kleinlimitations under the License.
185abe21ffSDan Klein"""
19552aaf48Simaromfrom __future__ import print_function
20709eda3bSimarom
21bae48d6cSimaromimport subprocess
22cbc645cbSimaromimport cmd
2365b3e045Simaromimport json
24a6be0ea7Simaromimport ast
258384612bSimaromimport argparse
2630c686d5Simaromimport random
2784e9d7a4SDan Kleinimport readline
2830c686d5Simaromimport string
29876c7657SDan Kleinimport os
308384612bSimaromimport sys
311f90e4edSimaromimport tty, termios
32951b09efSimaromfrom threading import Lock
33951b09efSimaromimport threading
34995267dbSimarom
358be43de7SYaroslav Brustinovtry:
368be43de7SYaroslav Brustinov    import stl_path
378be43de7SYaroslav Brustinovexcept:
388be43de7SYaroslav Brustinov    from . import stl_path
396107c1caSimaromfrom trex_stl_lib.api import *
40995267dbSimarom
416107c1caSimaromfrom trex_stl_lib.utils.text_opts import *
42693e822bSimaromfrom trex_stl_lib.utils.common import user_input, get_current_user, set_window_always_on_top
436107c1caSimaromfrom trex_stl_lib.utils import parsing_opts
44951b09efSimaromfrom .trex_capture import CaptureManager
45995267dbSimarom
468be43de7SYaroslav Brustinovtry:
478be43de7SYaroslav Brustinov    import trex_tui
488be43de7SYaroslav Brustinovexcept:
498be43de7SYaroslav Brustinov    from . import trex_tui
50995267dbSimarom
51a6af2a8eSimaromfrom functools import wraps
52aa37a0abSDan Klein
530b39ec30Simarom__version__ = "2.0"
541f90e4edSimarom
552d9d5e14Simarom# console custom logger
562d9d5e14Simaromclass ConsoleLogger(LoggerApi):
572d9d5e14Simarom    def __init__ (self):
582d9d5e14Simarom        self.prompt_redraw = None
592d9d5e14Simarom
602d9d5e14Simarom    def write (self, msg, newline = True):
612d9d5e14Simarom        if newline:
62552aaf48Simarom            print(msg)
632d9d5e14Simarom        else:
64552aaf48Simarom            print(msg, end=' ')
652d9d5e14Simarom
662d9d5e14Simarom    def flush (self):
672d9d5e14Simarom        sys.stdout.flush()
682d9d5e14Simarom
692d9d5e14Simarom    # override this for the prompt fix
702d9d5e14Simarom    def async_log (self, msg, level = LoggerApi.VERBOSE_REGULAR, newline = True):
712d9d5e14Simarom        self.log(msg, level, newline)
724c645004Simarom        if ( (self.level >= LoggerApi.VERBOSE_REGULAR) and self.prompt_redraw ):
732d9d5e14Simarom            self.prompt_redraw()
742d9d5e14Simarom            self.flush()
752d9d5e14Simarom
762d9d5e14Simarom
77bb6dec2eSDan Kleinclass TRexGeneralCmd(cmd.Cmd):
78bb6dec2eSDan Klein    def __init__(self):
79bb6dec2eSDan Klein        cmd.Cmd.__init__(self)
8084e9d7a4SDan Klein        # configure history behaviour
8184e9d7a4SDan Klein        self._history_file_dir = "/tmp/trex/console/"
8284e9d7a4SDan Klein        self._history_file = self.get_history_file_full_path()
8384e9d7a4SDan Klein        readline.set_history_length(100)
8484e9d7a4SDan Klein        # load history, if any
8584e9d7a4SDan Klein        self.load_console_history()
8684e9d7a4SDan Klein
8784e9d7a4SDan Klein
8884e9d7a4SDan Klein    def get_console_identifier(self):
8984e9d7a4SDan Klein        return self.__class__.__name__
9084e9d7a4SDan Klein
9184e9d7a4SDan Klein    def get_history_file_full_path(self):
9284e9d7a4SDan Klein        return "{dir}{filename}.hist".format(dir=self._history_file_dir,
9384e9d7a4SDan Klein                                             filename=self.get_console_identifier())
9484e9d7a4SDan Klein
9584e9d7a4SDan Klein    def load_console_history(self):
9684e9d7a4SDan Klein        if os.path.exists(self._history_file):
9784e9d7a4SDan Klein            readline.read_history_file(self._history_file)
9884e9d7a4SDan Klein        return
9984e9d7a4SDan Klein
10084e9d7a4SDan Klein    def save_console_history(self):
10184e9d7a4SDan Klein        if not os.path.exists(self._history_file_dir):
10261dd670bSimarom            # make the directory available for every user
10361dd670bSimarom            try:
10461dd670bSimarom                original_umask = os.umask(0)
105552aaf48Simarom                os.makedirs(self._history_file_dir, mode = 0o777)
10661dd670bSimarom            finally:
10761dd670bSimarom                os.umask(original_umask)
10861dd670bSimarom
10961dd670bSimarom
11084e9d7a4SDan Klein        # os.mknod(self._history_file)
11184e9d7a4SDan Klein        readline.write_history_file(self._history_file)
11284e9d7a4SDan Klein        return
113bb6dec2eSDan Klein
114963da095Simarom    def print_history (self):
115963da095Simarom
116963da095Simarom        length = readline.get_current_history_length()
117963da095Simarom
118552aaf48Simarom        for i in range(1, length + 1):
119963da095Simarom            cmd = readline.get_history_item(i)
120552aaf48Simarom            print("{:<5}   {:}".format(i, cmd))
121963da095Simarom
122963da095Simarom    def get_history_item (self, index):
123963da095Simarom        length = readline.get_current_history_length()
124963da095Simarom        if index > length:
125552aaf48Simarom            print(format_text("please select an index between {0} and {1}".format(0, length)))
126963da095Simarom            return None
127963da095Simarom
128963da095Simarom        return readline.get_history_item(index)
129963da095Simarom
130963da095Simarom
131bb6dec2eSDan Klein    def emptyline(self):
132bb6dec2eSDan Klein        """Called when an empty line is entered in response to the prompt.
133bb6dec2eSDan Klein
134bb6dec2eSDan Klein        This overriding is such that when empty line is passed, **nothing happens**.
135bb6dec2eSDan Klein        """
136bb6dec2eSDan Klein        return
137bb6dec2eSDan Klein
138bb6dec2eSDan Klein    def completenames(self, text, *ignored):
139bb6dec2eSDan Klein        """
140bb6dec2eSDan Klein        This overriding is such that a space is added to name completion.
141bb6dec2eSDan Klein        """
142bb6dec2eSDan Klein        dotext = 'do_'+text
143bb6dec2eSDan Klein        return [a[3:]+' ' for a in self.get_names() if a.startswith(dotext)]
144bb6dec2eSDan Klein
14584e9d7a4SDan Klein
146467382a7Simarom#
1471f90e4edSimarom# main console object
14884e9d7a4SDan Kleinclass TRexConsole(TRexGeneralCmd):
149cbc645cbSimarom    """Trex Console"""
150aa37a0abSDan Klein
1512d9d5e14Simarom    def __init__(self, stateless_client, verbose = False):
1522d9d5e14Simarom
153acf815dbSimarom        # cmd lock is used to make sure background job
154acf815dbSimarom        # of the console is not done while the user excutes commands
155951b09efSimarom        self.cmd_lock = Lock()
156951b09efSimarom
157f963facdSDan Klein        self.stateless_client = stateless_client
158898fadaeSimarom
15984e9d7a4SDan Klein        TRexGeneralCmd.__init__(self)
16084e9d7a4SDan Klein
161feb152b7Simarom        self.tui = trex_tui.TrexTUI(stateless_client)
162d3c89bc5Simarom        self.terminal = None
163479c4358Simarom
164467382a7Simarom        self.verbose = verbose
165467382a7Simarom
166a2a634fcSDan Klein        self.intro  = "\n-=TRex Console v{ver}=-\n".format(ver=__version__)
167aa37a0abSDan Klein        self.intro += "\nType 'help' or '?' for supported actions\n"
1682acb002dSimarom
169951b09efSimarom        self.cap_mngr = CaptureManager(stateless_client, self.cmd_lock)
170951b09efSimarom
171479c4358Simarom        self.postcmd(False, "")
172d09df997SDan Klein
173951b09efSimarom
174cbc645cbSimarom
175467382a7Simarom    ################### internal section ########################
17695c2405dSimarom
177898fadaeSimarom    def prompt_redraw (self):
1786f4a51c1Simarom        self.postcmd(False, "")
17911d328d3Simarom        sys.stdout.write("\n" + self.prompt + readline.get_line_buffer())
18011d328d3Simarom        sys.stdout.flush()
18111d328d3Simarom
182898fadaeSimarom
183a6af2a8eSimarom    def verify_connected(f):
184a6af2a8eSimarom        @wraps(f)
185a6af2a8eSimarom        def wrap(*args):
186a6af2a8eSimarom            inst = args[0]
18795c2405dSimarom            func_name = f.__name__
18895c2405dSimarom            if func_name.startswith("do_"):
18995c2405dSimarom                func_name = func_name[3:]
190c420d1fdSimarom
191a6af2a8eSimarom            if not inst.stateless_client.is_connected():
192552aaf48Simarom                print(format_text("\n'{0}' cannot be executed on offline mode\n".format(func_name), 'bold'))
193a6af2a8eSimarom                return
194a6af2a8eSimarom
195a6af2a8eSimarom            ret = f(*args)
196a6af2a8eSimarom            return ret
197a6af2a8eSimarom
198a6af2a8eSimarom        return wrap
199467382a7Simarom
20094ce0dcdSimarom
20184e9d7a4SDan Klein    def get_console_identifier(self):
2027351ddb5Simarom        return "{context}_{server}".format(context=get_current_user(),
2032d9d5e14Simarom                                           server=self.stateless_client.get_connection_info()['server'])
204467382a7Simarom
205467382a7Simarom    def register_main_console_methods(self):
206467382a7Simarom        main_names = set(self.trex_console.get_names()).difference(set(dir(self.__class__)))
207467382a7Simarom        for name in main_names:
208467382a7Simarom            for prefix in 'do_', 'help_', 'complete_':
209467382a7Simarom                if name.startswith(prefix):
210467382a7Simarom                    self.__dict__[name] = getattr(self.trex_console, name)
211467382a7Simarom
212fdc01234Simarom    def precmd(self, line):
213fdc01234Simarom        # before doing anything, save history snapshot of the console
214fdc01234Simarom        # this is done before executing the command in case of ungraceful application exit
215fdc01234Simarom        self.save_console_history()
216fdc01234Simarom
217fdc01234Simarom        lines = line.split(';')
2182dab6b6dSYaroslav Brustinov        try:
219951b09efSimarom            self.cmd_lock.acquire()
2202dab6b6dSYaroslav Brustinov            for line in lines:
2212dab6b6dSYaroslav Brustinov                stop = self.onecmd(line)
2222dab6b6dSYaroslav Brustinov                stop = self.postcmd(stop, line)
2232dab6b6dSYaroslav Brustinov                if stop:
2242dab6b6dSYaroslav Brustinov                    return "quit"
2252dab6b6dSYaroslav Brustinov
2262dab6b6dSYaroslav Brustinov            return ""
227951b09efSimarom
2282dab6b6dSYaroslav Brustinov        except STLError as e:
2292dab6b6dSYaroslav Brustinov            print(e)
2302dab6b6dSYaroslav Brustinov            return ''
231fdc01234Simarom
232951b09efSimarom        finally:
233951b09efSimarom            self.cmd_lock.release()
234951b09efSimarom
235951b09efSimarom
236fdc01234Simarom
237467382a7Simarom    def postcmd(self, stop, line):
238a913ed85Simarom        self.prompt = self.stateless_client.generate_prompt(prefix = 'trex')
239467382a7Simarom        return stop
240467382a7Simarom
2410b39ec30Simarom
242467382a7Simarom    def default(self, line):
243552aaf48Simarom        print("'{0}' is an unrecognized command. type 'help' or '?' for a list\n".format(line))
244467382a7Simarom
245467382a7Simarom    @staticmethod
246467382a7Simarom    def tree_autocomplete(text):
247467382a7Simarom        dir = os.path.dirname(text)
248467382a7Simarom        if dir:
249467382a7Simarom            path = dir
250467382a7Simarom        else:
251467382a7Simarom            path = "."
2522dee3346Simarom
2532dee3346Simarom
254467382a7Simarom        start_string = os.path.basename(text)
2552dee3346Simarom
2562dee3346Simarom        targets = []
2572dee3346Simarom
2582dee3346Simarom        for x in os.listdir(path):
2592dee3346Simarom            if x.startswith(start_string):
2602dee3346Simarom                y = os.path.join(path, x)
2612dee3346Simarom                if os.path.isfile(y):
2622dee3346Simarom                    targets.append(x + ' ')
2632dee3346Simarom                elif os.path.isdir(y):
2642dee3346Simarom                    targets.append(x + '/')
2652dee3346Simarom
2662dee3346Simarom        return targets
267467382a7Simarom
26878c6593cSimarom
269467382a7Simarom    ####################### shell commands #######################
270a6af2a8eSimarom    @verify_connected
27178c6593cSimarom    def do_ping (self, line):
27278c6593cSimarom        '''Ping the server\n'''
2731fa7b64cSimarom        self.stateless_client.ping_line(line)
27478c6593cSimarom
2751586ab13Simarom
276344e3045Simarom    @verify_connected
277344e3045Simarom    def do_shutdown (self, line):
278344e3045Simarom        '''Sends the server a shutdown request\n'''
279344e3045Simarom        self.stateless_client.shutdown_line(line)
280344e3045Simarom
28165b3e045Simarom    # set verbose on / off
282d04fb533SDan Klein    def do_verbose(self, line):
28330c686d5Simarom        '''Shows or set verbose mode\n'''
284cbc645cbSimarom        if line == "":
285552aaf48Simarom            print("\nverbose is " + ("on\n" if self.verbose else "off\n"))
2862acb002dSimarom
287cbc645cbSimarom        elif line == "on":
288cbc645cbSimarom            self.verbose = True
2897351ddb5Simarom            self.stateless_client.set_verbose("high")
290552aaf48Simarom            print(format_text("\nverbose set to on\n", 'green', 'bold'))
291cbc645cbSimarom
292cbc645cbSimarom        elif line == "off":
293cbc645cbSimarom            self.verbose = False
2947351ddb5Simarom            self.stateless_client.set_verbose("normal")
295552aaf48Simarom            print(format_text("\nverbose set to off\n", 'green', 'bold'))
2962acb002dSimarom
297cbc645cbSimarom        else:
298552aaf48Simarom            print(format_text("\nplease specify 'on' or 'off'\n", 'bold'))
29978c6593cSimarom
300963da095Simarom    # show history
301963da095Simarom    def help_history (self):
302963da095Simarom        self.do_history("-h")
303963da095Simarom
3044715b86aSimarom    def do_shell (self, line):
305a913ed85Simarom        self.do_history(line)
3064715b86aSimarom
307c420d1fdSimarom    @verify_connected
308268c7f12Simarom    def do_push (self, line):
3098691f401Simarom        '''Push a local PCAP file\n'''
310a913ed85Simarom        self.stateless_client.push_line(line)
3118691f401Simarom
312268c7f12Simarom    def help_push (self):
313a913ed85Simarom        self.do_push("-h")
314268c7f12Simarom
315d8a541a2Simarom    def do_debug (self, line):
316d8a541a2Simarom        '''Launches IPython for interactively debugging'''
317d8a541a2Simarom        self.stateless_client.debug_line(line)
318d8a541a2Simarom
319d8a541a2Simarom    def help_debug (self):
320d8a541a2Simarom        self.do_debug('-h')
321d8a541a2Simarom
32217833369Simarom    @verify_connected
323aaef3f95Simarom    def do_portattr (self, line):
324aaef3f95Simarom        '''Change/show port(s) attributes\n'''
325a913ed85Simarom        self.stateless_client.set_port_attr_line(line)
326aaef3f95Simarom
327aaef3f95Simarom    def help_portattr (self):
328a913ed85Simarom        self.do_portattr("-h")
3294715b86aSimarom
3300c458152Simarom    @verify_connected
3310fdd81a9Simarom    def do_l2 (self, line):
3320fdd81a9Simarom        '''Configures a port in L2 mode'''
3330fdd81a9Simarom        self.stateless_client.set_l2_mode_line(line)
3340c458152Simarom
3350fdd81a9Simarom    def help_l2 (self):
3360fdd81a9Simarom        self.do_l2("-h")
3370fdd81a9Simarom
3380c458152Simarom    @verify_connected
3390fdd81a9Simarom    def do_l3 (self, line):
3400fdd81a9Simarom        '''Configures a port in L3 mode'''
3410fdd81a9Simarom        self.stateless_client.set_l3_mode_line(line)
3420fdd81a9Simarom
3430fdd81a9Simarom    def help_l3 (self):
3440fdd81a9Simarom        self.do_l3("-h")
3450fdd81a9Simarom
3460c458152Simarom
347c420d1fdSimarom    @verify_connected
3485257dbb8Simarom    def do_capture (self, line):
349ac2e93d4Simarom        '''Manage PCAP captures'''
350951b09efSimarom        self.cap_mngr.parse_line(line)
351a1ade6fdSimarom
3525257dbb8Simarom    def help_capture (self):
3535257dbb8Simarom        self.do_capture("-h")
354a1ade6fdSimarom
355c420d1fdSimarom    @verify_connected
356e46e3f59Simarom    def do_resolve (self, line):
357e46e3f59Simarom        '''Resolve ARP for ports'''
358e46e3f59Simarom        self.stateless_client.resolve_line(line)
359e46e3f59Simarom
36039000f46SYaroslav Brustinov    @verify_connected
36139000f46SYaroslav Brustinov    def do_scan6(self, line):
36239000f46SYaroslav Brustinov        '''Search for IPv6 neighbors'''
36339000f46SYaroslav Brustinov        self.stateless_client.scan6_line(line)
36439000f46SYaroslav Brustinov
36520bb3657Simarom    def help_resolve (self):
366e46e3f59Simarom        self.do_resolve("-h")
367e46e3f59Simarom
36820bb3657Simarom    do_arp = do_resolve
36920bb3657Simarom    help_arp = help_resolve
37020bb3657Simarom
371e7118220Simarom    @verify_connected
372e7118220Simarom    def do_map (self, line):
373e7118220Simarom        '''Maps ports topology\n'''
374e7118220Simarom        ports = self.stateless_client.get_acquired_ports()
375e7118220Simarom        if not ports:
376552aaf48Simarom            print("No ports acquired\n")
377af49c70aSYaroslav Brustinov            return
378e7118220Simarom
37934cb66c9Simarom
38034cb66c9Simarom        try:
38134cb66c9Simarom            with self.stateless_client.logger.supress():
38234cb66c9Simarom                table = stl_map_ports(self.stateless_client, ports = ports)
38334cb66c9Simarom        except STLError as e:
38434cb66c9Simarom            print(format_text(e.brief() + "\n", 'bold'))
38534cb66c9Simarom            return
386e7118220Simarom
38726873712Simarom
388552aaf48Simarom        print(format_text('\nAcquired ports topology:\n', 'bold', 'underline'))
389e7118220Simarom
39026873712Simarom        # bi-dir ports
391552aaf48Simarom        print(format_text('Bi-directional ports:\n','underline'))
39226873712Simarom        for port_a, port_b in table['bi']:
393552aaf48Simarom            print("port {0} <--> port {1}".format(port_a, port_b))
39426873712Simarom
395552aaf48Simarom        print("")
396e7118220Simarom
39726873712Simarom        # unknown ports
398552aaf48Simarom        print(format_text('Mapping unknown:\n','underline'))
39926873712Simarom        for port in table['unknown']:
400552aaf48Simarom            print("port {0}".format(port))
401552aaf48Simarom        print("")
402e7118220Simarom
40326873712Simarom
40426873712Simarom
40526873712Simarom
406963da095Simarom    def do_history (self, line):
407963da095Simarom        '''Manage the command history\n'''
408963da095Simarom
409963da095Simarom        item = parsing_opts.ArgumentPack(['item'],
410963da095Simarom                                         {"nargs": '?',
411963da095Simarom                                          'metavar': 'item',
412963da095Simarom                                          'type': parsing_opts.check_negative,
413963da095Simarom                                          'help': "an history item index",
414963da095Simarom                                          'default': 0})
415963da095Simarom
4162dab6b6dSYaroslav Brustinov        parser = parsing_opts.gen_parser(self.stateless_client,
417963da095Simarom                                         "history",
418963da095Simarom                                         self.do_history.__doc__,
419963da095Simarom                                         item)
420963da095Simarom
421963da095Simarom        opts = parser.parse_args(line.split())
422963da095Simarom        if opts is None:
423963da095Simarom            return
424963da095Simarom
425963da095Simarom        if opts.item == 0:
426963da095Simarom            self.print_history()
427963da095Simarom        else:
428963da095Simarom            cmd = self.get_history_item(opts.item)
429963da095Simarom            if cmd == None:
430963da095Simarom                return
431963da095Simarom
432552aaf48Simarom            print("Executing '{0}'".format(cmd))
4334715b86aSimarom
4344715b86aSimarom            return self.onecmd(cmd)
435963da095Simarom
436963da095Simarom
437cbc645cbSimarom
438467382a7Simarom    ############### connect
439479c4358Simarom    def do_connect (self, line):
4400b39ec30Simarom        '''Connects to the server and acquire ports\n'''
4418384612bSimarom
4422d9d5e14Simarom        self.stateless_client.connect_line(line)
443479c4358Simarom
4440b39ec30Simarom    def help_connect (self):
4450b39ec30Simarom        self.do_connect("-h")
446cbc645cbSimarom
44778c6593cSimarom    def do_disconnect (self, line):
44878c6593cSimarom        '''Disconnect from the server\n'''
449951b09efSimarom
450951b09efSimarom        # stop any monitors before disconnecting
451951b09efSimarom        self.cap_mngr.stop()
4522d9d5e14Simarom        self.stateless_client.disconnect_line(line)
453c3e34b2fSimarom
4540b39ec30Simarom
45594ce0dcdSimarom    @verify_connected
4560b39ec30Simarom    def do_acquire (self, line):
4570b39ec30Simarom        '''Acquire ports\n'''
4580b39ec30Simarom
4590b39ec30Simarom        self.stateless_client.acquire_line(line)
4600b39ec30Simarom
46194ce0dcdSimarom
46294ce0dcdSimarom    @verify_connected
4630b39ec30Simarom    def do_release (self, line):
4640b39ec30Simarom        '''Release ports\n'''
4650b39ec30Simarom        self.stateless_client.release_line(line)
4660b39ec30Simarom
467c420d1fdSimarom    @verify_connected
4687e599394Simarom    def do_reacquire (self, line):
4697e599394Simarom        '''reacquire all the ports under your logged user name'''
4707e599394Simarom        self.stateless_client.reacquire_line(line)
47194ce0dcdSimarom
47294ce0dcdSimarom    def help_acquire (self):
47394ce0dcdSimarom        self.do_acquire("-h")
47494ce0dcdSimarom
4750b39ec30Simarom    def help_release (self):
4760b39ec30Simarom        self.do_release("-h")
4770b39ec30Simarom
478b12cc7fdSimarom    def help_reacquire (self):
479b12cc7fdSimarom        self.do_reacquire("-h")
480b12cc7fdSimarom
481467382a7Simarom    ############### start
482d09df997SDan Klein
483467382a7Simarom    def complete_start(self, text, line, begidx, endidx):
484467382a7Simarom        s = line.split()
485467382a7Simarom        l = len(s)
486d09df997SDan Klein
487467382a7Simarom        file_flags = parsing_opts.get_flags(parsing_opts.FILE_PATH)
488d09df997SDan Klein
489467382a7Simarom        if (l > 1) and (s[l - 1] in file_flags):
490467382a7Simarom            return TRexConsole.tree_autocomplete("")
491c3e34b2fSimarom
492467382a7Simarom        if (l > 2) and (s[l - 2] in file_flags):
493467382a7Simarom            return TRexConsole.tree_autocomplete(s[l - 1])
494d9e1cc14Simarom
49535ab1e17Simarom    complete_push = complete_start
496268c7f12Simarom
49794ce0dcdSimarom    @verify_connected
498d04fb533SDan Klein    def do_start(self, line):
499903b8553Simarom        '''Start selected traffic in specified port(s) on TRex\n'''
500467382a7Simarom
501c93acc26Simarom        self.stateless_client.start_line(line)
502d04fb533SDan Klein
5033f6e247eSHanoh Haim
504467382a7Simarom
505d04fb533SDan Klein    def help_start(self):
506d04fb533SDan Klein        self.do_start("-h")
507d04fb533SDan Klein
508467382a7Simarom    ############# stop
50994ce0dcdSimarom    @verify_connected
510d04fb533SDan Klein    def do_stop(self, line):
511903b8553Simarom        '''stops port(s) transmitting traffic\n'''
51227a7103dSimarom
513c93acc26Simarom        self.stateless_client.stop_line(line)
514d04fb533SDan Klein
515d9a11302Simarom    def help_stop(self):
516d9a11302Simarom        self.do_stop("-h")
517d9a11302Simarom
518d9a11302Simarom    ############# update
51994ce0dcdSimarom    @verify_connected
520d9a11302Simarom    def do_update(self, line):
5210074ceeeSimarom        '''update speed of port(s) currently transmitting traffic\n'''
52227a7103dSimarom
523c93acc26Simarom        self.stateless_client.update_line(line)
524d9a11302Simarom
525d9a11302Simarom    def help_update (self):
526d9a11302Simarom        self.do_update("-h")
527d9a11302Simarom
5281e1c1105SHanoh Haim    ############# pause
52994ce0dcdSimarom    @verify_connected
53054c1f0fcSHanoh Haim    def do_pause(self, line):
53154c1f0fcSHanoh Haim        '''pause port(s) transmitting traffic\n'''
53227a7103dSimarom
533c93acc26Simarom        self.stateless_client.pause_line(line)
53454c1f0fcSHanoh Haim
5351e1c1105SHanoh Haim    ############# resume
53694ce0dcdSimarom    @verify_connected
53754c1f0fcSHanoh Haim    def do_resume(self, line):
53854c1f0fcSHanoh Haim        '''resume port(s) transmitting traffic\n'''
53927a7103dSimarom
540c93acc26Simarom        self.stateless_client.resume_line(line)
54154c1f0fcSHanoh Haim
542d9a11302Simarom
543d04fb533SDan Klein
544467382a7Simarom    ########## reset
54594ce0dcdSimarom    @verify_connected
546467382a7Simarom    def do_reset (self, line):
547467382a7Simarom        '''force stop all ports\n'''
5482d9d5e14Simarom        self.stateless_client.reset_line(line)
549d04fb533SDan Klein
550a6af2a8eSimarom
551a6af2a8eSimarom    ######### validate
552a6af2a8eSimarom    @verify_connected
553a6af2a8eSimarom    def do_validate (self, line):
554a6af2a8eSimarom        '''validates port(s) stream configuration\n'''
555a6af2a8eSimarom
556cc75f3f7Simarom        self.stateless_client.validate_line(line)
557a6af2a8eSimarom
558a6af2a8eSimarom
55995c2405dSimarom    @verify_connected
56091f6c24fSDan Klein    def do_stats(self, line):
56191f6c24fSDan Klein        '''Fetch statistics from TRex server by port\n'''
562cc75f3f7Simarom        self.stateless_client.show_stats_line(line)
56324b895f6Simarom
56491f6c24fSDan Klein
56591f6c24fSDan Klein    def help_stats(self):
56691f6c24fSDan Klein        self.do_stats("-h")
56791f6c24fSDan Klein
5689fc980b8SDan Klein    @verify_connected
5699fc980b8SDan Klein    def do_streams(self, line):
5709fc980b8SDan Klein        '''Fetch statistics from TRex server by port\n'''
571c93acc26Simarom        self.stateless_client.show_streams_line(line)
5729fc980b8SDan Klein
5739fc980b8SDan Klein
5749fc980b8SDan Klein    def help_streams(self):
5759fc980b8SDan Klein        self.do_streams("-h")
5769fc980b8SDan Klein
57795c2405dSimarom    @verify_connected
578a609111bSDan Klein    def do_clear(self, line):
579a609111bSDan Klein        '''Clear cached local statistics\n'''
580c93acc26Simarom        self.stateless_client.clear_stats_line(line)
581a609111bSDan Klein
5820074ceeeSimarom    @verify_connected
5830074ceeeSimarom    def do_service (self, line):
5840074ceeeSimarom        '''Sets port(s) service mode state'''
5850074ceeeSimarom        self.stateless_client.service_line(line)
5860074ceeeSimarom
5870074ceeeSimarom    def help_service (self, line):
5880074ceeeSimarom        self.do_service("-h")
5897baa5bdaSimarom
590f34d418dSimarom    @verify_connected
591f34d418dSimarom    def do_pkt (self, line):
592f34d418dSimarom        '''Sends a scapy notation packet'''
593f34d418dSimarom        self.stateless_client.pkt_line(line)
594f34d418dSimarom
595f34d418dSimarom    def help_pkt (self, line):
596f34d418dSimarom        self.do_pkt("-h")
597f34d418dSimarom
598a609111bSDan Klein    def help_clear(self):
599a609111bSDan Klein        self.do_clear("-h")
600a609111bSDan Klein
601467382a7Simarom
602b6ec2066Simarom    def help_events (self):
603b6ec2066Simarom        self.do_events("-h")
604b6ec2066Simarom
60592dea378Simarom    def do_events (self, line):
60692dea378Simarom        '''shows events recieved from server\n'''
607a913ed85Simarom        self.stateless_client.get_events_line(line)
608b6ec2066Simarom
609d71dbce9Simarom
610aa334e0eSimarom    def complete_profile(self, text, line, begidx, endidx):
611aa334e0eSimarom        return self.complete_start(text,line, begidx, endidx)
612aa334e0eSimarom
613aa334e0eSimarom    def do_profile (self, line):
614aa334e0eSimarom        '''shows information about a profile'''
615aa334e0eSimarom        self.stateless_client.show_profile_line(line)
616aa334e0eSimarom
617467382a7Simarom    # tui
618a6af2a8eSimarom    @verify_connected
619467382a7Simarom    def do_tui (self, line):
620467382a7Simarom        '''Shows a graphical console\n'''
6212dab6b6dSYaroslav Brustinov        parser = parsing_opts.gen_parser(self.stateless_client,
622bae48d6cSimarom                                         "tui",
623bae48d6cSimarom                                         self.do_tui.__doc__,
6241ccf48c7Simarom                                         parsing_opts.XTERM,
6251ccf48c7Simarom                                         parsing_opts.LOCKED)
626bae48d6cSimarom
627bae48d6cSimarom        opts = parser.parse_args(line.split())
628bae48d6cSimarom
6292dab6b6dSYaroslav Brustinov        if not opts:
6302dab6b6dSYaroslav Brustinov            return opts
631bae48d6cSimarom        if opts.xterm:
6326d69e95cSimarom            if not os.path.exists('/usr/bin/xterm'):
633552aaf48Simarom                print(format_text("XTERM does not exists on this machine", 'bold'))
6346d69e95cSimarom                return
635d71dbce9Simarom
6362d9d5e14Simarom            info = self.stateless_client.get_connection_info()
6372d9d5e14Simarom
6387351ddb5Simarom            exe = './trex-console --top -t -q -s {0} -p {1} --async_port {2}'.format(info['server'], info['sync_port'], info['async_port'])
639a913ed85Simarom            cmd = ['/usr/bin/xterm', '-geometry', '{0}x{1}'.format(self.tui.MIN_COLS, self.tui.MIN_ROWS), '-sl', '0', '-title', 'trex_tui', '-e', exe]
6406d69e95cSimarom
64187bac1abSimarom            # detach child
64287bac1abSimarom            self.terminal = subprocess.Popen(cmd, preexec_fn = os.setpgrp)
643d71dbce9Simarom
644bae48d6cSimarom            return
645bae48d6cSimarom
646a913ed85Simarom
647a913ed85Simarom        try:
648a913ed85Simarom            with self.stateless_client.logger.supress():
64990c64917Simarom                self.tui.show(self.stateless_client, self.save_console_history, locked = opts.locked)
65090c64917Simarom
651a913ed85Simarom        except self.tui.ScreenSizeException as e:
652a913ed85Simarom            print(format_text(str(e) + "\n", 'bold'))
653d04fb533SDan Klein
654419a25e9Simarom
655419a25e9Simarom    def help_tui (self):
656419a25e9Simarom        do_tui("-h")
657419a25e9Simarom
658419a25e9Simarom
659467382a7Simarom    # quit function
660d04fb533SDan Klein    def do_quit(self, line):
661467382a7Simarom        '''Exit the client\n'''
662d04fb533SDan Klein        return True
663d04fb533SDan Klein
664467382a7Simarom
665467382a7Simarom    def do_help (self, line):
666467382a7Simarom         '''Shows This Help Screen\n'''
667467382a7Simarom         if line:
668467382a7Simarom             try:
669467382a7Simarom                 func = getattr(self, 'help_' + line)
670467382a7Simarom             except AttributeError:
671467382a7Simarom                 try:
672467382a7Simarom                     doc = getattr(self, 'do_' + line).__doc__
673467382a7Simarom                     if doc:
674467382a7Simarom                         self.stdout.write("%s\n"%str(doc))
675467382a7Simarom                         return
676467382a7Simarom                 except AttributeError:
677467382a7Simarom                     pass
678467382a7Simarom                 self.stdout.write("%s\n"%str(self.nohelp % (line,)))
679467382a7Simarom                 return
680467382a7Simarom             func()
681467382a7Simarom             return
682467382a7Simarom
683552aaf48Simarom         print("\nSupported Console Commands:")
684552aaf48Simarom         print("----------------------------\n")
685467382a7Simarom
686467382a7Simarom         cmds =  [x[3:] for x in self.get_names() if x.startswith("do_")]
687268c7f12Simarom         hidden = ['EOF', 'q', 'exit', 'h', 'shell']
688467382a7Simarom         for cmd in cmds:
689268c7f12Simarom             if cmd in hidden:
690467382a7Simarom                 continue
691467382a7Simarom
692467382a7Simarom             try:
693467382a7Simarom                 doc = getattr(self, 'do_' + cmd).__doc__
694467382a7Simarom                 if doc:
695467382a7Simarom                     help = str(doc)
696467382a7Simarom                 else:
697467382a7Simarom                     help = "*** Undocumented Function ***\n"
698467382a7Simarom             except AttributeError:
699467382a7Simarom                 help = "*** Undocumented Function ***\n"
700951a5033SHanoh Haim
701951a5033SHanoh Haim             l=help.splitlines()
702552aaf48Simarom             print("{:<30} {:<30}".format(cmd + " - ",l[0] ))
703d04fb533SDan Klein
704951b09efSimarom
7057ab12e39Simarom    # a custorm cmdloop wrapper
7067ab12e39Simarom    def start(self):
707f5f92b06Simarom        try:
708f5f92b06Simarom            while True:
709f5f92b06Simarom                try:
710f5f92b06Simarom                    self.cmdloop()
711f5f92b06Simarom                    break
712f5f92b06Simarom                except KeyboardInterrupt as e:
713f5f92b06Simarom                    if not readline.get_line_buffer():
714f5f92b06Simarom                        raise KeyboardInterrupt
715f5f92b06Simarom                    else:
716f5f92b06Simarom                        print("")
717f5f92b06Simarom                        self.intro = None
718f5f92b06Simarom                        continue
719f5f92b06Simarom
720f5f92b06Simarom        finally:
721acf815dbSimarom            # capture manager is not presistent - kill it before going out
722f5f92b06Simarom            self.cap_mngr.stop()
723951b09efSimarom
724d3c89bc5Simarom        if self.terminal:
725d3c89bc5Simarom            self.terminal.kill()
7267ab12e39Simarom
727963da095Simarom    # aliases
7282acb002dSimarom    do_exit = do_EOF = do_q = do_quit
729963da095Simarom    do_h = do_history
730467382a7Simarom
731d3c89bc5Simarom
732cc75f3f7Simarom# run a script of commands
733cc75f3f7Simaromdef run_script_file (self, filename, stateless_client):
734cc75f3f7Simarom
735cc75f3f7Simarom    self.logger.log(format_text("\nRunning script file '{0}'...".format(filename), 'bold'))
736cc75f3f7Simarom
737cc75f3f7Simarom    with open(filename) as f:
738cc75f3f7Simarom        script_lines = f.readlines()
739cc75f3f7Simarom
740cc75f3f7Simarom    cmd_table = {}
741cc75f3f7Simarom
742cc75f3f7Simarom    # register all the commands
743cc75f3f7Simarom    cmd_table['start'] = stateless_client.start_line
744cc75f3f7Simarom    cmd_table['stop']  = stateless_client.stop_line
745cc75f3f7Simarom    cmd_table['reset'] = stateless_client.reset_line
746cc75f3f7Simarom
747cc75f3f7Simarom    for index, line in enumerate(script_lines, start = 1):
748cc75f3f7Simarom        line = line.strip()
749cc75f3f7Simarom        if line == "":
750cc75f3f7Simarom            continue
751cc75f3f7Simarom        if line.startswith("#"):
752cc75f3f7Simarom            continue
753cc75f3f7Simarom
754cc75f3f7Simarom        sp = line.split(' ', 1)
755cc75f3f7Simarom        cmd = sp[0]
756cc75f3f7Simarom        if len(sp) == 2:
757cc75f3f7Simarom            args = sp[1]
758cc75f3f7Simarom        else:
759cc75f3f7Simarom            args = ""
760cc75f3f7Simarom
761cc75f3f7Simarom        stateless_client.logger.log(format_text("Executing line {0} : '{1}'\n".format(index, line)))
762cc75f3f7Simarom
763cc75f3f7Simarom        if not cmd in cmd_table:
764552aaf48Simarom            print("\n*** Error at line {0} : '{1}'\n".format(index, line))
765cc75f3f7Simarom            stateless_client.logger.log(format_text("unknown command '{0}'\n".format(cmd), 'bold'))
766cc75f3f7Simarom            return False
767cc75f3f7Simarom
7686f4a51c1Simarom        cmd_table[cmd](args)
769cc75f3f7Simarom
770cc75f3f7Simarom    stateless_client.logger.log(format_text("\n[Done]", 'bold'))
771cc75f3f7Simarom
772cc75f3f7Simarom    return True
773cc75f3f7Simarom
774cc75f3f7Simarom
7752dee3346Simarom#
7762dee3346Simaromdef is_valid_file(filename):
7772dee3346Simarom    if not os.path.isfile(filename):
7782dee3346Simarom        raise argparse.ArgumentTypeError("The file '%s' does not exist" % filename)
7792dee3346Simarom
7802dee3346Simarom    return filename
7812dee3346Simarom
7822dee3346Simarom
783cc75f3f7Simarom
784aa37a0abSDan Kleindef setParserOptions():
7858384612bSimarom    parser = argparse.ArgumentParser(prog="trex_console.py")
7868384612bSimarom
787b18e8dc6SHanoh Haim    parser.add_argument("-s", "--server", help = "TRex Server [default is localhost]",
7888384612bSimarom                        default = "localhost",
7898384612bSimarom                        type = str)
7908384612bSimarom
79145b71cffSHanoh Haim    parser.add_argument("-p", "--port", help = "TRex Server Port  [default is 4501]\n",
79245b71cffSHanoh Haim                        default = 4501,
7938384612bSimarom                        type = int)
7948384612bSimarom
79545b71cffSHanoh Haim    parser.add_argument("--async_port", help = "TRex ASync Publisher Port [default is 4500]\n",
79645b71cffSHanoh Haim                        default = 4500,
797d04fb533SDan Klein                        dest='pub',
7981586ab13Simarom                        type = int)
7991586ab13Simarom
8000ceddc74SDan Klein    parser.add_argument("-u", "--user", help = "User Name  [default is currently logged in user]\n",
8010ceddc74SDan Klein                        default = get_current_user(),
80230c686d5Simarom                        type = str)
80330c686d5Simarom
8046f4a51c1Simarom    parser.add_argument("-v", "--verbose", dest="verbose",
805f963facdSDan Klein                        action="store_true", help="Switch ON verbose option. Default is: OFF.",
806f963facdSDan Klein                        default = False)
807f963facdSDan Klein
808467382a7Simarom
8090b39ec30Simarom    group = parser.add_mutually_exclusive_group()
8100b39ec30Simarom
8110b39ec30Simarom    group.add_argument("-a", "--acquire", dest="acquire",
8120b39ec30Simarom                       nargs = '+',
8130b39ec30Simarom                       type = int,
8140b39ec30Simarom                       help="Acquire ports on connect. default is all available ports",
8150b39ec30Simarom                       default = None)
8160b39ec30Simarom
8170b39ec30Simarom    group.add_argument("-r", "--readonly", dest="readonly",
8180b39ec30Simarom                       action="store_true",
8190b39ec30Simarom                       help="Starts console in a read only mode",
8200b39ec30Simarom                       default = False)
8210b39ec30Simarom
8220b39ec30Simarom
8230b39ec30Simarom    parser.add_argument("-f", "--force", dest="force",
8240b39ec30Simarom                        action="store_true",
8250b39ec30Simarom                        help="Force acquire the requested ports",
8260b39ec30Simarom                        default = False)
827467382a7Simarom
8282dee3346Simarom    parser.add_argument("--batch", dest="batch",
8292dee3346Simarom                        nargs = 1,
8302dee3346Simarom                        type = is_valid_file,
8312dee3346Simarom                        help = "Run the console in a batch mode with file",
8322dee3346Simarom                        default = None)
8332dee3346Simarom
834fc4b8bddSimarom    parser.add_argument("-t", "--tui", dest="tui",
835fc4b8bddSimarom                        action="store_true", help="Starts with TUI mode",
836fc4b8bddSimarom                        default = False)
837fc4b8bddSimarom
8387351ddb5Simarom    parser.add_argument("-x", "--xtui", dest="xtui",
8397351ddb5Simarom                        action="store_true", help="Starts with XTERM TUI mode",
8407351ddb5Simarom                        default = False)
8417351ddb5Simarom
8427351ddb5Simarom    parser.add_argument("--top", dest="top",
8437351ddb5Simarom                        action="store_true", help="Set the window as always on top",
8447351ddb5Simarom                        default = False)
845d71dbce9Simarom
846d71dbce9Simarom    parser.add_argument("-q", "--quiet", dest="quiet",
847d71dbce9Simarom                        action="store_true", help="Starts with all outputs suppressed",
848d71dbce9Simarom                        default = False)
849d71dbce9Simarom
8508384612bSimarom    return parser
8518384612bSimarom
8521fa7b64cSimarom# a simple info printed on log on
8531fa7b64cSimaromdef show_intro (logger, c):
8541fa7b64cSimarom    x   = c.get_server_system_info()
8551fa7b64cSimarom    ver = c.get_server_version().get('version', 'N/A')
8561fa7b64cSimarom
8571fa7b64cSimarom    # find out which NICs the server has
8581fa7b64cSimarom    port_types = {}
8591fa7b64cSimarom    for port in x['ports']:
86069f6ba4cSYaroslav Brustinov        if 'supp_speeds' in port and port['supp_speeds']:
8612dab6b6dSYaroslav Brustinov            speed = max(port['supp_speeds']) // 1000
8622dab6b6dSYaroslav Brustinov        else:
86369f6ba4cSYaroslav Brustinov            speed = c.ports[port['index']].get_speed_gbps()
8642dab6b6dSYaroslav Brustinov        key = (speed, port.get('description', port['driver']))
8652dab6b6dSYaroslav Brustinov        if key not in port_types:
8661fa7b64cSimarom            port_types[key] = 0
8671fa7b64cSimarom        port_types[key] += 1
8681fa7b64cSimarom
8691fa7b64cSimarom    port_line = ''
8701fa7b64cSimarom    for k, v in port_types.items():
8712dab6b6dSYaroslav Brustinov        port_line += "{0} x {1}Gbps @ {2}\t".format(v, k[0], k[1])
8721fa7b64cSimarom
8731fa7b64cSimarom    logger.log(format_text("\nServer Info:\n", 'underline'))
8741fa7b64cSimarom    logger.log("Server version:   {:>}".format(format_text(ver, 'bold')))
8751fa7b64cSimarom    logger.log("Server CPU:       {:>}".format(format_text("{:>} x {:>}".format(x.get('dp_core_count'), x.get('core_type')), 'bold')))
8761fa7b64cSimarom    logger.log("Ports count:      {:>}".format(format_text(port_line, 'bold')))
8771fa7b64cSimarom
8781fa7b64cSimarom
879aa37a0abSDan Kleindef main():
8808384612bSimarom    parser = setParserOptions()
8811586ab13Simarom    options = parser.parse_args()
8828384612bSimarom
8837351ddb5Simarom    if options.xtui:
8847351ddb5Simarom        options.tui = True
8857351ddb5Simarom
8867351ddb5Simarom    # always on top
8877351ddb5Simarom    if options.top:
8887351ddb5Simarom        set_window_always_on_top('trex_tui')
8897351ddb5Simarom
8907351ddb5Simarom
891f963facdSDan Klein    # Stateless client connection
8929932ff8dSimarom    if options.quiet:
8939932ff8dSimarom        verbose_level = LoggerApi.VERBOSE_QUIET
8949932ff8dSimarom    elif options.verbose:
8959932ff8dSimarom        verbose_level = LoggerApi.VERBOSE_HIGH
8969932ff8dSimarom    else:
8979932ff8dSimarom        verbose_level = LoggerApi.VERBOSE_REGULAR
89895c2405dSimarom
8999932ff8dSimarom    # Stateless client connection
9002d9d5e14Simarom    logger = ConsoleLogger()
9016f4a51c1Simarom    stateless_client = STLClient(username = options.user,
9026f4a51c1Simarom                                 server = options.server,
9036f4a51c1Simarom                                 sync_port = options.port,
9046f4a51c1Simarom                                 async_port = options.pub,
9056f4a51c1Simarom                                 verbose_level = verbose_level,
9066f4a51c1Simarom                                 logger = logger)
90795c2405dSimarom
908fc4b8bddSimarom    # TUI or no acquire will give us READ ONLY mode
909b726b568Simarom    try:
9104c645004Simarom        stateless_client.connect()
911b726b568Simarom    except STLError as e:
912b726b568Simarom        logger.log("Log:\n" + format_text(e.brief() + "\n", 'bold'))
913fc4b8bddSimarom        return
91495c2405dSimarom
9150b39ec30Simarom    if not options.tui and not options.readonly:
916b726b568Simarom        try:
9174c645004Simarom            # acquire all ports
9180b39ec30Simarom            stateless_client.acquire(options.acquire, force = options.force)
919b726b568Simarom        except STLError as e:
920b726b568Simarom            logger.log("Log:\n" + format_text(e.brief() + "\n", 'bold'))
9210b39ec30Simarom
9220b39ec30Simarom            logger.log("\n*** Failed to acquire all required ports ***\n")
9230b39ec30Simarom            return
9240b39ec30Simarom
9250b39ec30Simarom    if options.readonly:
9260b39ec30Simarom        logger.log(format_text("\nRead only mode - only few commands will be available", 'bold'))
927b726b568Simarom
928cbc645cbSimarom    # console
92965b3e045Simarom    try:
930f11d01a0SYaroslav Brustinov        show_intro(logger, stateless_client)
931f11d01a0SYaroslav Brustinov
932f11d01a0SYaroslav Brustinov        # a script mode
933f11d01a0SYaroslav Brustinov        if options.batch:
934f11d01a0SYaroslav Brustinov            cont = run_script_file(options.batch[0], stateless_client)
935f11d01a0SYaroslav Brustinov            if not cont:
936f11d01a0SYaroslav Brustinov                return
937f11d01a0SYaroslav Brustinov
938fc4b8bddSimarom        console = TRexConsole(stateless_client, options.verbose)
9392d9d5e14Simarom        logger.prompt_redraw = console.prompt_redraw
9402d9d5e14Simarom
9417351ddb5Simarom        # TUI
942fc4b8bddSimarom        if options.tui:
9431ccf48c7Simarom            console.do_tui("-x" if options.xtui else "-l")
9441ccf48c7Simarom
945fc4b8bddSimarom        else:
9467ab12e39Simarom            console.start()
9477ab12e39Simarom
94865b3e045Simarom    except KeyboardInterrupt as e:
949552aaf48Simarom        print("\n\n*** Caught Ctrl + C... Exiting...\n\n")
9507ab12e39Simarom
95195c2405dSimarom    finally:
9524c645004Simarom        with stateless_client.logger.supress():
9532828fc9aSimarom            stateless_client.disconnect(stop_traffic = False)
954cbc645cbSimarom
955951b09efSimarom
956cbc645cbSimaromif __name__ == '__main__':
957cbc645cbSimarom    main()
95865b3e045Simarom
959