diff options
author | Christian Poessinger <christian@poessinger.com> | 2019-08-31 13:13:15 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-08-31 13:13:15 +0200 |
commit | 153c60d65d9cbb15e2d071dfecbb4d9994dd77ec (patch) | |
tree | 1831ccaf4b28ac1d7dccbc6b307edb22c25e16a3 | |
parent | 5348c46c0eb5e1a2f2d059695ba971c211c0f260 (diff) | |
parent | bfb2b883071b27adbb33035ae51caf69fc338972 (diff) | |
download | vyos-1x-153c60d65d9cbb15e2d071dfecbb4d9994dd77ec.tar.gz vyos-1x-153c60d65d9cbb15e2d071dfecbb4d9994dd77ec.zip |
Merge pull request #121 from c-po/t1557-ifconfig
T1557 Create generic abstraction for configuring interfaces e.g. IP address
-rw-r--r-- | debian/control | 1 | ||||
-rw-r--r-- | interface-definitions/interfaces-bridge.xml | 6 | ||||
-rw-r--r-- | python/vyos/ifconfig.py | 865 | ||||
-rwxr-xr-x | src/conf_mode/interface-bridge.py | 127 | ||||
-rwxr-xr-x | src/conf_mode/interface-dummy.py | 33 | ||||
-rwxr-xr-x | src/conf_mode/interface-loopback.py | 17 |
6 files changed, 743 insertions, 306 deletions
diff --git a/debian/control b/debian/control index 7b75ca111..12eb7c309 100644 --- a/debian/control +++ b/debian/control @@ -29,7 +29,6 @@ Depends: python3, python3-vici (>= 5.7.2), python3-bottle, python3-zmq, - python3-pyroute2, ipaddrcheck, tcpdump, tshark, diff --git a/interface-definitions/interfaces-bridge.xml b/interface-definitions/interfaces-bridge.xml index e92a55d63..98998bfa1 100644 --- a/interface-definitions/interfaces-bridge.xml +++ b/interface-definitions/interfaces-bridge.xml @@ -191,13 +191,13 @@ <properties> <help>Interval at which neighbor bridges are removed</help> <valueHelp> - <format>1-40</format> + <format>1-1200</format> <description>Bridge maximum aging time in seconds (default 20)</description> </valueHelp> <constraint> - <validator name="numeric" argument="--range 1-40"/> + <validator name="numeric" argument="--range 1-1200"/> </constraint> - <constraintErrorMessage>Bridge max aging value must be between 1 and 40 seconds</constraintErrorMessage> + <constraintErrorMessage>Bridge max aging value must be between 1 and 1200 seconds</constraintErrorMessage> </properties> </leafNode> <node name="member"> diff --git a/python/vyos/ifconfig.py b/python/vyos/ifconfig.py index 5f28125af..944c1ef82 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 @@ -17,49 +15,80 @@ import sys import os -import re -import json -import socket 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 @@ -67,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 @@ -93,7 +129,7 @@ class Interface: Example: >>> from vyos.ifconfig import Interface - >>> Interface('eth1').mtu + >>> Interface('eth0').mtu '1500' """ @@ -111,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' """ @@ -131,7 +167,7 @@ class Interface: Example: >>> from vyos.ifconfig import Interface - >>> Interface('eth1').mac + >>> Interface('eth0').mac '00:0c:29:11:aa:cc' """ address = '' @@ -148,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 @@ -176,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 @@ -183,7 +310,7 @@ class Interface: Example: >>> from vyos.ifconfig import Interface - >>> Interface('eth1').ifalias + >>> Interface('eth0').ifalias '' """ @@ -201,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 '' """ @@ -219,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): """ @@ -227,7 +355,7 @@ class Interface: Example: >>> from vyos.ifconfig import Interface - >>> Interface('eth1').state + >>> Interface('eth0').state 'up' """ @@ -245,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' """ @@ -259,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. @@ -279,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'] """ @@ -314,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() @@ -326,12 +442,7 @@ class Interface: raise ValueError('No IP address specified') if not is_intf_addr_assigned(self._ifname, addr): - cmd = '' - if is_ipv4(addr): - cmd = 'sudo ip -4 addr add "{}" broadcast + dev "{}"'.format(addr, self._ifname) - elif is_ipv6(addr): - cmd = 'sudo ip -6 addr add "{}" dev "{}"'.format(addr, self._ifname) - + cmd = 'sudo ip addr add "{}" dev "{}"'.format(addr, self._ifname) self._cmd(cmd) @@ -340,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() @@ -355,137 +467,524 @@ class Interface: raise ValueError('No IP address specified') if is_intf_addr_assigned(self._ifname, addr): - cmd = '' - if is_ipv4(addr): - cmd = 'ip -4 addr del "{}" dev "{}"'.format(addr, self._ifname) - elif is_ipv6(addr): - cmd = 'ip -6 addr del "{}" dev "{}"'.format(addr, self._ifname) - + cmd = 'ip addr del "{}" dev "{}"'.format(addr, self._ifname) self._cmd(cmd) # 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' - - a = [ - '# generated by interface_config.py', - 'option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;', - 'interface \"' + self._ifname + '\" {', - '\tsend host-name \"' + socket.gethostname() + '\";', - '\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 + def set_dhcp(self): + """ + Configure interface as DHCP client. The dhclient binary is automatically + started in background! - 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 + 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: + 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_dhcp(self): + """ + De-configure interface as DHCP clinet. All auto generated files like + pid, config and lease will be removed. + + Example: - 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 + >>> 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 LoopbackIf(Interface): + def __init__(self, ifname=None): + super().__init__(ifname, type='loopback') + + +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: - pid = open(pidfile, 'r').read() - print( - "dhclientv6 running on {0} with pid {1}".format(self._ifname, pid)) - return True + 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: + 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)) diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py index d5661be93..187be677e 100755 --- a/src/conf_mode/interface-bridge.py +++ b/src/conf_mode/interface-bridge.py @@ -16,21 +16,21 @@ # # -from os import environ +import os + from copy import deepcopy from sys import exit -from pyroute2 import IPDB from netifaces import interfaces from vyos.config import Config from vyos.validate import is_ip -from vyos.ifconfig import Interface as IF +from vyos.ifconfig import BridgeIf, Interface from vyos import ConfigError default_config_data = { 'address': [], 'address_remove': [], 'aging': 300, - 'arp_cache_timeout_ms': 30000, + 'arp_cache_tmo': 30, 'description': '', 'deleted': False, 'disable': False, @@ -57,7 +57,7 @@ def get_config(): # determine tagNode instance try: - bridge['intf'] = environ['VYOS_TAGNODE_VALUE'] + bridge['intf'] = os.environ['VYOS_TAGNODE_VALUE'] except KeyError as E: print("Interface not specified") @@ -107,7 +107,7 @@ def get_config(): # ARP cache entry timeout in seconds if conf.exists('ip arp-cache-timeout'): - bridge['arp_cache_timeout_ms'] = int(conf.return_value('ip arp-cache-timeout')) * 1000 + bridge['arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout')) # Media Access Control (MAC) address if conf.exists('mac'): @@ -181,56 +181,35 @@ def generate(bridge): return None def apply(bridge): - ipdb = IPDB(mode='explicit') - brif = bridge['intf'] + br = BridgeIf(bridge['intf']) if bridge['deleted']: - try: - # delete bridge interface - with ipdb.interfaces[ brif ] as br: - br.remove() - - # stop DHCP(v6) clients if configured - for addr in bridge['address_remove']: - if addr == 'dhcp': - IF(brif).del_dhcpv4() - elif addr == 'dhcpv6': - IF(brif).del_dhcpv6() - except: - pass + # delete bridge interface + # DHCP is stopped inside remove() + br.remove() else: - try: - # create bridge interface if it not already exists - ipdb.create(kind='bridge', ifname=brif).commit() - except: - pass - - # get handle in bridge interface - br = ipdb.interfaces[brif] - # begin() a transaction prior to make any change - br.begin() # enable interface - br.up() - # set ageing time - - value is in centiseconds YES! centiseconds! - br.br_ageing_time = bridge['aging'] * 100 - # set bridge forward delay - value is in centiseconds YES! centiseconds! - br.br_forward_delay = bridge['forwarding_delay'] * 100 - # set hello time - value is in centiseconds YES! centiseconds! - br.br_hello_time = bridge['hello_time'] * 100 - # set max message age - value is in centiseconds YES! centiseconds! - br.br_max_age = bridge['max_age'] * 100 + br.state = 'up' + # set ageing time + br.ageing_time = bridge['aging'] + # set bridge forward delay + br.forward_delay = bridge['forwarding_delay'] + # set hello time + br.hello_time = bridge['hello_time'] + # set max message age + br.max_age = bridge['max_age'] # set bridge priority - br.br_priority = bridge['priority'] + br.priority = bridge['priority'] # turn stp on/off - br.br_stp_state = bridge['stp'] + br.stp_state = bridge['stp'] # enable or disable IGMP querier - br.br_mcast_querier = bridge['igmp_querier'] + br.multicast_querier = bridge['igmp_querier'] # update interface description used e.g. within SNMP br.ifalias = bridge['description'] # Change interface MAC address if bridge['mac']: - br.set_address = bridge['mac'] + br.mac = bridge['mac'] # remove interface from bridge for intf in bridge['member_remove']: @@ -240,52 +219,40 @@ def apply(bridge): for member in bridge['member']: br.add_port(member['name']) + # up/down interface + if bridge['disable']: + br.state = 'down' + # remove configured network interface addresses/DHCP(v6) configuration for addr in bridge['address_remove']: - try: - is_ip(addr) - br.del_ip(addr) - except ValueError: - if addr == 'dhcp': - IF(brif).del_dhcpv4() - elif addr == 'dhcpv6': - IF(brif).del_dhcpv6() + if addr == 'dhcp': + br.del_dhcp() + elif addr == 'dhcpv6': + br.del_dhcpv6() + else: + br.del_addr(addr) # add configured network interface addresses/DHCP(v6) configuration for addr in bridge['address']: - try: - is_ip(addr) - br.add_ip(addr) - except: - if addr == 'dhcp': - IF(brif).set_dhcpv4() - elif addr == 'dhcpv6': - IF(brif).set_dhcpv6() - - # up/down interface - if bridge['disable']: - br.down() - - # commit changes on bridge interface - br.commit() + if addr == 'dhcp': + br.set_dhcp() + elif addr == 'dhcpv6': + br.set_dhcpv6() + else: + br.add_addr(addr) # configure additional bridge member options for member in bridge['member']: - # configure ARP cache timeout in milliseconds - with open('/proc/sys/net/ipv4/neigh/' + member['name'] + '/base_reachable_time_ms', 'w') as f: - f.write(str(bridge['arp_cache_timeout_ms'])) - # ignore link state changes - with open('/proc/sys/net/ipv4/conf/' + member['name'] + '/link_filter', 'w') as f: - f.write(str(bridge['disable_link_detect'])) - - # adjust member port stp attributes - member_if = ipdb.interfaces[ member['name'] ] - member_if.begin() # set bridge port cost - member_if.brport_cost = member['cost'] + br.set_cost(member['name'], member['cost']) # set bridge port priority - member_if.brport_priority = member['priority'] - member_if.commit() + br.set_priority(member['name'], member['priority']) + + i = Interface(member['name']) + # configure ARP cache timeout + i.arp_cache_tmo = bridge['arp_cache_tmo'] + # ignore link state changes + i.link_detect = bridge['disable_link_detect'] return None diff --git a/src/conf_mode/interface-dummy.py b/src/conf_mode/interface-dummy.py index d8a36a5b2..1f76d0638 100755 --- a/src/conf_mode/interface-dummy.py +++ b/src/conf_mode/interface-dummy.py @@ -19,8 +19,8 @@ from os import environ from copy import deepcopy from sys import exit -from pyroute2 import IPDB from vyos.config import Config +from vyos.ifconfig import DummyIf from vyos import ConfigError default_config_data = { @@ -83,45 +83,26 @@ def generate(dummy): return None def apply(dummy): - ipdb = IPDB(mode='explicit') - dummyif = dummy['intf'] + du = DummyIf(dummy['intf']) # Remove dummy interface if dummy['deleted']: - try: - # delete dummy interface - with ipdb.interface[ dummyif ] as du: - du.remove() - except: - pass + du.remove() else: - try: - # create dummy interface if it's non existing - ipdb.create(kind='dummy', ifname=dummyif).commit() - except: - pass - - # retrieve handle to dummy interface - du = ipdb.interfaces[dummyif] - # begin a transaction prior to make any change - du.begin() # enable interface - du.up() + du.state = 'up' # update interface description used e.g. within SNMP du.ifalias = dummy['description'] # Configure interface address(es) for addr in dummy['address_remove']: - du.del_ip(addr) + du.del_addr(addr) for addr in dummy['address']: - du.add_ip(addr) + du.add_addr(addr) # disable interface on demand if dummy['disable']: - du.down() - - # commit changes on bridge interface - du.commit() + du.state = 'down' return None diff --git a/src/conf_mode/interface-loopback.py b/src/conf_mode/interface-loopback.py index 5c1419b11..1dd68c039 100755 --- a/src/conf_mode/interface-loopback.py +++ b/src/conf_mode/interface-loopback.py @@ -18,7 +18,7 @@ from os import environ from sys import exit from copy import deepcopy -from pyroute2 import IPDB +from vyos.ifconfig import LoopbackIf from vyos.config import Config from vyos import ConfigError @@ -75,28 +75,19 @@ def generate(loopback): return None def apply(loopback): - ipdb = IPDB(mode='explicit') - lo_if = loopback['intf'] - - # the loopback device always exists - lo = ipdb.interfaces[lo_if] - # begin() a transaction prior to make any change - lo.begin() - + lo = LoopbackIf(loopback['intf']) if not loopback['deleted']: # update interface description used e.g. within SNMP # update interface description used e.g. within SNMP lo.ifalias = loopback['description'] # configure interface address(es) for addr in loopback['address']: - lo.add_ip(addr) + lo.add_addr(addr) # remove interface address(es) for addr in loopback['address_remove']: - lo.del_ip(addr) + lo.del_addr(addr) - # commit changes on loopback interface - lo.commit() return None if __name__ == '__main__': |