summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorChristian Poessinger <christian@poessinger.com>2019-08-31 13:00:40 +0200
committerChristian Poessinger <christian@poessinger.com>2019-08-31 13:00:40 +0200
commit56933983373634d8c56daeca7abd47feb7a5c791 (patch)
tree55c78971a50701b142d939fa80b7780cec159947 /python
parent3b119c91ca70c51aab24d4ef8b3913f47281321a (diff)
downloadvyos-1x-56933983373634d8c56daeca7abd47feb7a5c791.tar.gz
vyos-1x-56933983373634d8c56daeca7abd47feb7a5c791.zip
Python/ifconfig: T1557: fix DHCP/DHCPv6 daemon and add Bridge/Dummy interface
Diffstat (limited to 'python')
-rw-r--r--python/vyos/ifconfig.py843
1 files changed, 673 insertions, 170 deletions
diff --git a/python/vyos/ifconfig.py b/python/vyos/ifconfig.py
index d9f28c8ef..0cd27592a 100644
--- a/python/vyos/ifconfig.py
+++ b/python/vyos/ifconfig.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python3
-
# Copyright 2019 VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
@@ -19,44 +17,78 @@ import sys
import os
import subprocess
import ipaddress
+import jinja2
from vyos.validate import *
from ipaddress import IPv4Network, IPv6Address
from netifaces import ifaddresses, AF_INET, AF_INET6
-
-dhclient_conf_dir = r'/var/lib/dhcp/dhclient_'
+from time import sleep
+
+dhcp_cfg = """
+# generated by ifconfig.py
+option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;
+interface "{{ intf }}" {
+ send host-name "{{ hostname }}";
+ request subnet-mask, broadcast-address, routers, domain-name-servers, rfc3442-classless-static-routes, domain-name, interface-mtu;
+}
+"""
+
+dhcpv6_cfg = """
+# generated by ifconfig.py
+interface "{{ intf }}" {
+ request routers, domain-name-servers, domain-name;
+}
+"""
+
+dhclient_base = r'/var/lib/dhcp/dhclient_'
class Interface:
- def __init__(self, ifname=None, type=None):
+ def __init__(self, ifname=None, type=None, debug=False):
"""
Create instance of an IP interface
Example:
>>> from vyos.ifconfig import Interface
- >>> i = Interface('br111', type='bridge')
+ >>> i = Interface('eth0')
"""
if not ifname:
- raise Exception("interface name required")
-
- if not os.path.exists('/sys/class/net/{0}'.format(ifname)) and not type:
- raise Exception("interface {0} not found".format(str(ifname)))
-
- if not os.path.exists('/sys/class/net/{0}'.format(ifname)):
- try:
- cmd = 'ip link add dev "{}" type "{}"'.format(ifname, type)
- self._cmd(cmd)
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
- if "Operation not supported" in str(e.output.decode()):
- print(str(e.output.decode()))
- sys.exit(0)
+ raise Exception('interface name required')
+
+ if not os.path.exists('/sys/class/net/{}'.format(ifname)) and not type:
+ raise Exception('interface "{}" not found'.format(str(ifname)))
+ # variable already referenced from _debug()
+ self._debug = debug
self._ifname = str(ifname)
- @property
+ if not os.path.exists('/sys/class/net/{}'.format(ifname)):
+ cmd = 'ip link add dev "{}" type "{}"'.format(ifname, type)
+ self._cmd(cmd)
+
+ # per interface DHCP config files
+ self._dhcp_cfg_file = dhclient_base + self._ifname + '.conf'
+ self._dhcp_pid_file = dhclient_base + self._ifname + '.pid'
+ self._dhcp_lease_file = dhclient_base + self._ifname + '.leases'
+
+ # per interface DHCPv6 config files
+ self._dhcpv6_cfg_file = dhclient_base + self._ifname + '.v6conf'
+ self._dhcpv6_pid_file = dhclient_base + self._ifname + '.v6pid'
+ self._dhcpv6_lease_file = dhclient_base + self._ifname + '.v6leases'
+
+
+ def _debug_msg(self, msg):
+ if self._debug:
+ print('"DEBUG/{}: {}'.format(self._ifname, msg))
+
+
+ def set_debug(self, debug):
+ if debug not in [True, False]:
+ raise ValueError('must specify True or False for debug')
+ self._debug = debug
+
+
def remove(self):
"""
Remove system interface
@@ -64,21 +96,28 @@ class Interface:
Example:
>>> from vyos.ifconfig import Interface
- >>> i = Interface('br111', type='bridge')
- >>> i.remove
+ >>> i = Interface('eth0')
+ >>> i.remove()
"""
+ # stop DHCP(v6) if running
+ self.del_dhcp()
+ self.del_dhcpv6()
+
# NOTE (Improvement):
# after interface removal no other commands should be allowed
# to be called and instead should raise an Exception:
-
cmd = 'ip link del dev "{}"'.format(self._ifname)
self._cmd(cmd)
def _cmd(self, command):
+ self._debug_msg(command)
+
process = subprocess.Popen(command,stdout=subprocess.PIPE, shell=True)
proc_stdout = process.communicate()[0].strip()
+
+ # add exception handling code
pass
@@ -90,7 +129,7 @@ class Interface:
Example:
>>> from vyos.ifconfig import Interface
- >>> Interface('eth1').mtu
+ >>> Interface('eth0').mtu
'1500'
"""
@@ -108,8 +147,8 @@ class Interface:
Example:
>>> from vyos.ifconfig import Interface
- >>> Interface('br100', type='bridge').mtu = 1400
- >>> Interface('br100').mtu
+ >>> Interface('eth0').mtu = 1400
+ >>> Interface('eth0').mtu
'1400'
"""
@@ -128,7 +167,7 @@ class Interface:
Example:
>>> from vyos.ifconfig import Interface
- >>> Interface('eth1').mac
+ >>> Interface('eth0').mac
'00:0c:29:11:aa:cc'
"""
address = ''
@@ -145,8 +184,8 @@ class Interface:
Example:
>>> from vyos.ifconfig import Interface
- >>> Interface('eth1').mac = '00:90:43:fe:fe:1b'
- >>> Interface('eth1').mac
+ >>> Interface('eth0').mac = '00:90:43:fe:fe:1b'
+ >>> Interface('eth0').mac
'00:90:43:fe:fe:1b'
"""
# a mac address consits out of 6 octets
@@ -173,6 +212,97 @@ class Interface:
@property
+ def arp_cache_tmo(self):
+ """
+ Get configured ARP cache timeout value from interface. Example shows
+ default value of 30 seconds.
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').arp_cache_tmo
+ '30000'
+ """
+
+ alias = ''
+ with open('/proc/sys/net/ipv4/neigh/{0}/base_reachable_time_ms'.format(self._ifname), 'r') as f:
+ alias = f.read().rstrip('\n')
+ return alias
+
+
+ @arp_cache_tmo.setter
+ def arp_cache_tmo(self, tmo=None):
+ """
+ Set ARP cache timeout value in seconds for this.
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').arp_cache_tmo = '40000'
+ """
+
+ # clear interface alias
+ if not tmo:
+ raise ValueError('Timeout value required')
+
+ # Kernel interface is on milli seconds
+ tmo = int(tmo) * 1000
+ with open('/proc/sys/net/ipv4/neigh/{0}/base_reachable_time_ms'.format(self._ifname), 'w') as f:
+ f.write(str(tmo))
+
+ @property
+ def link_detect(self):
+ """
+ How does the kernel act when receiving packets on 'down' interfaces
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').link_detect
+ '0'
+ """
+
+ alias = ''
+ with open('/proc/sys/net/ipv4/conf/{0}/link_filter'.format(self._ifname), 'r') as f:
+ alias = f.read().rstrip('\n')
+ return alias
+
+
+ @link_detect.setter
+ def link_detect(self, link_filter=None):
+ """
+ Konfigure kernel response in packets received on interfaces that are 'down'
+
+ 0 - Allow packets to be received for the address on this interface
+ even if interface is disabled or no carrier.
+
+ 1 - Ignore packets received if interface associated with the incoming
+ address is down.
+
+ 2 - Ignore packets received if interface associated with the incoming
+ address is down or has no carrier.
+
+ Default value is 0. Note that some distributions enable it in startup
+ scripts.
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').link_detect = '1'
+ """
+
+ # clear interface alias
+ if not link_filter:
+ raise ValueError()
+
+ if link_filter >= 0 and link_filter <= 2:
+ with open('/proc/sys/net/ipv4/conf/{0}/link_filter'.format(self._ifname), 'w') as f:
+ f.write(str(link_filter))
+ else:
+ raise ValueError()
+
+
+ @property
def ifalias(self):
"""
Get/set interface alias name
@@ -180,7 +310,7 @@ class Interface:
Example:
>>> from vyos.ifconfig import Interface
- >>> Interface('eth1').ifalias
+ >>> Interface('eth0').ifalias
''
"""
@@ -198,14 +328,14 @@ class Interface:
Example:
>>> from vyos.ifconfig import Interface
- >>> Interface('eth1').ifalias = 'VyOS upstream interface'
- >>> Interface('eth1').ifalias
+ >>> Interface('eth0').ifalias = 'VyOS upstream interface'
+ >>> Interface('eth0').ifalias
'VyOS upstream interface'
to clear interface alias e.g. delete it use:
- >>> Interface('eth1').ifalias = ''
- >>> Interface('eth1').ifalias
+ >>> Interface('eth0').ifalias = ''
+ >>> Interface('eth0').ifalias
''
"""
@@ -216,6 +346,7 @@ class Interface:
with open('/sys/class/net/{0}/ifalias'.format(self._ifname), 'w') as f:
f.write(str(ifalias))
+
@property
def state(self):
"""
@@ -224,7 +355,7 @@ class Interface:
Example:
>>> from vyos.ifconfig import Interface
- >>> Interface('eth1').state
+ >>> Interface('eth0').state
'up'
"""
@@ -242,8 +373,8 @@ class Interface:
Example:
>>> from vyos.ifconfig import Interface
- >>> Interface('eth1').state = 'down'
- >>> Interface('eth1').state
+ >>> Interface('eth0').state = 'down'
+ >>> Interface('eth0').state
'down'
"""
@@ -256,18 +387,6 @@ class Interface:
self._cmd(cmd)
- def _debug(self, e=None):
- """
- export DEBUG=1 to see debug messages
- """
- if os.getenv('DEBUG') == '1':
- if e:
- print ("Exception raised:\ncommand: {0}\nerror code: {1}\nsubprocess output: {2}".format(
- e.cmd, e.returncode, e.output.decode()))
- return True
- return False
-
-
def get_addr(self):
"""
Retrieve assigned IPv4 and IPv6 addresses from given interface.
@@ -276,7 +395,7 @@ class Interface:
Example:
>>> from vyos.ifconfig import Interface
- >>> Interface('eth1').get_addrs()
+ >>> Interface('eth0').get_addrs()
['172.16.33.30/24', 'fe80::20c:29ff:fe11:a174/64']
"""
@@ -311,8 +430,8 @@ class Interface:
Example:
- >>> from vyos.interfaceconfig import Interface
- >>> j = Interface('br100', type='bridge')
+ >>> from vyos.ifconfig import Interface
+ >>> j = Interface('eth0')
>>> j.add_addr('192.0.2.1/24')
>>> j.add_addr('2001:db8::ffff/64')
>>> j.get_addr()
@@ -332,8 +451,9 @@ class Interface:
Remove IP address from interface.
Example:
- >>> from vyos.interfaceconfig import Interface
- >>> j = Interface('br100', type='bridge')
+
+ >>> from vyos.ifconfig import Interface
+ >>> j = Interface('eth0')
>>> j.add_addr('2001:db8::ffff/64')
>>> j.add_addr('192.0.2.1/24')
>>> j.get_addr()
@@ -352,131 +472,514 @@ class Interface:
# replace dhcpv4/v6 with systemd.networkd?
- def set_dhcpv4(self):
- conf_file = dhclient_conf_dir + self._ifname + '.conf'
- pidfile = dhclient_conf_dir + self._ifname + '.pid'
- leasefile = dhclient_conf_dir + self._ifname + '.leases'
+ def set_dhcp(self):
+ """
+ Configure interface as DHCP client. The dhclient binary is automatically
+ started in background!
- hostname = 'vyos'
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> j = Interface('eth0')
+ >>> j.set_dhcp()
+ """
+
+ dhcp = {
+ 'hostname': 'vyos',
+ 'intf': self._ifname
+ }
+
+ # read configured system hostname.
+ # maybe change to vyos hostd client ???
with open('/etc/hostname', 'r') as f:
- hostname = f.read().rstrip('\n')
-
- a = [
- '# generated by interface_config.py',
- 'option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;',
- 'interface \"' + self._ifname + '\" {',
- '\tsend host-name \"' + hostname + '\";',
- '\trequest subnet-mask, broadcast-address, routers, domain-name-servers, rfc3442-classless-static-routes, domain-name, interface-mtu;',
- '}'
- ]
-
- cnf = ""
- for ln in a:
- cnf += str(ln + "\n")
- open(conf_file, 'w').write(cnf)
- if os.path.exists(dhclient_conf_dir + self._ifname + '.pid'):
- try:
- ret = subprocess.check_output(
- ['/sbin/dhclient -4 -r -pf ' + pidfile], shell=True).decode()
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
- try:
- ret = subprocess.check_output(
- ['/sbin/dhclient -4 -q -nw -cf ' + conf_file + ' -pf ' + pidfile + ' -lf ' + leasefile + ' ' + self._ifname], shell=True).decode()
- return True
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
- return None
+ dhcp['hostname'] = f.read().rstrip('\n')
+
+ # render DHCP configuration
+ tmpl = jinja2.Template(dhcp_cfg)
+ dhcp_text = tmpl.render(dhcp)
+ with open(self._dhcp_cfg_file, 'w') as f:
+ f.write(dhcp_text)
+
+ cmd = 'start-stop-daemon --start --quiet --pidfile ' + self._dhcp_pid_file
+ cmd += ' --exec /sbin/dhclient --'
+ # now pass arguments to dhclient binary
+ cmd += ' -4 -nw -cf {} -pf {} -lf {} {}'.format(self._dhcp_cfg_file, self._dhcp_pid_file, self._dhcp_lease_file, self._ifname)
+ self._cmd(cmd)
- def del_dhcpv4(self):
- conf_file = dhclient_conf_dir + self._ifname + '.conf'
- pidfile = dhclient_conf_dir + self._ifname + '.pid'
- leasefile = dhclient_conf_dir + self._ifname + '.leases'
- if not os.path.exists(pidfile):
- return 1
- try:
- ret = subprocess.check_output(
- ['/sbin/dhclient -4 -r -pf ' + pidfile], shell=True).decode()
- return True
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
- return None
- def get_dhcpv4(self):
- pidfile = dhclient_conf_dir + self._ifname + '.pid'
- if not os.path.exists(pidfile):
- print (
- "no dhcp client running on interface {0}".format(self._ifname))
- return False
+ def del_dhcp(self):
+ """
+ De-configure interface as DHCP clinet. All auto generated files like
+ pid, config and lease will be removed.
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> j = Interface('eth0')
+ >>> j.del_dhcp()
+ """
+
+ pid = 0
+ if os.path.isfile(self._dhcp_pid_file):
+ with open(self._dhcp_pid_file, 'r') as f:
+ pid = int(f.read())
else:
- pid = open(pidfile, 'r').read()
- print(
- "dhclient running on {0} with pid {1}".format(self._ifname, pid))
- return True
+ self._debug_msg('No DHCP client PID found')
+ return None
+
+ # stop dhclient
+ cmd = 'start-stop-daemon --stop --quiet --pidfile {}'.format(self._dhcp_pid_file)
+ self._cmd(cmd)
+
+ # cleanup old config file
+ if os.path.isfile(self._dhcp_cfg_file):
+ os.remove(self._dhcp_cfg_file)
+
+ # cleanup old pid file
+ if os.path.isfile(self._dhcp_pid_file):
+ os.remove(self._dhcp_pid_file)
+
+ # cleanup old lease file
+ if os.path.isfile(self._dhcp_lease_file):
+ os.remove(self._dhcp_lease_file)
+
def set_dhcpv6(self):
- conf_file = dhclient_conf_dir + self._ifname + '.v6conf'
- pidfile = dhclient_conf_dir + self._ifname + '.v6pid'
- leasefile = dhclient_conf_dir + self._ifname + '.v6leases'
- a = [
- '# generated by interface_config.py',
- 'interface \"' + self._ifname + '\" {',
- '\trequest routers, domain-name-servers, domain-name;',
- '}'
- ]
- cnf = ""
- for ln in a:
- cnf += str(ln + "\n")
- open(conf_file, 'w').write(cnf)
- subprocess.call(
- ['sysctl', '-q', '-w', 'net.ipv6.conf.' + self._ifname + '.accept_ra=0'])
- if os.path.exists(pidfile):
- try:
- ret = subprocess.check_output(
- ['/sbin/dhclient -6 -q -x -pf ' + pidfile], shell=True).decode()
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
- try:
- ret = subprocess.check_output(
- ['/sbin/dhclient -6 -q -nw -cf ' + conf_file + ' -pf ' + pidfile + ' -lf ' + leasefile + ' ' + self._ifname], shell=True).decode()
- return True
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
- return None
+ """
+ Configure interface as DHCPv6 client. The dhclient binary is automatically
+ started in background!
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> j = Interface('eth0')
+ >>> j.set_dhcpv6()
+ """
+
+ dhcpv6 = {
+ 'intf': self._ifname
+ }
+
+ # render DHCP configuration
+ tmpl = jinja2.Template(dhcpv6_cfg)
+ dhcpv6_text = tmpl.render(dhcpv6)
+ with open(self._dhcpv6_cfg_file, 'w') as f:
+ f.write(dhcpv6_text)
+
+ # https://bugs.launchpad.net/ubuntu/+source/ifupdown/+bug/1447715
+ #
+ # wee need to wait for IPv6 DAD to finish once and interface is added
+ # this suxx :-(
+ sleep(5)
+
+ # no longer accept router announcements on this interface
+ cmd = 'sysctl -q -w net.ipv6.conf.{}.accept_ra=0'.format(self._ifname)
+ self._cmd(cmd)
+
+ cmd = 'start-stop-daemon --start --quiet --pidfile ' + self._dhcpv6_pid_file
+ cmd += ' --exec /sbin/dhclient --'
+ # now pass arguments to dhclient binary
+ cmd += ' -6 -nw -cf {} -pf {} -lf {} {}'.format(self._dhcpv6_cfg_file, self._dhcpv6_pid_file, self._dhcpv6_lease_file, self._ifname)
+ self._cmd(cmd)
+
def del_dhcpv6(self):
- conf_file = dhclient_conf_dir + self._ifname + '.v6conf'
- pidfile = dhclient_conf_dir + self._ifname + '.v6pid'
- leasefile = dhclient_conf_dir + self._ifname + '.v6leases'
- if not os.path.exists(pidfile):
- return 1
- try:
- ret = subprocess.check_output(
- ['/sbin/dhclient -6 -q -x -pf ' + pidfile], shell=True).decode()
- subprocess.call(
- ['sysctl', '-q', '-w', 'net.ipv6.conf.' + self._ifname + '.accept_ra=1'])
- return True
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
+ """
+ De-configure interface as DHCPv6 clinet. All auto generated files like
+ pid, config and lease will be removed.
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> j = Interface('eth0')
+ >>> j.del_dhcpv6()
+ """
+
+ pid = 0
+ if os.path.isfile(self._dhcpv6_pid_file):
+ with open(self._dhcpv6_pid_file, 'r') as f:
+ pid = int(f.read())
+ else:
+ self._debug_msg('No DHCPv6 client PID found')
return None
- def get_dhcpv6(self):
- pidfile = dhclient_conf_dir + self._ifname + '.v6pid'
- if not os.path.exists(pidfile):
- print (
- "no dhcpv6 client running on interface {0}".format(self._ifname))
- return False
+ # stop dhclient
+ cmd = 'start-stop-daemon --stop --quiet --pidfile {}'.format(self._dhcpv6_pid_file)
+ self._cmd(cmd)
+
+ # accept router announcements on this interface
+ cmd = 'sysctl -q -w net.ipv6.conf.{}.accept_ra=1'.format(self._ifname)
+ self._cmd(cmd)
+
+ # cleanup old config file
+ if os.path.isfile(self._dhcpv6_cfg_file):
+ os.remove(self._dhcpv6_cfg_file)
+
+ # cleanup old pid file
+ if os.path.isfile(self._dhcpv6_pid_file):
+ os.remove(self._dhcpv6_pid_file)
+
+ # cleanup old lease file
+ if os.path.isfile(self._dhcpv6_lease_file):
+ os.remove(self._dhcpv6_lease_file)
+
+
+class DummyIf(Interface):
+ def __init__(self, ifname=None):
+ super().__init__(ifname, type='dummy')
+
+
+class BridgeIf(Interface):
+ def __init__(self, ifname=None):
+ super().__init__(ifname, type='bridge')
+
+ @property
+ def ageing_time(self):
+ """
+ Get bridge aging time in seconds.
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').aging_time
+ '3'
+ """
+
+ time = 0
+ with open('/sys/class/net/{0}/bridge/ageing_time'.format(self._ifname), 'r') as f:
+ time = int(f.read().rstrip('\n'))
+
+ # kernel representation is in centiseconds - convert to seconds
+ return time/100
+
+
+ @ageing_time.setter
+ def ageing_time(self, time=None):
+ """
+ Set bridge aging time in seconds.
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').ageing_time = 2
+ """
+
+ if not time:
+ raise ValueError()
+
+ # kernel representation is in centiseconds - convert from seconds to centiseconds
+ time = int(time) * 100
+
+ with open('/sys/class/net/{0}/bridge/ageing_time'.format(self._ifname), 'w') as f:
+ f.write(str(time))
+
+ @property
+ def forward_delay(self):
+ """
+ Get bridge forwarding delay in seconds.
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').ageing_time
+ '3"""
+
+ time = 0
+ with open('/sys/class/net/{0}/bridge/forward_delay'.format(self._ifname), 'r') as f:
+ time = int(f.read().rstrip('\n'))
+
+ # kernel representation is in centiseconds - convert to seconds
+ return time/100
+
+
+ @forward_delay.setter
+ def forward_delay(self, time=None):
+ """
+ Set bridge forwarding delay in seconds.
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').forward_delay = 15
+ """
+
+ if not time:
+ raise ValueError()
+
+ # kernel representation is in centiseconds - convert from seconds to centiseconds
+ time = int(time) * 100
+
+ with open('/sys/class/net/{0}/bridge/forward_delay'.format(self._ifname), 'w') as f:
+ f.write(str(time))
+
+ @property
+ def hello_time(self):
+ """
+ Get bridge hello time in seconds.
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').hello_time
+ '2'
+ """
+
+ time = 0
+ with open('/sys/class/net/{0}/bridge/hello_time'.format(self._ifname), 'r') as f:
+ time = int(f.read().rstrip('\n'))
+
+ # kernel representation is in centiseconds - convert to seconds
+ return time/100
+
+
+ @hello_time.setter
+ def hello_time(self, time=None):
+ """
+ Set bridge hello time in seconds.
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').hello_time = 2
+ """
+
+ if not time:
+ raise ValueError()
+
+ # kernel representation is in centiseconds - convert from seconds to centiseconds
+ time = int(time) * 100
+
+ with open('/sys/class/net/{0}/bridge/hello_time'.format(self._ifname), 'w') as f:
+ f.write(str(time))
+
+ @property
+ def max_age(self):
+ """
+ Get bridge max max message age in seconds.
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').max_age
+ '20'
+ """
+
+ time = 0
+ with open('/sys/class/net/{0}/bridge/max_age'.format(self._ifname), 'r') as f:
+ time = int(f.read().rstrip('\n'))
+
+ # kernel representation is in centiseconds - convert to seconds
+ return time/100
+
+
+ @max_age.setter
+ def max_age(self, time=None):
+ """
+ Set bridge max message age in seconds.
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').max_age = 30
+ """
+
+ if not time:
+ raise ValueError()
+
+ # kernel representation is in centiseconds - convert from seconds to centiseconds
+ time = int(time) * 100
+
+ with open('/sys/class/net/{0}/bridge/max_age'.format(self._ifname), 'w') as f:
+ f.write(str(time))
+
+ @property
+ def priority(self):
+ """
+ Get bridge max aging time in seconds.
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').priority
+ '32768'
+ """
+
+ priority = 0
+ with open('/sys/class/net/{0}/bridge/priority'.format(self._ifname), 'r') as f:
+ priority = int(f.read().rstrip('\n'))
+
+ # kernel representation is in centiseconds - convert to seconds
+ return priority
+
+
+ @priority.setter
+ def priority(self, priority=None):
+ """
+ Set bridge max aging time in seconds.
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').priority = 8192
+ """
+
+ if not priority:
+ raise ValueError()
+
+ with open('/sys/class/net/{0}/bridge/priority'.format(self._ifname), 'w') as f:
+ f.write(str(priority))
+
+
+ @property
+ def stp_state(self):
+ """
+ Get bridge STP state
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').stp_state
+ '0'
+ """
+
+ state = 0
+ with open('/sys/class/net/{0}/bridge/stp_state'.format(self._ifname), 'r') as f:
+ state = int(f.read().rstrip('\n'))
+
+ return state
+
+
+ @stp_state.setter
+ def stp_state(self, state=None):
+ """
+ Set bridge STP state.
+ 0 -> STP disabled, 1 -> STP enabled
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').stp_state = 1
+ """
+
+ if int(state) >= 0 and int(state) <= 1:
+ with open('/sys/class/net/{0}/bridge/stp_state'.format(self._ifname), 'w') as f:
+ f.write(str(state))
+ else:
+ raise ValueError()
+
+
+ @property
+ def multicast_querier(self):
+ """
+ Get bridge multicast querier membership state.
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').multicast_querier
+ '0'
+ """
+
+ enable = 0
+ with open('/sys/class/net/{0}/bridge/multicast_querier'.format(self._ifname), 'r') as f:
+ enable = int(f.read().rstrip('\n'))
+
+ return enable
+
+
+ @multicast_querier.setter
+ def multicast_querier(self, enable=None):
+ """
+ Sets whether the bridge actively runs a multicast querier or not. When a
+ bridge receives a 'multicast host membership' query from another network
+ host, that host is tracked based on the time that the query was received
+ plus the multicast query interval time.
+
+ Use enable=1 to enable or enable=0 to disable
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').multicast_querier = 1
+ """
+
+ if int(enable) >= 0 and int(enable) <= 1:
+ with open('/sys/class/net/{0}/bridge/multicast_querier'.format(self._ifname), 'w') as f:
+ f.write(str(enable))
else:
- pid = open(pidfile, 'r').read()
- print(
- "dhclientv6 running on {0} with pid {1}".format(self._ifname, pid))
- return True
+ raise ValueError()
+
+
+ def add_port(self, interface=None):
+ """
+ Add bridge member port
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').add_port('eth0')
+ >>> BridgeIf('br0').add_port('eth1')
+ """
+
+ if not interface:
+ raise ValueError('No interface address specified')
+
+ cmd = 'ip link set dev "{}" master "{}"'.format(interface, self._ifname)
+ self._cmd(cmd)
+
+
+ def del_port(self, interface=None):
+ """
+ Add bridge member port
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').del_port('eth1')
+ """
+
+ if not interface:
+ raise ValueError('No interface address specified')
+
+ cmd = 'ip link set dev "{}" nomaster'.format(interface)
+ self._cmd(cmd)
+
+
+ def set_cost(self, interface=None, cost=None):
+ """
+ Set interface path cost, only relevant for STP enabled interfaces
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').path_cost(4)
+ """
+
+ if not interface:
+ raise ValueError('interface not specified')
+
+ if not cost:
+ raise ValueError('cost not specified')
+
+ with open('/sys/class/net/{}/brif/{}/path_cost'.format(self._ifname, interface), 'w') as f:
+ f.write(str(cost))
+
+
+ def set_priority(self, interface=None, priority=None):
+ """
+ Set interface path priority, only relevant for STP enabled interfaces
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').priority(4)
+ """
+
+ if not interface:
+ raise ValueError('interface not specified')
+ if not priority:
+ raise ValueError('priority not specified')
-# TODO: dhcpv6-pd via dhclient
+ with open('/sys/class/net/{}/brif/{}/priority'.format(self._ifname, interface), 'w') as f:
+ f.write(str(priority))