mirror of
https://github.com/CumulusNetworks/ifupdown2.git
synced 2024-05-06 15:54:50 +00:00
Ticket: None Reviewed By: CCR-4692 Testing Done: smoke + scale tests If called with close_fds=True the subprocess module will try to close every fd from 3 to MAXFD before executing the specified command. This is done in Python not even with a C-implementation which truly affecting performances. This patch aims to better handle the file descriptor used by ifupdown2. Either by closing them after use or by setting the close-on-exec flag for the file descriptor, which causes the file descriptor to be automatically (and atomically) closed when any of the exec-family functions succeed. With the actual patch all tests are passing, I can't think of any future issue but if any a possible future modification might be to use the parameter 'preexec_fn', which allows us to set function which will be executed in the child process before executing the command line. We can always manually close any remaining open file descriptors with something like: >>> os.listdir('/proc/self/fd/') ['0', '1', '2', ‘3’, etc..] >>> for fd in os.listdir('/proc/self/fd/') >>> if int(fd) > 2: >>> os.close(fd) This patch is also totally re-organising the use of subprocesses. By removing all subprocess code redundancy.
543 lines
23 KiB
Python
Executable File
543 lines
23 KiB
Python
Executable File
#!/usr/bin/python
|
|
#
|
|
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
|
|
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
|
|
#
|
|
# ifupdown --
|
|
# tool to configure network interfaces
|
|
#
|
|
import sys
|
|
import os
|
|
import argcomplete
|
|
import argparse
|
|
import ConfigParser
|
|
import StringIO
|
|
import logging
|
|
import logging.handlers
|
|
import resource
|
|
import pkg_resources
|
|
from ifupdown.ifupdownmain import *
|
|
from ifupdown.utils import *
|
|
|
|
lockfile="/run/network/.lock"
|
|
configfile="/etc/network/ifupdown2/ifupdown2.conf"
|
|
configmap_g=None
|
|
logger = None
|
|
interfacesfileiobuf=None
|
|
interfacesfilename=None
|
|
ENVPATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
|
|
def run_up(args):
|
|
logger.debug('args = %s' %str(args))
|
|
|
|
try:
|
|
iflist = args.iflist
|
|
if len(args.iflist) == 0:
|
|
iflist = None
|
|
logger.debug('creating ifupdown object ..')
|
|
cachearg=(False if (iflist or args.nocache or
|
|
args.perfmode or args.noact)
|
|
else True)
|
|
ifupdown_handle = ifupdownMain(config=configmap_g,
|
|
force=args.force,
|
|
withdepends=args.withdepends,
|
|
perfmode=args.perfmode,
|
|
dryrun=args.noact,
|
|
cache=cachearg,
|
|
addons_enable=not args.noaddons,
|
|
statemanager_enable=not args.noaddons,
|
|
interfacesfile=interfacesfilename,
|
|
interfacesfileiobuf=interfacesfileiobuf,
|
|
interfacesfileformat=args.interfacesfileformat)
|
|
if args.noaddons:
|
|
ifupdown_handle.up(['up'], args.all, args.CLASS, iflist,
|
|
excludepats=args.excludepats,
|
|
printdependency=args.printdependency,
|
|
syntaxcheck=args.syntaxcheck, type=args.type,
|
|
skipupperifaces=args.skipupperifaces)
|
|
else:
|
|
ifupdown_handle.up(['pre-up', 'up', 'post-up'],
|
|
args.all, args.CLASS, iflist,
|
|
excludepats=args.excludepats,
|
|
printdependency=args.printdependency,
|
|
syntaxcheck=args.syntaxcheck, type=args.type,
|
|
skipupperifaces=args.skipupperifaces)
|
|
except:
|
|
raise
|
|
|
|
def run_down(args):
|
|
logger.debug('args = %s' %str(args))
|
|
|
|
try:
|
|
iflist = args.iflist
|
|
logger.debug('creating ifupdown object ..')
|
|
ifupdown_handle = ifupdownMain(config=configmap_g, force=args.force,
|
|
withdepends=args.withdepends,
|
|
perfmode=args.perfmode,
|
|
dryrun=args.noact,
|
|
addons_enable=not args.noaddons,
|
|
statemanager_enable=not args.noaddons,
|
|
interfacesfile=interfacesfilename,
|
|
interfacesfileiobuf=interfacesfileiobuf,
|
|
interfacesfileformat=args.interfacesfileformat)
|
|
|
|
ifupdown_handle.down(['pre-down', 'down', 'post-down'],
|
|
args.all, args.CLASS, iflist,
|
|
excludepats=args.excludepats,
|
|
printdependency=args.printdependency,
|
|
usecurrentconfig=args.usecurrentconfig,
|
|
type=args.type)
|
|
except:
|
|
raise
|
|
|
|
def run_query(args):
|
|
logger.debug('args = %s' %str(args))
|
|
|
|
try:
|
|
iflist = args.iflist
|
|
if args.checkcurr:
|
|
qop='query-checkcurr'
|
|
elif args.running:
|
|
qop='query-running'
|
|
elif args.raw:
|
|
qop='query-raw'
|
|
elif args.syntaxhelp:
|
|
qop = 'query-syntax'
|
|
elif args.printdependency:
|
|
qop = 'query-dependency'
|
|
elif args.printsavedstate:
|
|
qop = 'query-savedstate'
|
|
else:
|
|
qop='query'
|
|
cachearg=(False if (iflist or args.nocache or
|
|
args.perfmode or args.syntaxhelp or
|
|
(qop != 'query-checkcurr' and
|
|
qop != 'query-running')) else True)
|
|
if not iflist and qop == 'query-running':
|
|
iflist = [i for i in os.listdir('/sys/class/net/')
|
|
if os.path.isdir('/sys/class/net/%s' %i)]
|
|
logger.debug('creating ifupdown object ..')
|
|
ifupdown_handle = ifupdownMain(config=configmap_g,
|
|
withdepends=args.withdepends,
|
|
perfmode=args.perfmode,
|
|
cache=cachearg,
|
|
interfacesfile=interfacesfilename,
|
|
interfacesfileiobuf=interfacesfileiobuf,
|
|
interfacesfileformat=args.interfacesfileformat,
|
|
withdefaults=args.withdefaults)
|
|
# list implies all auto interfaces (this is how ifupdown behaves)
|
|
if args.list:
|
|
args.all = True
|
|
ifupdown_handle.query([qop], args.all, args.list, args.CLASS, iflist,
|
|
excludepats=args.excludepats,
|
|
printdependency=args.printdependency,
|
|
format=args.format, type=args.type)
|
|
except:
|
|
raise
|
|
|
|
def run_reload(args):
|
|
logger.debug('args = %s' %str(args))
|
|
|
|
try:
|
|
logger.debug('creating ifupdown object ..')
|
|
ifupdown_handle = ifupdownMain(config=configmap_g,
|
|
interfacesfile=interfacesfilename,
|
|
withdepends=args.withdepends,
|
|
perfmode=args.perfmode,
|
|
dryrun=args.noact)
|
|
ifupdown_handle.reload(['pre-up', 'up', 'post-up'],
|
|
['pre-down', 'down', 'post-down'],
|
|
auto=args.all, allow=args.CLASS, ifacenames=None,
|
|
excludepats=args.excludepats,
|
|
usecurrentconfig=args.usecurrentconfig,
|
|
syntaxcheck=args.syntaxcheck,
|
|
currentlyup=args.currentlyup)
|
|
except:
|
|
raise
|
|
|
|
def init(args):
|
|
global logger
|
|
global interfacesfileiobuf
|
|
global interfacesfilename
|
|
|
|
log_level = logging.WARNING
|
|
if args.verbose:
|
|
log_level = logging.INFO
|
|
if args.debug:
|
|
log_level = logging.DEBUG
|
|
|
|
try:
|
|
if hasattr(args, 'syslog') and args.syslog:
|
|
root_logger = logging.getLogger()
|
|
syslog_handler = logging.handlers.SysLogHandler(address='/dev/log',
|
|
facility=logging.handlers.SysLogHandler.LOG_DAEMON)
|
|
logging.addLevelName(logging.ERROR, 'error')
|
|
logging.addLevelName(logging.WARNING, 'warning')
|
|
logging.addLevelName(logging.DEBUG, 'debug')
|
|
logging.addLevelName(logging.INFO, 'info')
|
|
root_logger.setLevel(log_level)
|
|
syslog_handler.setFormatter(logging.Formatter(
|
|
'%(name)s: %(levelname)s: %(message)s'))
|
|
root_logger.addHandler(syslog_handler)
|
|
logger = logging.getLogger('ifupdown')
|
|
else:
|
|
logging.basicConfig(level=log_level,
|
|
format='%(levelname)s: %(message)s')
|
|
logging.addLevelName(logging.ERROR, 'error')
|
|
logging.addLevelName(logging.WARNING, 'warning')
|
|
logging.addLevelName(logging.DEBUG, 'debug')
|
|
logging.addLevelName(logging.INFO, 'info')
|
|
logger = logging.getLogger('ifupdown')
|
|
except:
|
|
raise
|
|
|
|
if hasattr(args, 'interfacesfile') and args.interfacesfile != None:
|
|
# Check to see if -i option is allowed by config file
|
|
# But for ifquery, we will not check this
|
|
if (not sys.argv[0].endswith('ifquery') and
|
|
configmap_g.get('disable_cli_interfacesfile','0') == '1'):
|
|
logger.error('disable_cli_interfacesfile is set so users '
|
|
'not allowed to specify interfaces file on cli.')
|
|
exit(1)
|
|
if args.interfacesfile == '-':
|
|
# If interfaces file is stdin, read
|
|
interfacesfileiobuf = sys.stdin.read()
|
|
else:
|
|
interfacesfilename = args.interfacesfile
|
|
else:
|
|
# if the ifupdown2 config file does not have it, default to standard
|
|
interfacesfilename = configmap_g.get('default_interfaces_configfile',
|
|
'/etc/network/interfaces')
|
|
|
|
|
|
|
|
|
|
def deinit():
|
|
{}
|
|
|
|
def update_argparser(argparser):
|
|
""" base parser, common to all commands """
|
|
|
|
argparser.add_argument('-a', '--all', action='store_true', required=False,
|
|
help='process all interfaces marked \"auto\"')
|
|
argparser.add_argument('iflist', metavar='IFACE',
|
|
nargs='*', help='interface list separated by spaces. ' +
|
|
'IFACE list is mutually exclusive with -a option.')
|
|
argparser.add_argument('-v', '--verbose', dest='verbose',
|
|
action='store_true', help='verbose')
|
|
argparser.add_argument('-d', '--debug', dest='debug',
|
|
action='store_true',
|
|
help='output debug info')
|
|
argparser.add_argument('-q', '--quiet', dest='quiet',
|
|
action='store_true',
|
|
help=argparse.SUPPRESS)
|
|
argparser.add_argument('--allow', dest='CLASS',
|
|
help='ignore non-\"allow-CLASS\" interfaces')
|
|
argparser.add_argument('-w', '--with-depends', dest='withdepends',
|
|
action='store_true', help='run with all dependent interfaces.'+
|
|
' This option is redundant when \'-a\' is specified. With ' +
|
|
'\'-a\' interfaces are always executed in dependency order')
|
|
argparser.add_argument('--perfmode', dest='perfmode',
|
|
action='store_true', help=argparse.SUPPRESS)
|
|
#argparser.add_argument('-j', '--jobs', dest='jobs', type=int,
|
|
# default=-1, choices=range(1,12), help=argparse.SUPPRESS)
|
|
argparser.add_argument('--nocache', dest='nocache', action='store_true',
|
|
help=argparse.SUPPRESS)
|
|
argparser.add_argument('-X', '--exclude', dest='excludepats',
|
|
action='append',
|
|
help='Exclude interfaces from the list of interfaces' +
|
|
' to operate on. Can be specified multiple times.')
|
|
argparser.add_argument('-i', '--interfaces', dest='interfacesfile',
|
|
default=None,
|
|
help='Specify interfaces file instead of file defined ' +
|
|
'in ifupdown2.conf file')
|
|
argparser.add_argument('-t', '--interfaces-format',
|
|
dest='interfacesfileformat',
|
|
default='native',
|
|
choices=['native', 'json'],
|
|
help='interfaces file format')
|
|
argparser.add_argument('-T', '--type',
|
|
dest='type',
|
|
default=None,
|
|
choices=['iface', 'vlan'],
|
|
help='type of interface entry (iface or vlan).' +
|
|
'This option can be used in case of ambiguity between ' +
|
|
'a vlan interface and an iface interface of the same name')
|
|
|
|
def update_ifupdown_argparser(argparser):
|
|
""" common arg parser for ifup and ifdown """
|
|
argparser.add_argument('-f', '--force', dest='force',
|
|
action='store_true',
|
|
help='force run all operations')
|
|
argparser.add_argument('-l', '--syslog', dest='syslog',
|
|
action='store_true',
|
|
help=argparse.SUPPRESS)
|
|
group = argparser.add_mutually_exclusive_group(required=False)
|
|
group.add_argument('-n', '--no-act', dest='noact',
|
|
action='store_true', help='print out what would happen,' +
|
|
'but don\'t do it')
|
|
group.add_argument('-p', '--print-dependency',
|
|
dest='printdependency', choices=['list', 'dot'],
|
|
help='print iface dependency')
|
|
group.add_argument('--no-scripts', '--admin-state',
|
|
dest='noaddons', action='store_true',
|
|
help='dont run any addon modules/scripts. Only bring the ' +
|
|
'interface administratively up/down')
|
|
|
|
def update_ifup_argparser(argparser):
|
|
argparser.add_argument('-s', '--syntax-check', dest='syntaxcheck',
|
|
action='store_true',
|
|
help='Only run the interfaces file parser')
|
|
argparser.add_argument('-k', '--skip-upperifaces', dest='skipupperifaces',
|
|
action='store_true',
|
|
help='ifup by default tries to add newly created interfaces' +
|
|
' into its upper/parent interfaces. Eg. if a bridge port is' +
|
|
' created as a result of ifup on the port, ifup automatically' +
|
|
' adds the port to the bridge. This option can be used to ' +
|
|
'disable this default behaviour')
|
|
update_ifupdown_argparser(argparser)
|
|
|
|
def update_ifdown_argparser(argparser):
|
|
update_ifupdown_argparser(argparser)
|
|
argparser.add_argument('-u', '--use-current-config',
|
|
dest='usecurrentconfig', action='store_true',
|
|
help='By default ifdown looks at the saved state for ' +
|
|
'interfaces to bring down. This option allows ifdown to ' +
|
|
'look at the current interfaces file. Useful when your ' +
|
|
'state file is corrupted or you want down to use the latest '
|
|
'from the interfaces file')
|
|
|
|
def update_ifquery_argparser(argparser):
|
|
""" arg parser for ifquery options """
|
|
|
|
# -l is same as '-a', only here for backward compatibility
|
|
argparser.add_argument('-l', '--list', action='store_true', dest='list',
|
|
help='list all matching known interfaces')
|
|
group = argparser.add_mutually_exclusive_group(required=False)
|
|
group.add_argument('-r', '--running', dest='running',
|
|
action='store_true',
|
|
help='query running state of an interface')
|
|
group.add_argument('-c', '--check', dest='checkcurr',
|
|
action='store_true',
|
|
help='check interface file contents against ' +
|
|
'running state of an interface')
|
|
group.add_argument('-x', '--raw', action='store_true', dest='raw',
|
|
help='print raw config file entries')
|
|
group.add_argument('--print-savedstate', action='store_true',
|
|
dest='printsavedstate',
|
|
help=argparse.SUPPRESS)
|
|
argparser.add_argument('-o', '--format', dest='format', default='native',
|
|
choices=['native', 'json'],
|
|
help='interface display format')
|
|
argparser.add_argument('-p', '--print-dependency',
|
|
dest='printdependency', choices=['list', 'dot'],
|
|
help='print interface dependency')
|
|
argparser.add_argument('-s', '--syntax-help', action='store_true',
|
|
dest='syntaxhelp',
|
|
help='print supported interface config syntax')
|
|
argparser.add_argument('--with-defaults', action='store_true',
|
|
dest='withdefaults',
|
|
help='check policy default file contents, ' +
|
|
'for unconfigured attributes, against ' +
|
|
'running state of an interface')
|
|
|
|
def update_ifreload_argparser(argparser):
|
|
""" parser for ifreload """
|
|
group = argparser.add_mutually_exclusive_group(required=True)
|
|
group.add_argument('-a', '--all', action='store_true',
|
|
help='process all interfaces marked \"auto\"')
|
|
group.add_argument('-c', '--currently-up', dest='currentlyup',
|
|
action='store_true',
|
|
help='only reload auto and other interfaces that are ' +
|
|
'currently up')
|
|
group.add_argument('--allow', dest='CLASS',
|
|
help='ignore non-\"allow-CLASS\" interfaces')
|
|
argparser.add_argument('iflist', metavar='IFACE',
|
|
nargs='*', help=argparse.SUPPRESS)
|
|
argparser.add_argument('-n', '--no-act', dest='noact',
|
|
action='store_true', help='print out what would happen,' +
|
|
'but don\'t do it')
|
|
argparser.add_argument('-v', '--verbose', dest='verbose',
|
|
action='store_true', help='verbose')
|
|
argparser.add_argument('-d', '--debug', dest='debug',
|
|
action='store_true',
|
|
help='output debug info')
|
|
argparser.add_argument('-w', '--with-depends', dest='withdepends',
|
|
action='store_true', help=argparse.SUPPRESS)
|
|
argparser.add_argument('--perfmode', dest='perfmode',
|
|
action='store_true', help=argparse.SUPPRESS)
|
|
argparser.add_argument('--nocache', dest='nocache', action='store_true',
|
|
help=argparse.SUPPRESS)
|
|
argparser.add_argument('-X', '--exclude', dest='excludepats',
|
|
action='append',
|
|
help=argparse.SUPPRESS)
|
|
#argparser.add_argument('-j', '--jobs', dest='jobs', type=int,
|
|
# default=-1, choices=range(1,12), help=argparse.SUPPRESS)
|
|
#argparser.add_argument('-i', '--interfaces', dest='interfacesfile',
|
|
# default='/etc/network/interfaces',
|
|
# help='use interfaces file instead of default ' +
|
|
# '/etc/network/interfaces')
|
|
argparser.add_argument('-u', '--use-current-config',
|
|
dest='usecurrentconfig', action='store_true',
|
|
help='By default ifreload looks at saved state for ' +
|
|
'interfaces to bring down. With this option ifreload will'
|
|
' only look at the current interfaces file. Useful when your ' +
|
|
'state file is corrupted or you want down to use the latest '
|
|
'from the interfaces file')
|
|
argparser.add_argument('-l', '--syslog', dest='syslog',
|
|
action='store_true',
|
|
help=argparse.SUPPRESS)
|
|
argparser.add_argument('-f', '--force', dest='force',
|
|
action='store_true',
|
|
help='force run all operations')
|
|
argparser.add_argument('-s', '--syntax-check', dest='syntaxcheck',
|
|
action='store_true',
|
|
help='Only run the interfaces file parser')
|
|
|
|
def update_common_argparser(argparser):
|
|
''' general parsing rules '''
|
|
|
|
package = pkg_resources.get_distribution("ifupdown2")
|
|
argparser.add_argument('-V', '--version',
|
|
action='version',
|
|
version='ifupdown2:%(prog)s ' + package.version,
|
|
help='display current ifupdown2 version')
|
|
|
|
def parse_args(argsv, op):
|
|
if op == 'query':
|
|
descr = 'query interfaces (all or interface list)'
|
|
elif op == 'reload':
|
|
descr = 'reload interface configuration.'
|
|
else:
|
|
descr = 'interface management'
|
|
argparser = argparse.ArgumentParser(description=descr)
|
|
if op == 'reload':
|
|
update_ifreload_argparser(argparser)
|
|
else:
|
|
update_argparser(argparser)
|
|
if op == 'up':
|
|
update_ifup_argparser(argparser)
|
|
elif op == 'down':
|
|
update_ifdown_argparser(argparser)
|
|
elif op == 'query':
|
|
update_ifquery_argparser(argparser)
|
|
update_common_argparser(argparser)
|
|
argcomplete.autocomplete(argparser)
|
|
return argparser.parse_args(argsv)
|
|
|
|
handlers = {'up' : run_up,
|
|
'down' : run_down,
|
|
'query' : run_query,
|
|
'reload' : run_reload }
|
|
|
|
def validate_args(op, args):
|
|
#if op == 'up' and args.syntaxcheck:
|
|
# if args.iflist or args.all:
|
|
# print 'ignoring interface options ..'
|
|
# return True
|
|
if op == 'query' and (args.syntaxhelp or args.list):
|
|
return True
|
|
if op == 'reload':
|
|
if not args.all and not args.currentlyup and not args.CLASS:
|
|
print '\'-a\' or \'-c\' or \'-allow\' option is required'
|
|
return False
|
|
elif (not args.iflist and
|
|
not args.all and not args.CLASS):
|
|
print '\'-a\' option or interface list are required'
|
|
return False
|
|
if args.iflist and args.all:
|
|
print '\'-a\' option and interface list are mutually exclusive'
|
|
return False
|
|
if op != 'reload' and args.CLASS and (args.all or args.iflist):
|
|
print ('\'--allow\' option is mutually exclusive ' +
|
|
'with interface list and \'-a\'')
|
|
return False
|
|
return True
|
|
|
|
def read_config(args):
|
|
global configmap_g
|
|
|
|
with open(configfile, 'r') as f:
|
|
config = f.read()
|
|
configStr = '[ifupdown2]\n' + config
|
|
configFP = StringIO.StringIO(configStr)
|
|
parser = ConfigParser.RawConfigParser()
|
|
parser.readfp(configFP)
|
|
configmap_g = dict(parser.items('ifupdown2'))
|
|
|
|
# Preprocess config map
|
|
configval = configmap_g.get('multiple_vlan_aware_bridge_support', '0')
|
|
if configval == '0':
|
|
# if multiple bridges not allowed, set the bridge-vlan-aware
|
|
# attribute in the 'no_repeats' config, so that the ifupdownmain
|
|
# module can catch it appropriately
|
|
configmap_g['no_repeats'] = {'bridge-vlan-aware' : 'yes'}
|
|
|
|
|
|
configval = configmap_g.get('link_master_slave', '0')
|
|
if configval == '1':
|
|
# link_master_slave is only valid when all is set
|
|
if hasattr(args, 'all') and not args.all:
|
|
configmap_g['link_master_slave'] = '0'
|
|
|
|
configval = configmap_g.get('delay_admin_state_change', '0')
|
|
if configval == '1':
|
|
# reset link_master_slave if delay_admin_state_change is on
|
|
configmap_g['link_master_slave'] = '0'
|
|
|
|
def main(argv):
|
|
""" main function """
|
|
args = None
|
|
try:
|
|
op = None
|
|
if argv[0].endswith('ifup'):
|
|
op = 'up'
|
|
elif argv[0].endswith('ifdown'):
|
|
op = 'down'
|
|
elif argv[0].endswith('ifquery'):
|
|
op = 'query'
|
|
elif argv[0].endswith('ifreload'):
|
|
op = 'reload'
|
|
else:
|
|
print ('Unexpected executable.' +
|
|
' Should be \'ifup\' or \'ifdown\' or \'ifquery\'')
|
|
exit(1)
|
|
# Command line arg parser
|
|
args = parse_args(argv[1:], op)
|
|
if not validate_args(op, args):
|
|
exit(1)
|
|
|
|
if not sys.argv[0].endswith('ifquery') and not os.geteuid() == 0:
|
|
print 'error: must be root to run this command'
|
|
exit(1)
|
|
|
|
if not sys.argv[0].endswith('ifquery') and not utils.lockFile(lockfile):
|
|
print 'Another instance of this program is already running.'
|
|
exit(0)
|
|
|
|
read_config(args)
|
|
init(args)
|
|
handlers.get(op)(args)
|
|
except Exception, e:
|
|
if not str(e):
|
|
exit(1)
|
|
if args and args.debug:
|
|
raise
|
|
else:
|
|
if logger:
|
|
logger.error(str(e))
|
|
else:
|
|
print str(e)
|
|
#if args and not args.debug:
|
|
# print '\nrerun the command with \'-d\' for a detailed errormsg'
|
|
exit(1)
|
|
finally:
|
|
deinit()
|
|
|
|
if __name__ == "__main__":
|
|
|
|
# required during boot
|
|
os.putenv('PATH', ENVPATH)
|
|
resource.setrlimit(resource.RLIMIT_CORE, (0,0))
|
|
main(sys.argv)
|