2013-11-04 06:06:11 -08:00
|
|
|
#!/usr/bin/python
|
2013-11-13 16:07:15 -08:00
|
|
|
#
|
|
|
|
# Copyright 2013. Cumulus Networks, Inc.
|
|
|
|
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
|
|
|
|
#
|
|
|
|
# networkInterfaces --
|
|
|
|
# ifupdown network interfaces file parser
|
|
|
|
#
|
2013-11-04 06:06:11 -08:00
|
|
|
|
|
|
|
import collections
|
|
|
|
import logging
|
|
|
|
import glob
|
2014-01-16 06:46:17 -08:00
|
|
|
import re
|
2013-11-13 16:07:15 -08:00
|
|
|
from iface import *
|
2013-11-04 06:06:11 -08:00
|
|
|
|
|
|
|
class networkInterfaces():
|
|
|
|
|
|
|
|
hotplugs = {}
|
|
|
|
auto_ifaces = []
|
|
|
|
callbacks = {}
|
|
|
|
|
|
|
|
ifaces_file = "/etc/network/interfaces"
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
self.logger = logging.getLogger('ifupdown.' +
|
|
|
|
self.__class__.__name__)
|
2014-02-08 09:05:32 -08:00
|
|
|
self.callbacks = {'iface_found' : None,
|
|
|
|
'validate' : None}
|
2013-11-04 06:06:11 -08:00
|
|
|
self.allow_classes = {}
|
|
|
|
|
|
|
|
|
|
|
|
def subscribe(self, callback_name, callback_func):
|
|
|
|
if callback_name not in self.callbacks.keys():
|
|
|
|
print 'warning: invalid callback ' + callback_name
|
|
|
|
return -1
|
|
|
|
|
|
|
|
self.callbacks[callback_name] = callback_func
|
|
|
|
|
|
|
|
|
|
|
|
def ignore_line(self, line):
|
|
|
|
l = line.strip('\n ')
|
2014-02-19 21:30:55 -08:00
|
|
|
if not l or l[0] == '#':
|
2013-11-04 06:06:11 -08:00
|
|
|
return 1
|
|
|
|
return 0
|
|
|
|
|
|
|
|
def process_allow(self, lines, cur_idx, lineno):
|
2013-11-13 16:07:15 -08:00
|
|
|
allow_line = lines[cur_idx]
|
2013-11-04 06:06:11 -08:00
|
|
|
|
|
|
|
words = allow_line.split()
|
|
|
|
if len(words) <= 1:
|
2014-02-17 19:01:37 -08:00
|
|
|
raise Exception('invalid allow line \'%s\' at line %d'
|
|
|
|
%(allow_line, lineno))
|
2013-11-04 06:06:11 -08:00
|
|
|
|
|
|
|
allow_class = words[0].split('-')[1]
|
|
|
|
ifacenames = words[1:]
|
|
|
|
|
2014-03-22 22:16:53 -07:00
|
|
|
if self.allow_classes.get(allow_class):
|
2013-11-04 06:06:11 -08:00
|
|
|
for i in ifacenames:
|
|
|
|
self.allow_classes[allow_class].append(i)
|
|
|
|
else:
|
|
|
|
self.allow_classes[allow_class] = ifacenames
|
|
|
|
return 0
|
|
|
|
|
|
|
|
def process_source(self, lines, cur_idx, lineno):
|
|
|
|
# Support regex
|
2014-01-30 22:36:41 -08:00
|
|
|
self.logger.debug('processing sourced line ..\'%s\'' %lines[cur_idx])
|
2013-11-04 06:06:11 -08:00
|
|
|
sourced_file = lines[cur_idx].split(' ', 2)[1]
|
2014-03-22 22:16:53 -07:00
|
|
|
if sourced_file:
|
2013-11-04 06:06:11 -08:00
|
|
|
for f in glob.glob(sourced_file):
|
2013-11-10 22:35:40 -08:00
|
|
|
self.read_file(f)
|
2013-11-04 06:06:11 -08:00
|
|
|
else:
|
|
|
|
self.logger.warn('unable to read source line at %d', lineno)
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
def process_auto(self, lines, cur_idx, lineno):
|
2014-02-17 19:01:37 -08:00
|
|
|
for a in lines[cur_idx].split()[1:]:
|
|
|
|
self.auto_ifaces.append(a)
|
2013-11-04 06:06:11 -08:00
|
|
|
return 0
|
|
|
|
|
2014-02-17 19:01:37 -08:00
|
|
|
def _add_to_iface_config(self, iface_config, attrname, attrval):
|
|
|
|
attrvallist = iface_config.get(attrname, [])
|
|
|
|
if attrname in ['scope', 'netmask', 'broadcast', 'preferred-lifetime']:
|
|
|
|
# For attributes that are related and that can have multiple
|
|
|
|
# entries, store them at the same index as their parent attribute.
|
|
|
|
# The example of such attributes is 'address' and its related
|
|
|
|
# attributes. since the related attributes can be optional,
|
|
|
|
# we add null string '' in places where they are optional.
|
|
|
|
# XXX: this introduces awareness of attribute names in
|
|
|
|
# this class which is a violation.
|
|
|
|
|
|
|
|
# get the index corresponding to the 'address'
|
|
|
|
addrlist = iface_config.get('address')
|
|
|
|
if addrlist:
|
|
|
|
# find the index of last address element
|
|
|
|
for i in range(0, len(addrlist) - len(attrvallist) -1):
|
|
|
|
attrvallist.append('')
|
|
|
|
attrvallist.append(attrval)
|
|
|
|
iface_config[attrname] = attrvallist
|
|
|
|
elif not attrvallist:
|
|
|
|
iface_config[attrname] = [attrval]
|
|
|
|
else:
|
|
|
|
iface_config[attrname].append(attrval)
|
2013-11-04 06:06:11 -08:00
|
|
|
|
|
|
|
def process_iface(self, lines, cur_idx, lineno):
|
|
|
|
lines_consumed = 0
|
2013-12-20 13:59:37 -08:00
|
|
|
line_idx = cur_idx
|
2013-11-04 06:06:11 -08:00
|
|
|
|
|
|
|
ifaceobj = iface()
|
|
|
|
|
|
|
|
iface_line = lines[cur_idx].strip('\n ')
|
|
|
|
iface_attrs = iface_line.split()
|
2014-01-16 06:46:17 -08:00
|
|
|
ifacename = iface_attrs[1]
|
2013-11-04 06:06:11 -08:00
|
|
|
|
2014-03-22 22:16:53 -07:00
|
|
|
ifaceobj.raw_config.append(iface_line)
|
2013-11-04 06:06:11 -08:00
|
|
|
|
|
|
|
iface_config = collections.OrderedDict()
|
|
|
|
for line_idx in range(cur_idx + 1, len(lines)):
|
|
|
|
l = lines[line_idx].strip('\n\t ')
|
|
|
|
|
|
|
|
if self.ignore_line(l) == 1:
|
|
|
|
continue
|
|
|
|
|
|
|
|
if self.is_keyword(l.split()[0]) == True:
|
|
|
|
line_idx -= 1
|
|
|
|
break
|
|
|
|
|
2014-03-22 22:16:53 -07:00
|
|
|
ifaceobj.raw_config.append(l)
|
2013-11-10 22:35:40 -08:00
|
|
|
|
2014-01-16 06:46:17 -08:00
|
|
|
# preprocess vars (XXX: only preprocesses $IFACE for now)
|
|
|
|
l = re.sub(r'\$IFACE', ifacename, l)
|
|
|
|
|
2013-11-10 22:35:40 -08:00
|
|
|
attrs = l.split(' ', 1)
|
|
|
|
if len(attrs) < 2:
|
2014-02-17 19:01:37 -08:00
|
|
|
self.logger.warn('%s: invalid syntax at line %d'
|
|
|
|
%(ifacename, line_idx + 1))
|
2013-11-10 22:35:40 -08:00
|
|
|
continue
|
2014-02-08 09:05:32 -08:00
|
|
|
attrname = attrs[0]
|
|
|
|
attrval = attrs[1].strip(' ')
|
|
|
|
try:
|
|
|
|
if not self.callbacks.get('validate')(attrname, attrval):
|
|
|
|
self.logger.warn('unsupported keyword (%s) at line %d'
|
|
|
|
%(l, line_idx + 1))
|
|
|
|
except:
|
|
|
|
pass
|
2014-02-17 19:01:37 -08:00
|
|
|
self._add_to_iface_config(iface_config, attrname, attrval)
|
2013-11-04 06:06:11 -08:00
|
|
|
|
|
|
|
lines_consumed = line_idx - cur_idx
|
|
|
|
|
|
|
|
# Create iface object
|
2014-01-30 22:36:41 -08:00
|
|
|
if ifacename.find(':') != -1:
|
2014-03-22 22:16:53 -07:00
|
|
|
ifaceobj.name = ifacename.split(':')[0]
|
2014-01-30 22:36:41 -08:00
|
|
|
else:
|
2014-03-22 22:16:53 -07:00
|
|
|
ifaceobj.name = ifacename
|
2014-01-30 22:36:41 -08:00
|
|
|
|
2014-03-22 22:16:53 -07:00
|
|
|
ifaceobj.config = iface_config
|
2013-11-04 06:06:11 -08:00
|
|
|
ifaceobj.generate_env()
|
|
|
|
if len(iface_attrs) > 2:
|
2014-03-22 22:16:53 -07:00
|
|
|
ifaceobj.addr_family = iface_attrs[2]
|
|
|
|
ifaceobj.addr_method = iface_attrs[3]
|
2013-11-04 06:06:11 -08:00
|
|
|
|
2014-03-22 22:16:53 -07:00
|
|
|
if ifaceobj.name in self.auto_ifaces:
|
|
|
|
ifaceobj.auto = True
|
2013-11-04 06:06:11 -08:00
|
|
|
|
2014-03-22 22:16:53 -07:00
|
|
|
classes = self.get_allow_classes_for_iface(ifaceobj.name)
|
2014-02-19 21:30:55 -08:00
|
|
|
if classes:
|
2014-03-22 22:16:53 -07:00
|
|
|
[ifaceobj.set_class(c) for c in classes]
|
2013-11-04 06:06:11 -08:00
|
|
|
|
|
|
|
# Call iface found callback
|
|
|
|
self.callbacks.get('iface_found')(ifaceobj)
|
|
|
|
|
|
|
|
return lines_consumed # Return next index
|
|
|
|
|
|
|
|
|
|
|
|
network_elems = { 'source' : process_source,
|
|
|
|
'allow' : process_allow,
|
|
|
|
'auto' : process_auto,
|
|
|
|
'iface' : process_iface}
|
|
|
|
|
|
|
|
def is_keyword(self, str):
|
|
|
|
|
|
|
|
# The additional split here is for allow- keyword
|
|
|
|
tmp_str = str.split('-')[0]
|
|
|
|
if tmp_str in self.network_elems.keys():
|
|
|
|
return 1
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
def get_keyword_func(self, str):
|
|
|
|
tmp_str = str.split('-')[0]
|
|
|
|
|
|
|
|
return self.network_elems.get(tmp_str)
|
|
|
|
|
|
|
|
def get_allow_classes_for_iface(self, ifacename):
|
|
|
|
classes = []
|
|
|
|
for class_name, ifacenames in self.allow_classes.items():
|
|
|
|
if ifacename in ifacenames:
|
|
|
|
classes.append(class_name)
|
|
|
|
|
|
|
|
return classes
|
|
|
|
|
2013-12-20 13:59:37 -08:00
|
|
|
def process_filedata(self, filedata):
|
2013-11-04 06:06:11 -08:00
|
|
|
lineno = 0
|
|
|
|
line_idx = 0
|
|
|
|
lines_consumed = 0
|
|
|
|
|
2014-03-22 22:16:53 -07:00
|
|
|
raw_config = filedata.split('\n')
|
|
|
|
lines = [l.strip(' \n') for l in raw_config]
|
2013-11-04 06:06:11 -08:00
|
|
|
|
2013-12-20 13:59:37 -08:00
|
|
|
while (line_idx < len(lines)):
|
|
|
|
lineno = lineno + 1
|
2013-11-04 06:06:11 -08:00
|
|
|
|
2013-12-20 13:59:37 -08:00
|
|
|
if self.ignore_line(lines[line_idx]):
|
|
|
|
line_idx += 1
|
|
|
|
continue
|
|
|
|
|
|
|
|
words = lines[line_idx].split()
|
2013-11-04 06:06:11 -08:00
|
|
|
|
2013-12-20 13:59:37 -08:00
|
|
|
# Check if first element is a supported keyword
|
|
|
|
if self.is_keyword(words[0]):
|
|
|
|
keyword_func = self.get_keyword_func(words[0])
|
|
|
|
lines_consumed = keyword_func(self, lines, line_idx, lineno)
|
|
|
|
line_idx += lines_consumed
|
|
|
|
else:
|
|
|
|
self.logger.warning('could not process line %s' %l + ' at' +
|
|
|
|
' lineno %d' %lineno)
|
2013-11-04 06:06:11 -08:00
|
|
|
|
2013-12-20 13:59:37 -08:00
|
|
|
line_idx += 1
|
2013-11-04 06:06:11 -08:00
|
|
|
|
2013-12-20 13:59:37 -08:00
|
|
|
return 0
|
2013-11-04 06:06:11 -08:00
|
|
|
|
2013-12-20 13:59:37 -08:00
|
|
|
def run_template_engine(self, textdata):
|
|
|
|
try:
|
|
|
|
from mako.template import Template
|
|
|
|
except:
|
2014-01-30 22:36:41 -08:00
|
|
|
self.logger.warning('template engine mako not found. ' +
|
|
|
|
'skip template parsing ..');
|
2013-12-20 13:59:37 -08:00
|
|
|
return textdata
|
2013-11-04 06:06:11 -08:00
|
|
|
|
2013-12-20 13:59:37 -08:00
|
|
|
t = Template(text=textdata, output_encoding='utf-8')
|
|
|
|
return t.render()
|
2013-11-04 06:06:11 -08:00
|
|
|
|
2013-12-20 13:59:37 -08:00
|
|
|
def read_file(self, filename=None):
|
|
|
|
ifaces_file = filename
|
2014-02-19 21:30:55 -08:00
|
|
|
if not ifaces_file:
|
2013-12-20 13:59:37 -08:00
|
|
|
ifaces_file=self.ifaces_file
|
|
|
|
|
2014-01-30 22:36:41 -08:00
|
|
|
self.logger.debug('reading interfaces file %s' %ifaces_file)
|
2013-12-20 13:59:37 -08:00
|
|
|
f = open(ifaces_file)
|
|
|
|
filedata = f.read()
|
|
|
|
f.close()
|
|
|
|
|
|
|
|
# process line continuations
|
|
|
|
filedata = ' '.join(d.strip() for d in filedata.split('\\'))
|
|
|
|
|
|
|
|
# run through template engine
|
|
|
|
filedata = self.run_template_engine(filedata)
|
|
|
|
self.process_filedata(filedata)
|
2013-11-04 06:06:11 -08:00
|
|
|
|
|
|
|
def load(self, filename=None):
|
|
|
|
return self.read_file(filename)
|