]>
git.proxmox.com Git - mirror_ifupdown2.git/blob - ifupdown2/ifupdown/networkinterfaces.py
3 # Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved.
4 # Author: Roopa Prabhu, roopa@cumulusnetworks.com
7 # ifupdown network interfaces file parser
18 from ifupdown2
.ifupdown
.iface
import *
19 from ifupdown2
.ifupdown
.utils
import utils
20 from ifupdown2
.ifupdown
.template
import templateEngine
21 except (ImportError, ModuleNotFoundError
):
22 from ifupdown
.iface
import *
23 from ifupdown
.utils
import utils
24 from ifupdown
.template
import templateEngine
27 whitespaces
= '\n\t\r '
29 class networkInterfaces():
30 """ debian ifupdown /etc/network/interfaces file parser """
32 _addrfams
= {'inet' : ['static', 'manual', 'loopback', 'dhcp', 'dhcp6', 'ppp', 'tunnel'],
33 'inet6' : ['static', 'manual', 'loopback', 'dhcp', 'dhcp6', 'ppp', 'tunnel']}
34 # tunnel is part of the address family for backward compatibility but is not required.
36 def __init__(self
, interfacesfile
='/etc/network/interfaces',
37 interfacesfileiobuf
=None, interfacesfileformat
='native',
38 template_enable
='0', template_engine
=None,
39 template_lookuppath
=None, raw
=False):
40 """This member function initializes the networkinterfaces parser object.
43 **interfacesfile** (str): path to the interfaces file (default is /etc/network/interfaces)
45 **interfacesfileiobuf** (object): interfaces file io stream
47 **interfacesfileformat** (str): format of interfaces file (choices are 'native' and 'json'. 'native' being the default)
49 **template_engine** (str): template engine name
51 **template_lookuppath** (str): template lookup path
54 AttributeError, KeyError """
60 self
.logger
= logging
.getLogger('ifupdown.' +
61 self
.__class
__.__name
__)
62 self
.callbacks
= {'iface_found' : None,
63 'validateifaceattr' : None,
64 'validateifaceobj' : None}
65 self
.allow_classes
= {}
66 self
.interfacesfile
= interfacesfile
67 self
.interfacesfileiobuf
= interfacesfileiobuf
68 self
.interfacesfileformat
= interfacesfileformat
69 self
._filestack
= [self
.interfacesfile
]
71 self
._template
_engine
= None
72 self
._template
_enable
= template_enable
73 self
._template
_engine
_name
= template_engine
74 self
._template
_engine
_path
= template_lookuppath
76 self
._currentfile
_has
_template
= False
77 self
._ws
_split
_regex
= re
.compile(r
'[\s\t]\s*')
83 def _currentfile(self
):
85 return self
._filestack
[-1]
87 return self
.interfacesfile
89 def _parse_error(self
, filename
, lineno
, msg
):
90 if lineno
== -1 or self
._currentfile
_has
_template
:
91 self
.logger
.error('%s: %s' %(filename
, msg
))
93 self
.logger
.error('%s: line%d: %s' %(filename
, lineno
, msg
))
96 def _parse_warn(self
, filename
, lineno
, msg
):
97 if lineno
== -1 or self
._currentfile
_has
_template
:
98 self
.logger
.warning('%s: %s' %(filename
, msg
))
100 self
.logger
.warning('%s: line%d: %s' %(filename
, lineno
, msg
))
103 def _validate_addr_family(self
, ifaceobj
, lineno
=-1):
104 for family
in ifaceobj
.addr_family
:
105 if not self
._addrfams
.get(family
):
106 self
._parse
_error
(self
._currentfile
, lineno
,
107 'iface %s: unsupported address family \'%s\''
108 % (ifaceobj
.name
, family
))
109 ifaceobj
.addr_family
= []
110 ifaceobj
.addr_method
= None
112 if ifaceobj
.addr_method
:
113 if ifaceobj
.addr_method
not in self
._addrfams
.get(family
):
114 self
._parse
_error
(self
._currentfile
, lineno
,
115 'iface %s: unsupported '
116 'address method \'%s\''
117 % (ifaceobj
.name
, ifaceobj
.addr_method
))
119 ifaceobj
.addr_method
= 'static'
121 def subscribe(self
, callback_name
, callback_func
):
122 """This member function registers callback functions.
125 **callback_name** (str): callback function name (supported names: 'iface_found', 'validateifaceattr', 'validateifaceobj')
127 **callback_func** (function pointer): callback function pointer
132 if callback_name
not in list(self
.callbacks
.keys()):
133 print('warning: invalid callback ' + callback_name
)
136 self
.callbacks
[callback_name
] = callback_func
138 def ignore_line(self
, line
):
139 l
= line
.strip(whitespaces
)
140 if not l
or l
[0] == '#':
144 def process_allow(self
, lines
, cur_idx
, lineno
):
145 allow_line
= lines
[cur_idx
]
147 words
= re
.split(self
._ws
_split
_regex
, allow_line
)
149 raise Exception('invalid allow line \'%s\' at line %d'
150 %(allow_line
, lineno
))
152 allow_class
= words
[0].split('-')[1]
153 ifacenames
= words
[1:]
155 if self
.allow_classes
.get(allow_class
):
157 self
.allow_classes
[allow_class
].append(i
)
159 self
.allow_classes
[allow_class
] = ifacenames
162 def process_source(self
, lines
, cur_idx
, lineno
):
164 self
.logger
.debug('processing sourced line ..\'%s\'' % lines
[cur_idx
])
165 sourced_file
= re
.split(self
._ws
_split
_regex
, lines
[cur_idx
], 2)[1]
168 if not os
.path
.isabs(sourced_file
):
169 sourced_file
= os
.path
.join(os
.path
.dirname(self
._currentfile
), sourced_file
)
171 filenames
= sorted(glob
.glob(sourced_file
))
173 if '*' not in sourced_file
:
174 self
._parse
_warn
(self
._currentfile
, lineno
,
175 'cannot find source file %s' %sourced_file
)
180 self
._parse
_error
(self
._currentfile
, lineno
,
181 'unable to read source line')
184 def process_source_directory(self
, lines
, cur_idx
, lineno
):
185 self
.logger
.debug('processing source-directory line ..\'%s\'' % lines
[cur_idx
])
186 sourced_directory
= re
.split(self
._ws
_split
_regex
, lines
[cur_idx
], 2)[1]
188 if sourced_directory
:
189 if not os
.path
.isabs(sourced_directory
):
190 sourced_directory
= os
.path
.join(os
.path
.dirname(self
._currentfile
), sourced_directory
)
192 folders
= glob
.glob(sourced_directory
)
193 for folder
in folders
:
194 for file in os
.listdir(folder
):
195 self
.read_file(os
.path
.join(folder
, file))
197 self
._parse
_error
(self
._currentfile
, lineno
,
198 'unable to read source-directory line')
201 def process_auto(self
, lines
, cur_idx
, lineno
):
202 auto_ifaces
= re
.split(self
._ws
_split
_regex
, lines
[cur_idx
])[1:]
204 self
._parse
_error
(self
._currentfile
, lineno
,
205 'invalid auto line \'%s\''%lines
[cur_idx
])
207 for a
in auto_ifaces
:
211 r
= utils
.parse_iface_range(a
)
214 # eg swp1.[2-4], r = "swp1.", 2, 4)
215 for i
in range(r
[1], r
[2]+1):
216 self
.auto_ifaces
.append('%s%d' %(r
[0], i
))
218 for i
in range(r
[1], r
[2]+1):
219 # eg swp[2-4].100, r = ("swp", 2, 4, ".100")
220 self
.auto_ifaces
.append('%s%d%s' %(r
[0], i
, r
[3]))
221 self
.auto_ifaces
.append(a
)
224 def _add_to_iface_config(self
, ifacename
, iface_config
, attrname
,
226 newattrname
= attrname
.replace("_", "-")
228 if not self
.callbacks
.get('validateifaceattr')(newattrname
,
230 self
._parse
_error
(self
._currentfile
, lineno
,
231 'iface %s: unsupported keyword (%s)'
232 %(ifacename
, attrname
))
236 attrvallist
= iface_config
.get(newattrname
, [])
237 if newattrname
in ['scope', 'netmask', 'broadcast', 'preferred-lifetime']:
238 # For attributes that are related and that can have multiple
239 # entries, store them at the same index as their parent attribute.
240 # The example of such attributes is 'address' and its related
241 # attributes. since the related attributes can be optional,
242 # we add null string '' in places where they are optional.
243 # XXX: this introduces awareness of attribute names in
244 # this class which is a violation.
246 # get the index corresponding to the 'address'
247 addrlist
= iface_config
.get('address')
249 # find the index of last address element
250 for i
in range(0, len(addrlist
) - len(attrvallist
) -1):
251 attrvallist
.append('')
252 attrvallist
.append(attrval
)
253 iface_config
[newattrname
] = attrvallist
254 elif not attrvallist
:
255 iface_config
[newattrname
] = [attrval
]
257 iface_config
[newattrname
].append(attrval
)
259 def parse_iface(self
, lines
, cur_idx
, lineno
, ifaceobj
):
263 iface_line
= lines
[cur_idx
].strip(whitespaces
)
264 iface_attrs
= re
.split(self
._ws
_split
_regex
, iface_line
)
265 ifacename
= iface_attrs
[1]
267 if (not utils
.is_ifname_range(ifacename
) and
268 utils
.check_ifname_size_invalid(ifacename
)):
269 self
._parse
_warn
(self
._currentfile
, lineno
,
270 '%s: interface name too long' %ifacename
)
272 # in cases where mako is unable to render the template
273 # or incorrectly renders it due to user template
274 # errors, we maybe left with interface names with
275 # mako variables in them. There is no easy way to
276 # recognize and warn about these. In the below check
277 # we try to warn the user of such cases by looking for
278 # variable patterns ('$') in interface names.
280 self
._parse
_warn
(self
._currentfile
, lineno
,
281 '%s: unexpected characters in interface name' %ifacename
)
284 ifaceobj
.raw_config
.append(iface_line
)
285 iface_config
= collections
.OrderedDict()
286 for line_idx
in range(cur_idx
+ 1, len(lines
)):
287 l
= lines
[line_idx
].strip(whitespaces
)
288 if self
.ignore_line(l
) == 1:
290 ifaceobj
.raw_config
.append(l
)
292 attrs
= re
.split(self
._ws
_split
_regex
, l
, 1)
293 if self
._is
_keyword
(attrs
[0]):
296 # if not a keyword, every line must have at least a key and value
298 self
._parse
_error
(self
._currentfile
, line_idx
,
299 'iface %s: invalid syntax \'%s\'' %(ifacename
, l
))
302 ifaceobj
.raw_config
.append(l
)
304 # preprocess vars (XXX: only preprocesses $IFACE for now)
305 attrval
= re
.sub(r
'\$IFACE', ifacename
, attrs
[1])
306 self
._add
_to
_iface
_config
(ifacename
, iface_config
, attrname
,
308 lines_consumed
= line_idx
- cur_idx
310 # Create iface object
311 if ifacename
.find(':') != -1:
312 ifaceobj
.name
= ifacename
.split(':')[0]
314 ifaceobj
.name
= ifacename
316 ifaceobj
.config
= iface_config
317 ifaceobj
.generate_env()
321 ifaceobj
.addr_family
.append(iface_attrs
[2])
322 ifaceobj
.addr_method
= iface_attrs
[3]
326 self
._validate
_addr
_family
(ifaceobj
, lineno
)
328 if self
.auto_all
or (ifaceobj
.name
in self
.auto_ifaces
):
331 classes
= self
.get_allow_classes_for_iface(ifaceobj
.name
)
333 [ifaceobj
.set_class(c
) for c
in classes
]
335 return lines_consumed
# Return next index
337 def _create_ifaceobj_clone(self
, ifaceobj
, newifaceobjname
,
338 newifaceobjtype
, newifaceobjflags
):
339 ifaceobj_new
= copy
.deepcopy(ifaceobj
)
340 ifaceobj_new
.realname
= '%s' %ifaceobj
.name
341 ifaceobj_new
.name
= newifaceobjname
342 ifaceobj_new
.type = newifaceobjtype
343 ifaceobj_new
.flags
= newifaceobjflags
347 def process_iface(self
, lines
, cur_idx
, lineno
):
349 lines_consumed
= self
.parse_iface(lines
, cur_idx
, lineno
, ifaceobj
)
351 range_val
= utils
.parse_iface_range(ifaceobj
.name
)
353 if len(range_val
) == 3:
354 for v
in range(range_val
[1], range_val
[2]+1):
355 ifacename
= '%s%d' %(range_val
[0], v
)
356 if utils
.check_ifname_size_invalid(ifacename
):
357 self
._parse
_warn
(self
._currentfile
, lineno
,
358 '%s: interface name too long' %ifacename
)
359 flags
= iface
.IFACERANGE_ENTRY
360 if v
== range_val
[1]:
361 flags |
= iface
.IFACERANGE_START
362 ifaceobj_new
= self
._create
_ifaceobj
_clone
(ifaceobj
,
363 ifacename
, ifaceobj
.type, flags
)
364 self
.callbacks
.get('iface_found')(ifaceobj_new
)
365 elif len(range_val
) == 4:
366 for v
in range(range_val
[1], range_val
[2]+1):
367 ifacename
= '%s%d%s' %(range_val
[0], v
, range_val
[3])
368 if utils
.check_ifname_size_invalid(ifacename
):
369 self
._parse
_warn
(self
._currentfile
, lineno
,
370 '%s: interface name too long' %ifacename
)
371 flags
= iface
.IFACERANGE_ENTRY
372 if v
== range_val
[1]:
373 flags |
= iface
.IFACERANGE_START
374 ifaceobj_new
= self
._create
_ifaceobj
_clone
(ifaceobj
,
375 ifacename
, ifaceobj
.type, flags
)
376 self
.callbacks
.get('iface_found')(ifaceobj_new
)
378 self
.callbacks
.get('iface_found')(ifaceobj
)
380 return lines_consumed
# Return next index
382 def process_vlan(self
, lines
, cur_idx
, lineno
):
384 lines_consumed
= self
.parse_iface(lines
, cur_idx
, lineno
, ifaceobj
)
386 range_val
= utils
.parse_iface_range(ifaceobj
.name
)
388 if len(range_val
) == 3:
389 for v
in range(range_val
[1], range_val
[2]+1):
390 flags
= iface
.IFACERANGE_ENTRY
391 if v
== range_val
[1]:
392 flags |
= iface
.IFACERANGE_START
393 ifaceobj_new
= self
._create
_ifaceobj
_clone
(ifaceobj
,
394 '%s%d' %(range_val
[0], v
),
395 ifaceType
.BRIDGE_VLAN
, flags
)
396 self
.callbacks
.get('iface_found')(ifaceobj_new
)
397 elif len(range_val
) == 4:
398 for v
in range(range_val
[1], range_val
[2]+1):
399 flags
= iface
.IFACERANGE_ENTRY
400 if v
== range_val
[1]:
401 flags |
= iface
.IFACERANGE_START
402 ifaceobj_new
= self
._create
_ifaceobj
_clone
(ifaceobj
,
403 '%s%d%s' %(range_val
[0], v
, range_val
[3]),
404 ifaceType
.BRIDGE_VLAN
,
406 self
.callbacks
.get('iface_found')(ifaceobj_new
)
408 ifaceobj
.type = ifaceType
.BRIDGE_VLAN
409 self
.callbacks
.get('iface_found')(ifaceobj
)
411 return lines_consumed
# Return next index
414 'source': process_source
,
415 'source-directory': process_source_directory
,
416 'allow': process_allow
,
417 'auto': process_auto
,
418 'iface': process_iface
,
422 def _is_keyword(self
, str):
423 # The additional split here is for allow- keyword
424 if (str in list(self
.network_elems
.keys()) or
425 str.split('-')[0] == 'allow'):
429 def _get_keyword_func(self
, str_
):
430 tmp_str
= str_
.split('-')[0]
431 if tmp_str
== "allow":
432 return self
.network_elems
.get(tmp_str
)
434 return self
.network_elems
.get(str_
)
436 def get_allow_classes_for_iface(self
, ifacename
):
438 for class_name
, ifacenames
in list(self
.allow_classes
.items()):
439 if ifacename
in ifacenames
:
440 classes
.append(class_name
)
443 def process_interfaces(self
, filedata
):
445 # process line continuations
446 filedata
= ' '.join(d
.strip() for d
in filedata
.split('\\'))
450 raw_config
= filedata
.split('\n')
451 lines
= [l
.strip(whitespaces
) for l
in raw_config
]
452 while (line_idx
< len(lines
)):
453 if self
.ignore_line(lines
[line_idx
]):
456 words
= re
.split(self
._ws
_split
_regex
, lines
[line_idx
])
460 # Check if first element is a supported keyword
461 if self
._is
_keyword
(words
[0]):
462 keyword_func
= self
._get
_keyword
_func
(words
[0])
463 lines_consumed
= keyword_func(self
, lines
, line_idx
, line_idx
+1)
464 line_idx
+= lines_consumed
466 self
._parse
_error
(self
._currentfile
, line_idx
+ 1,
467 'error processing line \'%s\'' %lines
[line_idx
])
471 def read_filedata(self
, filedata
):
472 self
._currentfile
_has
_template
= False
473 # run through template engine
474 if filedata
and '%' in filedata
:
476 if not self
._template
_engine
:
477 self
._template
_engine
= templateEngine(
478 template_engine
=self
._template
_engine
_name
,
479 template_enable
=self
._template
_enable
,
480 template_lookuppath
=self
._template
_engine
_path
)
481 rendered_filedata
= self
._template
_engine
.render(filedata
)
482 if rendered_filedata
is filedata
:
483 self
._currentfile
_has
_template
= False
485 self
._currentfile
_has
_template
= True
486 except Exception as e
:
487 self
._parse
_error
(self
._currentfile
, -1,
488 'failed to render template (%s). Continue without template rendering ...'
490 rendered_filedata
= None
491 if rendered_filedata
:
493 if isinstance(rendered_filedata
, bytes
):
494 # some template engine might return bytes but we want str
495 rendered_filedata
= rendered_filedata
.decode()
497 self
.process_interfaces(rendered_filedata
)
499 self
.process_interfaces(filedata
)
501 def read_file(self
, filename
, fileiobuf
=None):
503 self
.read_filedata(fileiobuf
)
505 self
._filestack
.append(filename
)
506 self
.logger
.info('processing interfaces file %s' %filename
)
508 with
open(filename
) as f
:
510 except Exception as e
:
511 self
.logger
.warning('error processing file %s (%s)',
514 self
.read_filedata(filedata
)
515 self
._filestack
.pop()
517 def read_file_json(self
, filename
, fileiobuf
=None):
519 ifacedicts
= json
.loads(fileiobuf
, encoding
="utf-8")
520 #object_hook=ifaceJsonDecoder.json_object_hook)
522 self
.logger
.info('processing interfaces file %s' %filename
)
523 with
open(filename
) as fp
:
524 ifacedicts
= json
.load(fp
)
525 #object_hook=ifaceJsonDecoder.json_object_hook)
527 # we need to handle both lists and non lists formats (e.g. {{}})
528 if not isinstance(ifacedicts
,list):
529 ifacedicts
= [ifacedicts
]
532 for ifacedict
in ifacedicts
:
533 ifaceobj
= ifaceJsonDecoder
.json_to_ifaceobj(ifacedict
)
535 self
._validate
_addr
_family
(ifaceobj
)
536 if not self
.callbacks
.get('validateifaceobj')(ifaceobj
):
538 self
.callbacks
.get('iface_found')(ifaceobj
)
539 self
.errors
+= errors
542 """ This member function loads the networkinterfaces file.
544 Assumes networkinterfaces parser object is initialized with the
547 if not self
.interfacesfile
and not self
.interfacesfileiobuf
:
548 self
.logger
.warning('no terminal line stdin used or ')
549 self
.logger
.warning('no network interfaces file defined.')
553 if self
.interfacesfileformat
== 'json':
554 return self
.read_file_json(self
.interfacesfile
,
555 self
.interfacesfileiobuf
)
556 return self
.read_file(self
.interfacesfile
,
557 self
.interfacesfileiobuf
)