dpdk_nic_bind.py revision 2ee9ac46
1#! /usr/bin/python
2#
3#   BSD LICENSE
4#
5#   Copyright(c) 2010-2014 Intel Corporation. All rights reserved.
6#   All rights reserved.
7#
8#   Redistribution and use in source and binary forms, with or without
9#   modification, are permitted provided that the following conditions
10#   are met:
11#
12#     * Redistributions of source code must retain the above copyright
13#       notice, this list of conditions and the following disclaimer.
14#     * Redistributions in binary form must reproduce the above copyright
15#       notice, this list of conditions and the following disclaimer in
16#       the documentation and/or other materials provided with the
17#       distribution.
18#     * Neither the name of Intel Corporation nor the names of its
19#       contributors may be used to endorse or promote products derived
20#       from this software without specific prior written permission.
21#
22#   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23#   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24#   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25#   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
26#   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27#   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28#   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29#   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30#   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31#   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32#   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33#
34
35import sys, os, getopt, subprocess, shlex
36from os.path import exists, abspath, dirname, basename
37from distutils.util import strtobool
38text_tables_path = os.path.join('external_libs', 'texttable-0.8.4')
39sys.path.append(text_tables_path)
40import texttable
41sys.path.remove(text_tables_path)
42import re
43import termios
44
45# The PCI device class for ETHERNET devices
46ETHERNET_CLASS = "0200"
47
48# global dict ethernet devices present. Dictionary indexed by PCI address.
49# Each device within this is itself a dictionary of device properties
50devices = {}
51# list of supported DPDK drivers
52dpdk_drivers = [ "igb_uio", "vfio-pci", "uio_pci_generic" ]
53
54# command-line arg flags
55b_flag = None
56status_flag = False
57table_flag = False
58force_flag = False
59args = []
60
61def usage():
62    '''Print usage information for the program'''
63    argv0 = basename(sys.argv[0])
64    print("""
65Usage:
66------
67
68     %(argv0)s [options] DEVICE1 DEVICE2 ....
69
70where DEVICE1, DEVICE2 etc, are specified via PCI "domain:bus:slot.func" syntax
71or "bus:slot.func" syntax. For devices bound to Linux kernel drivers, they may
72also be referred to by Linux interface name e.g. eth0, eth1, em0, em1, etc.
73
74Options:
75    --help, --usage:
76        Display usage information and quit
77
78    -s, --status:
79        Print the current status of all known network interfaces.
80        For each device, it displays the PCI domain, bus, slot and function,
81        along with a text description of the device. Depending upon whether the
82        device is being used by a kernel driver, the igb_uio driver, or no
83        driver, other relevant information will be displayed:
84        * the Linux interface name e.g. if=eth0
85        * the driver being used e.g. drv=igb_uio
86        * any suitable drivers not currently using that device
87            e.g. unused=igb_uio
88        NOTE: if this flag is passed along with a bind/unbind option, the status
89        display will always occur after the other operations have taken place.
90
91    -t, --table:
92        Similar to --status, but gives more info: NUMA, MAC etc.
93
94    -b driver, --bind=driver:
95        Select the driver to use or \"none\" to unbind the device
96
97    -u, --unbind:
98        Unbind a device (Equivalent to \"-b none\")
99
100    --force:
101        By default, devices which are used by Linux - as indicated by having
102        established connections - cannot be modified. Using the --force
103        flag overrides this behavior, allowing active links to be forcibly
104        unbound.
105        WARNING: This can lead to loss of network connection and should be used
106        with caution.
107
108Examples:
109---------
110
111To display current device status:
112        %(argv0)s --status
113
114To bind eth1 from the current driver and move to use igb_uio
115        %(argv0)s --bind=igb_uio eth1
116
117To unbind 0000:01:00.0 from using any driver
118        %(argv0)s -u 0000:01:00.0
119
120To bind 0000:02:00.0 and 0000:02:00.1 to the ixgbe kernel driver
121        %(argv0)s -b ixgbe 02:00.0 02:00.1
122
123    """ % locals()) # replace items from local variables
124
125# This is roughly compatible with check_output function in subprocess module
126# which is only available in python 2.7.
127def check_output(args, stderr=None, **kwargs):
128    '''Run a command and capture its output'''
129    return subprocess.Popen(args, stdout=subprocess.PIPE,
130                            stderr=stderr, **kwargs).communicate()[0]
131
132def find_module(mod):
133    '''find the .ko file for kernel module named mod.
134    Searches the $RTE_SDK/$RTE_TARGET directory, the kernel
135    modules directory and finally under the parent directory of
136    the script '''
137    # check $RTE_SDK/$RTE_TARGET directory
138    if 'RTE_SDK' in os.environ and 'RTE_TARGET' in os.environ:
139        path = "%s/%s/kmod/%s.ko" % (os.environ['RTE_SDK'],\
140                                     os.environ['RTE_TARGET'], mod)
141        if exists(path):
142            return path
143
144    # check using depmod
145    try:
146        depmod_out = check_output(["modinfo", "-n", mod], \
147                                  stderr=subprocess.STDOUT, universal_newlines = True).lower()
148        if "error" not in depmod_out:
149            path = depmod_out.strip()
150            if exists(path):
151                return path
152    except: # if modinfo can't find module, it fails, so continue
153        pass
154
155    # check for a copy based off current path
156    tools_dir = dirname(abspath(sys.argv[0]))
157    if (tools_dir.endswith("tools")):
158        base_dir = dirname(tools_dir)
159        find_out = check_output(["find", base_dir, "-name", mod + ".ko"], universal_newlines = True)
160        if len(find_out) > 0: #something matched
161            path = find_out.splitlines()[0]
162            if exists(path):
163                return path
164
165def check_modules():
166    '''Checks that igb_uio is loaded'''
167    global dpdk_drivers
168
169    with open("/proc/modules") as fd:
170        loaded_mods = fd.readlines()
171
172    # list of supported modules
173    mods =  [{"Name" : driver, "Found" : False} for driver in dpdk_drivers]
174
175    # first check if module is loaded
176    for line in loaded_mods:
177        for mod in mods:
178            if line.startswith(mod["Name"]):
179                mod["Found"] = True
180            # special case for vfio_pci (module is named vfio-pci,
181            # but its .ko is named vfio_pci)
182            elif line.replace("_", "-").startswith(mod["Name"]):
183                mod["Found"] = True
184
185    # check if we have at least one loaded module
186    if True not in [mod["Found"] for mod in mods] and b_flag is not None:
187        if b_flag in dpdk_drivers:
188            print("Error - no supported modules(DPDK driver) are loaded")
189            sys.exit(1)
190        else:
191            print("Warning - no supported modules(DPDK driver) are loaded")
192
193    # change DPDK driver list to only contain drivers that are loaded
194    dpdk_drivers = [mod["Name"] for mod in mods if mod["Found"]]
195
196def has_driver(dev_id):
197    '''return true if a device is assigned to a driver. False otherwise'''
198    return "Driver_str" in devices[dev_id]
199
200def get_pci_device_details(dev_id):
201    '''This function gets additional details for a PCI device'''
202    device = {}
203
204    extra_info = check_output(["lspci", "-vmmks", dev_id], universal_newlines = True).splitlines()
205
206    # parse lspci details
207    for line in extra_info:
208        if len(line) == 0:
209            continue
210        name, value = line.split("\t", 1)
211        name = name.strip(":") + "_str"
212        device[name] = value
213    # check for a unix interface name
214    sys_path = "/sys/bus/pci/devices/%s/net/" % dev_id
215    if exists(sys_path):
216        device["Interface"] = ",".join(os.listdir(sys_path))
217    else:
218        device["Interface"] = ""
219    # check if a port is used for connections (ssh etc)
220    device["Active"] = ""
221
222    return device
223
224def get_nic_details():
225    '''This function populates the "devices" dictionary. The keys used are
226    the pci addresses (domain:bus:slot.func). The values are themselves
227    dictionaries - one for each NIC.'''
228    global devices
229    global dpdk_drivers
230
231    # clear any old data
232    devices = {}
233    # first loop through and read details for all devices
234    # request machine readable format, with numeric IDs
235    dev = {};
236    dev_lines = check_output(["lspci", "-Dvmmn"], universal_newlines = True).splitlines()
237    for dev_line in dev_lines:
238        if (len(dev_line) == 0):
239            if dev["Class"] == ETHERNET_CLASS:
240                #convert device and vendor ids to numbers, then add to global
241                dev["Vendor"] = int(dev["Vendor"],16)
242                dev["Device"] = int(dev["Device"],16)
243                devices[dev["Slot"]] = dict(dev) # use dict to make copy of dev
244        else:
245            name, value = dev_line.split("\t", 1)
246            dev[name.rstrip(":")] = value
247
248    # check active interfaces with established connections
249    established_ip = set()
250    for line in check_output(['netstat', '-tn'], universal_newlines = True).splitlines(): # tcp        0      0 10.56.216.133:22        10.56.46.20:45079       ESTABLISHED
251        line_arr = line.split()
252        if len(line_arr) == 6 and line_arr[0] == 'tcp' and line_arr[5] == 'ESTABLISHED':
253            established_ip.add(line_arr[3].rsplit(':', 1)[0])
254
255    active_if = []
256    for line in check_output(["ip", "-o", "addr"], universal_newlines = True).splitlines(): # 6: eth4    inet 10.56.216.133/24 brd 10.56.216.255 scope global eth4\       valid_lft forever preferred_lft forever
257        line_arr = line.split()
258        if len(line_arr) > 6 and line_arr[2] == 'inet':
259            if line_arr[3].rsplit('/', 1)[0] in established_ip:
260                active_if.append(line_arr[1])
261
262    # based on the basic info, get extended text details
263    for d in devices.keys():
264        # get additional info and add it to existing data
265        devices[d] = dict(list(devices[d].items()) +
266                          list(get_pci_device_details(d).items()))
267
268        for _if in active_if:
269            if _if in devices[d]["Interface"].split(","):
270                devices[d]["Active"] = "*Active*"
271                break;
272
273        # add igb_uio to list of supporting modules if needed
274        if "Module_str" in devices[d]:
275            for driver in dpdk_drivers:
276                if driver not in devices[d]["Module_str"]:
277                    devices[d]["Module_str"] = devices[d]["Module_str"] + ",%s" % driver
278        else:
279            devices[d]["Module_str"] = ",".join(dpdk_drivers)
280
281        # make sure the driver and module strings do not have any duplicates
282        if has_driver(d):
283            modules = devices[d]["Module_str"].split(",")
284            if devices[d]["Driver_str"] in modules:
285                modules.remove(devices[d]["Driver_str"])
286                devices[d]["Module_str"] = ",".join(modules)
287
288        # get MAC from Linux if available
289        mac_file = '/sys/bus/pci/devices/%s/net/%s/address' % (devices[d]['Slot'], devices[d]['Interface'])
290        if os.path.exists(mac_file):
291            with open(mac_file) as f:
292                devices[d]['MAC'] = f.read().strip()
293
294        # get NUMA from Linux if available
295        numa_node_file = '/sys/bus/pci/devices/%s/numa_node' % devices[d]['Slot']
296        if os.path.exists(numa_node_file):
297            with open(numa_node_file) as f:
298                devices[d]['NUMA'] = int(f.read().strip())
299
300def dev_id_from_dev_name(dev_name):
301    '''Take a device "name" - a string passed in by user to identify a NIC
302    device, and determine the device id - i.e. the domain:bus:slot.func - for
303    it, which can then be used to index into the devices array'''
304    dev = None
305    # check if it's already a suitable index
306    if dev_name in devices:
307        return dev_name
308    # check if it's an index just missing the domain part
309    elif "0000:" + dev_name in devices:
310        return "0000:" + dev_name
311    else:
312        # check if it's an interface name, e.g. eth1
313        for d in devices.keys():
314            if dev_name in devices[d]["Interface"].split(","):
315                return devices[d]["Slot"]
316    # if nothing else matches - error
317    print("Unknown device: %s. " \
318        "Please specify device in \"bus:slot.func\" format" % dev_name)
319    sys.exit(1)
320
321def get_igb_uio_usage():
322    for driver in dpdk_drivers:
323        refcnt_file = '/sys/module/%s/refcnt' % driver
324        if not os.path.exists(refcnt_file):
325            continue
326        with open(refcnt_file) as f:
327            ref_cnt = int(f.read().strip())
328            if ref_cnt:
329                return True
330    return False
331
332def get_pid_using_pci(pci_list):
333    if not isinstance(pci_list, list):
334        pci_list = [pci_list]
335    pci_list = map(dev_id_from_dev_name, pci_list)
336    for pid in os.listdir('/proc'):
337        try:
338            int(pid)
339        except ValueError:
340            continue
341        with open('/proc/%s/maps' % pid) as f:
342            f_cont = f.read()
343        for pci in pci_list:
344            if '/%s/' % pci in f_cont:
345                return int(pid)
346
347def read_pid_cmdline(pid):
348    cmdline_file = '/proc/%s/cmdline' % pid
349    if not os.path.exists(cmdline_file):
350        return None
351    with open(cmdline_file, 'rb') as f:
352        return f.read().replace(b'\0', b' ').decode(errors = 'replace')
353
354def confirm(msg, default = False):
355    try:
356        if not os.isatty(1):
357            return default
358        termios.tcflush(sys.stdin, termios.TCIOFLUSH)
359        return strtobool(raw_input(msg))
360    except:
361        return default
362
363def read_line(msg = '', default = ''):
364    try:
365        if not os.isatty(1):
366            return default
367        termios.tcflush(sys.stdin, termios.TCIOFLUSH)
368        return raw_input(msg).strip()
369    except KeyboardInterrupt:
370        print('')
371        sys.exit(1)
372
373def unbind_one(dev_id, force):
374    '''Unbind the device identified by "dev_id" from its current driver'''
375    dev = devices[dev_id]
376    if not has_driver(dev_id):
377        print("%s %s %s is not currently managed by any driver\n" % \
378            (dev[b"Slot"], dev[b"Device_str"], dev[b"Interface"]))
379        return
380
381    if not force and dev.get('Driver_str') in dpdk_drivers and get_igb_uio_usage():
382        pid = get_pid_using_pci(dev_id)
383        if pid:
384            cmdline = read_pid_cmdline(pid)
385            print('Interface %s is in use by process:\n%s' % (dev_id, cmdline))
386            if not confirm('Unbinding might hang the process. Confirm unbind (y/N)'):
387                print('Not unbinding.')
388                return
389
390    # prevent us disconnecting ourselves
391    if dev["Active"] and not force:
392        print('netstat indicates that interface %s is active.' % dev_id)
393        if not confirm('Confirm unbind (y/N)'):
394            print('Not unbinding.')
395            return
396
397    # write to /sys to unbind
398    filename = "/sys/bus/pci/drivers/%s/unbind" % dev["Driver_str"]
399    try:
400        f = open(filename, "a")
401    except:
402        print("Error: unbind failed for %s - Cannot open %s" % (dev_id, filename))
403        sys.exit(1)
404    f.write(dev_id)
405    f.close()
406
407def bind_one(dev_id, driver, force):
408    '''Bind the device given by "dev_id" to the driver "driver". If the device
409    is already bound to a different driver, it will be unbound first'''
410    dev = devices[dev_id]
411    saved_driver = None # used to rollback any unbind in case of failure
412
413    # prevent disconnection of our ssh session
414    if dev["Active"] and not force:
415        print("netstat indicates that interface %s is active" % dev_id)
416        if not confirm("Confirm bind (y/N)"):
417            print('Not binding.')
418            return
419
420    # unbind any existing drivers we don't want
421    if has_driver(dev_id):
422        if dev["Driver_str"] == driver:
423            print("%s already bound to driver %s, skipping\n" % (dev_id, driver))
424            return
425        else:
426            saved_driver = dev["Driver_str"]
427            if not force and get_igb_uio_usage():
428                pid = get_pid_using_pci(dev_id)
429                if pid:
430                    cmdline = read_pid_cmdline(pid)
431                    print('Interface %s is in use by process:\n%s' % (dev_id, cmdline))
432                    if not confirm('Binding to other driver might hang the process. Confirm unbind (y/N)'):
433                        print('Not binding.')
434                        return
435
436            unbind_one(dev_id, force)
437            dev["Driver_str"] = "" # clear driver string
438
439    # if we are binding to one of DPDK drivers, add PCI id's to that driver
440    if driver in dpdk_drivers:
441        filename = "/sys/bus/pci/drivers/%s/new_id" % driver
442        try:
443            f = open(filename, "w")
444        except:
445            print("Error: bind failed for %s - Cannot open %s" % (dev_id, filename))
446            return
447        try:
448            f.write("%04x %04x" % (dev["Vendor"], dev["Device"]))
449            f.close()
450        except:
451            print("Error: bind failed for %s - Cannot write new PCI ID to " \
452                "driver %s" % (dev_id, driver))
453            return
454
455    # do the bind by writing to /sys
456    filename = "/sys/bus/pci/drivers/%s/bind" % driver
457    try:
458        f = open(filename, "a")
459    except:
460        print("Error: bind failed for %s - Cannot open %s" % (dev_id, filename))
461        if saved_driver is not None: # restore any previous driver
462            bind_one(dev_id, saved_driver, force)
463        return
464    try:
465        f.write(dev_id)
466        f.close()
467    except:
468        # for some reason, closing dev_id after adding a new PCI ID to new_id
469        # results in IOError. however, if the device was successfully bound,
470        # we don't care for any errors and can safely ignore IOError
471        tmp = get_pci_device_details(dev_id)
472        if "Driver_str" in tmp and tmp["Driver_str"] == driver:
473            return
474        print("Error: bind failed for %s - Cannot bind to driver %s" % (dev_id, driver))
475        if saved_driver is not None: # restore any previous driver
476            bind_one(dev_id, saved_driver, force)
477        return
478
479
480def unbind_all(dev_list, force=False):
481    """Unbind method, takes a list of device locations"""
482    dev_list = map(dev_id_from_dev_name, dev_list)
483    for d in dev_list:
484        unbind_one(d, force)
485
486def bind_all(dev_list, driver, force=False):
487    """Bind method, takes a list of device locations"""
488    global devices
489
490    dev_list = list(map(dev_id_from_dev_name, dev_list))
491
492    for d in dev_list:
493        bind_one(d, driver, force)
494
495    # when binding devices to a generic driver (i.e. one that doesn't have a
496    # PCI ID table), some devices that are not bound to any other driver could
497    # be bound even if no one has asked them to. hence, we check the list of
498    # drivers again, and see if some of the previously-unbound devices were
499    # erroneously bound.
500    for d in devices.keys():
501        # skip devices that were already bound or that we know should be bound
502        if "Driver_str" in devices[d] or d in dev_list:
503            continue
504
505        # update information about this device
506        devices[d] = dict(list(devices[d].items()) +
507                          list(get_pci_device_details(d).items()))
508
509        # check if updated information indicates that the device was bound
510        if "Driver_str" in devices[d]:
511            unbind_one(d, force)
512
513def display_devices(title, dev_list, extra_params = None):
514    '''Displays to the user the details of a list of devices given in "dev_list"
515    The "extra_params" parameter, if given, should contain a string with
516    %()s fields in it for replacement by the named fields in each device's
517    dictionary.'''
518    strings = [] # this holds the strings to print. We sort before printing
519    print("\n%s" % title)
520    print("="*len(title))
521    if len(dev_list) == 0:
522        strings.append("<none>")
523    else:
524        for dev in dev_list:
525            if extra_params is not None:
526                strings.append("%s '%s' %s" % (dev["Slot"], \
527                                dev["Device_str"], extra_params % dev))
528            else:
529                strings.append("%s '%s'" % (dev["Slot"], dev["Device_str"]))
530    # sort before printing, so that the entries appear in PCI order
531    strings.sort()
532    print("\n".join(strings)) # print one per line
533
534def show_status():
535    '''Function called when the script is passed the "--status" option. Displays
536    to the user what devices are bound to the igb_uio driver, the kernel driver
537    or to no driver'''
538    global dpdk_drivers
539    kernel_drv = []
540    dpdk_drv = []
541    no_drv = []
542
543    # split our list of devices into the three categories above
544    for d in devices.keys():
545        if not has_driver(d):
546            no_drv.append(devices[d])
547            continue
548        if devices[d]["Driver_str"] in dpdk_drivers:
549            dpdk_drv.append(devices[d])
550        else:
551            kernel_drv.append(devices[d])
552
553    # print each category separately, so we can clearly see what's used by DPDK
554    display_devices("Network devices using DPDK-compatible driver", dpdk_drv, \
555                    "drv=%(Driver_str)s unused=%(Module_str)s")
556    display_devices("Network devices using kernel driver", kernel_drv,
557                    "if=%(Interface)s drv=%(Driver_str)s unused=%(Module_str)s %(Active)s")
558    display_devices("Other network devices", no_drv,\
559                    "unused=%(Module_str)s")
560
561def get_info_from_trex(pci_addr_list):
562    if not pci_addr_list:
563        return {}
564    pci_info_dict = {}
565    run_command = 'sudo ./t-rex-64 --dump-interfaces %s' % ' '.join(pci_addr_list)
566    proc = subprocess.Popen(shlex.split(run_command), stdout=subprocess.PIPE,
567                            stderr=subprocess.STDOUT, universal_newlines = True)
568    stdout, _ = proc.communicate()
569    if proc.returncode:
570        if 'PANIC in rte_eal_init' in stdout:
571            print("Could not run TRex to get MAC info about interfaces, check if it's already running.")
572        else:
573            print('Error upon running TRex to get MAC info:\n%s' % stdout)
574        sys.exit(1)
575    pci_mac_str = 'PCI: (\S+).+?MAC: (\S+).+?Driver: (\S+)'
576    pci_mac_re = re.compile(pci_mac_str)
577    for line in stdout.splitlines():
578        match = pci_mac_re.match(line)
579        if match:
580            pci = match.group(1)
581            if pci not in pci_addr_list: # sanity check, should not happen
582                print('Internal error while getting info of DPDK bound interfaces, unknown PCI: %s' % pci)
583                sys.exit(1)
584            pci_info_dict[pci] = {}
585            pci_info_dict[pci]['MAC'] = match.group(2)
586            pci_info_dict[pci]['TRex_Driver'] = match.group(3)
587    return pci_info_dict
588
589def show_table():
590    '''Function called when the script is passed the "--table" option.
591    Similar to show_status() function, but shows more info: NUMA etc.'''
592    global dpdk_drivers
593    dpdk_drv = []
594    for d in devices.keys():
595        if devices[d].get("Driver_str") in dpdk_drivers:
596            dpdk_drv.append(d)
597
598    for pci, info in get_info_from_trex(dpdk_drv).items():
599        if pci not in dpdk_drv: # sanity check, should not happen
600            print('Internal error while getting MACs of DPDK bound interfaces, unknown PCI: %s' % pci)
601            return
602        devices[pci].update(info)
603
604    table = texttable.Texttable(max_width=-1)
605    table.header(['ID', 'NUMA', 'PCI', 'MAC', 'Name', 'Driver', 'Linux IF', 'Active'])
606    for id, pci in enumerate(sorted(devices.keys())):
607        d = devices[pci]
608        table.add_row([id, d['NUMA'], d['Slot_str'], d.get('MAC', ''), d['Device_str'], d.get('Driver_str', ''), d['Interface'], d['Active']])
609    print(table.draw())
610
611def parse_args():
612    '''Parses the command-line arguments given by the user and takes the
613    appropriate action for each'''
614    global b_flag
615    global status_flag
616    global table_flag
617    global force_flag
618    global args
619    if len(sys.argv) <= 1:
620        usage()
621        sys.exit(0)
622
623    try:
624        opts, args = getopt.getopt(sys.argv[1:], "b:ust",
625                               ["help", "usage", "status", "table", "force",
626                                "bind=", "unbind"])
627    except getopt.GetoptError as error:
628        print(str(error))
629        print("Run '%s --usage' for further information" % sys.argv[0])
630        sys.exit(1)
631
632    for opt, arg in opts:
633        if opt == "--help" or opt == "--usage":
634            usage()
635            sys.exit(0)
636        if opt == "--status" or opt == "-s":
637            status_flag = True
638        if opt == "--table" or opt == "-t":
639            table_flag = True
640        if opt == "--force":
641            force_flag = True
642        if opt == "-b" or opt == "-u" or opt == "--bind" or opt == "--unbind":
643            if b_flag is not None:
644                print("Error - Only one bind or unbind may be specified\n")
645                sys.exit(1)
646            if opt == "-u" or opt == "--unbind":
647                b_flag = "none"
648            else:
649                b_flag = arg
650
651def do_arg_actions():
652    '''do the actual action requested by the user'''
653    global b_flag
654    global status_flag
655    global table_flag
656    global force_flag
657    global args
658
659    if b_flag is None and not status_flag and not table_flag:
660        print("Error: No action specified for devices. Please give a -b or -u option")
661        print("Run '%s --usage' for further information" % sys.argv[0])
662        sys.exit(1)
663
664    if b_flag is not None and len(args) == 0:
665        print("Error: No devices specified.")
666        print("Run '%s --usage' for further information" % sys.argv[0])
667        sys.exit(1)
668
669    if b_flag == "none" or b_flag == "None":
670        unbind_all(args, force_flag)
671    elif b_flag is not None:
672        bind_all(args, b_flag, force_flag)
673    if status_flag:
674        if b_flag is not None:
675            get_nic_details() # refresh if we have changed anything
676        show_status()
677    if table_flag:
678        if b_flag is not None:
679            get_nic_details() # refresh if we have changed anything
680        show_table()
681
682def main():
683    '''program main function'''
684    parse_args()
685    check_modules()
686    get_nic_details()
687    do_arg_actions()
688
689if __name__ == "__main__":
690    main()
691