diff options
Diffstat (limited to 'python/vyos')
-rw-r--r-- | python/vyos/configsession.py | 3 | ||||
-rw-r--r-- | python/vyos/ifconfig.py | 1061 |
2 files changed, 830 insertions, 234 deletions
diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index 8626839f2..acbdd3d5f 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -24,6 +24,7 @@ COMMENT = '/opt/vyatta/sbin/my_comment' COMMIT = '/opt/vyatta/sbin/my_commit' DISCARD = '/opt/vyatta/sbin/my_discard' SHOW_CONFIG = ['/bin/cli-shell-api', 'showConfig'] +LOAD_CONFIG = ['/bin/cli-shell-api', 'loadFile'] # Default "commit via" string APP = "vyos-http-api" @@ -155,3 +156,5 @@ class ConfigSession(object): if format == 'raw': return config_data + def load_config(self, file_path): + self.__run_command(LOAD_CONFIG + [file_path]) diff --git a/python/vyos/ifconfig.py b/python/vyos/ifconfig.py index 5f28125af..506004fa0 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 @@ -15,113 +13,164 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see <http://www.gnu.org/licenses/>. -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, type=None): """ - Create instance of an IP interface + This is the base interface class which supports basic IP/MAC address + operations as well as DHCP(v6). Other interface which represent e.g. + and ethernet bridge are implemented as derived classes adding all + additional functionality. - Example: + DEBUG: + This class has embedded debugging (print) which can be enabled by + creating the following file: + vyos@vyos# touch /tmp/vyos.ifconfig.debug + 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/{}'.format(ifname)) and not type: + raise Exception('interface "{}" not found'.format(str(ifname))) - if not os.path.exists('/sys/class/net/{0}'.format(ifname)) and not type: - raise Exception("interface {0} not found".format(str(ifname))) + self._ifname = str(ifname) + self._debug = False - 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) + if os.path.isfile('/tmp/vyos.ifconfig.debug'): + self._debug = True + + 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)) - self._ifname = str(ifname) - @property def remove(self): """ Remove system 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 + def _read_sysfs(self, filename): + """ + Provide a single primitive w/ error checking for reading from sysfs. + """ + var = None + with open(filename, 'r') as f: + var = f.read().rstrip('\n') + + self._debug_msg('read "{}" <- "{}"'.format(value, filename)) + return var + + + def _write_sysfs(self, filename, value): + """ + Provide a single primitive w/ error checking for writing to sysfs. + """ + self._debug_msg('write "{}" -> "{}"'.format(value, filename)) + with open(filename, 'w') as f: + f.write(str(value)) + + return None + + @property def mtu(self): """ Get/set interface mtu in bytes. Example: - >>> from vyos.ifconfig import Interface - >>> Interface('eth1').mtu + >>> Interface('eth0').mtu '1500' """ - - mtu = 0 - with open('/sys/class/net/{0}/mtu'.format(self._ifname), 'r') as f: - mtu = f.read().rstrip('\n') - return mtu + return self._read_sysfs('/sys/class/net/{0}/mtu' + .format(self._ifname)) @mtu.setter - def mtu(self, mtu=None): + def mtu(self, mtu): """ Get/set interface mtu in bytes. Example: - >>> from vyos.ifconfig import Interface - >>> Interface('br100', type='bridge').mtu = 1400 - >>> Interface('br100').mtu + >>> Interface('eth0').mtu = 1400 + >>> Interface('eth0').mtu '1400' """ - if mtu < 68 or mtu > 9000: raise ValueError('Invalid MTU size: "{}"'.format(mru)) - with open('/sys/class/net/{0}/mtu'.format(self._ifname), 'w') as f: - f.write(str(mtu)) - + return self._write_sysfs('/sys/class/net/{0}/mtu' + .format(self._ifname), mtu) @property def mac(self): @@ -129,27 +178,23 @@ class Interface: Get/set interface mac address Example: - >>> from vyos.ifconfig import Interface - >>> Interface('eth1').mac + >>> Interface('eth0').mac '00:0c:29:11:aa:cc' """ - address = '' - with open('/sys/class/net/{0}/address'.format(self._ifname), 'r') as f: - address = f.read().rstrip('\n') - return address + return self._read_sysfs('/sys/class/net/{0}/address' + .format(self._ifname)) @mac.setter - def mac(self, mac=None): + def mac(self, mac): """ Get/set interface mac address 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 +221,76 @@ class Interface: @property + def arp_cache_tmo(self): + """ + Get configured ARP cache timeout value from interface in seconds. + Internal Kernel representation is in milliseconds. + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').arp_cache_tmo + '30' + """ + return (self._read_sysfs('/proc/sys/net/ipv4/neigh/{0}/base_reachable_time_ms' + .format(self._ifname)) / 1000) + + + @arp_cache_tmo.setter + def arp_cache_tmo(self, tmo): + """ + Set ARP cache timeout value in seconds. Internal Kernel representation + is in milliseconds. + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').arp_cache_tmo = '40' + """ + return self._write_sysfs('/proc/sys/net/ipv4/neigh/{0}/base_reachable_time_ms' + .format(self._ifname), (int(tmo) * 1000)) + + @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' + """ + return self._read_sysfs('/proc/sys/net/ipv4/conf/{0}/link_filter' + .format(self._ifname)) + + + @link_detect.setter + def link_detect(self, link_filter): + """ + 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' + """ + if link_filter >= 0 and link_filter <= 2: + return self._write_sysfs('/proc/sys/net/ipv4/conf/{0}/link_filter' + .format(self._ifname), link_filter) + else: + raise ValueError() + + + @property def ifalias(self): """ Get/set interface alias name @@ -183,14 +298,11 @@ class Interface: Example: >>> from vyos.ifconfig import Interface - >>> Interface('eth1').ifalias + >>> Interface('eth0').ifalias '' """ - - alias = '' - with open('/sys/class/net/{0}/ifalias'.format(self._ifname), 'r') as f: - alias = f.read().rstrip('\n') - return alias + return self._read_sysfs('/sys/class/net/{0}/ifalias' + .format(self._ifname)) @ifalias.setter @@ -199,25 +311,24 @@ class Interface: Get/set interface alias name 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 '' """ - - # clear interface alias if not ifalias: + # clear interface alias ifalias = '\0' - with open('/sys/class/net/{0}/ifalias'.format(self._ifname), 'w') as f: - f.write(str(ifalias)) + self._write_sysfs('/sys/class/net/{0}/ifalias' + .format(self._ifname), ifalias) + @property def state(self): @@ -225,31 +336,25 @@ class Interface: Enable (up) / Disable (down) an interface Example: - >>> from vyos.ifconfig import Interface - >>> Interface('eth1').state + >>> Interface('eth0').state 'up' """ - - state = '' - with open('/sys/class/net/{0}/operstate'.format(self._ifname), 'r') as f: - state = f.read().rstrip('\n') - return state + return self._read_sysfs('/sys/class/net/{0}/operstate' + .format(self._ifname)) @state.setter - def state(self, state=None): + def state(self, state): """ Enable (up) / Disable (down) an interface Example: - >>> from vyos.ifconfig import Interface - >>> Interface('eth1').state = 'down' - >>> Interface('eth1').state + >>> Interface('eth0').state = 'down' + >>> Interface('eth0').state 'down' """ - if state not in ['up', 'down']: raise ValueError('state must be "up" or "down"') @@ -258,17 +363,98 @@ class Interface: cmd = 'ip link set dev "{}" "{}"'.format(self._ifname, state) self._cmd(cmd) + @property + def proxy_arp(self): + """ + Get current proxy ARP configuration from sysfs. Default: 0 + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').proxy_arp + '0' + """ + return self._read_sysfs('/proc/sys/net/ipv4/conf/{}/proxy_arp' + .format(self._ifname)) + + @proxy_arp.setter + def proxy_arp(self, enable): + """ + Set per interface proxy ARP configuration + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').proxy_arp = 1 + >>> Interface('eth0').proxy_arp + '1' + """ + if int(enable) >= 0 and int(enable) <= 1: + return self._write_sysfs('/proc/sys/net/ipv4/conf/{}/proxy_arp' + .format(self._ifname), enable) + else: + raise ValueError("Value out of range") + + + @property + def proxy_arp_pvlan(self): + """ + Private VLAN proxy arp. + Basically allow proxy arp replies back to the same interface + (from which the ARP request/solicitation was received). + + This is done to support (ethernet) switch features, like RFC + 3069, where the individual ports are NOT allowed to + communicate with each other, but they are allowed to talk to + the upstream router. As described in RFC 3069, it is possible + to allow these hosts to communicate through the upstream + router by proxy_arp'ing. Don't need to be used together with + proxy_arp. + + This technology is known by different names: + In RFC 3069 it is called VLAN Aggregation. + Cisco and Allied Telesyn call it Private VLAN. + Hewlett-Packard call it Source-Port filtering or port-isolation. + Ericsson call it MAC-Forced Forwarding (RFC Draft). + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').proxy_arp_pvlan + '0' + """ + return self._read_sysfs('/proc/sys/net/ipv4/conf/{}/proxy_arp_pvlan' + .format(self._ifname)) - def _debug(self, e=None): + @proxy_arp_pvlan.setter + def proxy_arp_pvlan(self, enable): """ - export DEBUG=1 to see debug messages + Private VLAN proxy arp. + Basically allow proxy arp replies back to the same interface + (from which the ARP request/solicitation was received). + + This is done to support (ethernet) switch features, like RFC + 3069, where the individual ports are NOT allowed to + communicate with each other, but they are allowed to talk to + the upstream router. As described in RFC 3069, it is possible + to allow these hosts to communicate through the upstream + router by proxy_arp'ing. Don't need to be used together with + proxy_arp. + + This technology is known by different names: + In RFC 3069 it is called VLAN Aggregation. + Cisco and Allied Telesyn call it Private VLAN. + Hewlett-Packard call it Source-Port filtering or port-isolation. + Ericsson call it MAC-Forced Forwarding (RFC Draft). + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').proxy_arp_pvlan = 1 + >>> Interface('eth0').proxy_arp_pvlan + '1' """ - 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 + if int(enable) >= 0 and int(enable) <= 1: + return self._write_sysfs('/proc/sys/net/ipv4/conf/{}/proxy_arp_pvlan' + .format(self._ifname), enable) + else: + raise ValueError("Value out of range") def get_addr(self): @@ -277,9 +463,8 @@ class Interface: This is done using the netifaces and ipaddress python modules. 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'] """ @@ -307,41 +492,31 @@ class Interface: return ipv4 + ipv6 - def add_addr(self, addr=None): + def add_addr(self, addr): """ Add IP address to interface. Address is only added if it yet not added to that 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() ['192.0.2.1/24', '2001:db8::ffff/64'] """ - - if not addr: - 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) - def del_addr(self, addr=None): + def del_addr(self, addr): """ 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() @@ -350,142 +525,560 @@ class Interface: >>> j.get_addr() ['2001:db8::ffff/64'] """ - - if not addr: - 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: - 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.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: + + >>> 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): + super().__init__(ifname, type='loopback') + + +class DummyIf(Interface): + def __init__(self, ifname): + super().__init__(ifname, type='dummy') + + +class BridgeIf(Interface): + def __init__(self, ifname): + super().__init__(ifname, type='bridge') + + @property + def ageing_time(self): + """ + Return configured bridge interface MAC address aging time in seconds. + Internal kernel representation is in centiseconds, thus its converted + in the end. Kernel default is 300 seconds. + + Example: + >>> from vyos.ifconfig import Interface + >>> BridgeIf('br0').aging_time + '300' + """ + return (self._read_sysfs('/sys/class/net/{0}/bridge/ageing_time' + .format(self._ifname)) / 100) + + @ageing_time.setter + def ageing_time(self, time): + """ + Set bridge interface MAC address aging time in seconds. Internal kernel + representation is in centiseconds. Kernel default is 300 seconds. + + Example: + >>> from vyos.ifconfig import Interface + >>> BridgeIf('br0').ageing_time = 2 + """ + time = int(time) * 100 + return self._write_sysfs('/sys/class/net/{0}/bridge/ageing_time' + .format(self._ifname), time) + + @property + def forward_delay(self): + """ + Get bridge forwarding delay in seconds. Internal Kernel representation + is in centiseconds. + + Example: + >>> from vyos.ifconfig import Interface + >>> BridgeIf('br0').ageing_time + '3' + """ + return (self._read_sysfs('/sys/class/net/{0}/bridge/forward_delay' + .format(self._ifname)) / 100) + + @forward_delay.setter + def forward_delay(self, time): + """ + Set bridge forwarding delay in seconds. Internal Kernel representation + is in centiseconds. + + Example: + >>> from vyos.ifconfig import Interface + >>> BridgeIf('br0').forward_delay = 15 + """ + return self._write_sysfs('/sys/class/net/{0}/bridge/forward_delay' + .format(self._ifname), (int(time) * 100)) + + @property + def hello_time(self): + """ + Get bridge hello time in seconds. Internal Kernel representation + is in centiseconds. + + Example: + >>> from vyos.ifconfig import Interface + >>> BridgeIf('br0').hello_time + '2' + """ + return (self._read_sysfs('/sys/class/net/{0}/bridge/hello_time' + .format(self._ifname)) / 100) + + + @hello_time.setter + def hello_time(self, time): + """ + Set bridge hello time in seconds. Internal Kernel representation + is in centiseconds. + + Example: + >>> from vyos.ifconfig import Interface + >>> BridgeIf('br0').hello_time = 2 + """ + return self._write_sysfs('/sys/class/net/{0}/bridge/hello_time' + .format(self._ifname), (int(time) * 100)) + + @property + def max_age(self): + """ + Get bridge max max message age in seconds. Internal Kernel representation + is in centiseconds. + + Example: + >>> from vyos.ifconfig import Interface + >>> BridgeIf('br0').max_age + '20' + """ + + return (self._read_sysfs('/sys/class/net/{0}/bridge/max_age' + .format(self._ifname)) / 100) + + @max_age.setter + def max_age(self, time): + """ + Set bridge max message age in seconds. Internal Kernel representation + is in centiseconds. + + Example: + >>> from vyos.ifconfig import Interface + >>> BridgeIf('br0').max_age = 30 + """ + return self._write_sysfs('/sys/class/net/{0}/bridge/max_age' + .format(self._ifname), (int(time) * 100)) + + @property + def priority(self): + """ + Get bridge max aging time in seconds. + + Example: + >>> from vyos.ifconfig import Interface + >>> BridgeIf('br0').priority + '32768' + """ + return self._read_sysfs('/sys/class/net/{0}/bridge/priority' + .format(self._ifname)) + + @priority.setter + def priority(self, priority): + """ + Set bridge max aging time in seconds. + + Example: + >>> from vyos.ifconfig import Interface + >>> BridgeIf('br0').priority = 8192 + """ + return self._write_sysfs('/sys/class/net/{0}/bridge/priority' + .format(self._ifname), priority) + + @property + def stp_state(self): + """ + Get current bridge STP (Spanning Tree) 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): + """ + Set bridge STP (Spannign Tree) 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: + return self._write_sysfs('/sys/class/net/{0}/bridge/stp_state' + .format(self._ifname), state) + else: + raise ValueError("Value out of range") + + @property + def multicast_querier(self): + """ + Get bridge multicast querier membership state. + + Example: + >>> from vyos.ifconfig import Interface + >>> BridgeIf('br0').multicast_querier + '0' + """ + return self._read_sysfs('/sys/class/net/{0}/bridge/multicast_querier' + .format(self._ifname)) + + @multicast_querier.setter + def multicast_querier(self, enable): + """ + 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: + return self._write_sysfs('/sys/class/net/{0}/bridge/multicast_querier' + .format(self._ifname), enable) else: - pid = open(pidfile, 'r').read() - print( - "dhclientv6 running on {0} with pid {1}".format(self._ifname, pid)) - return True + raise ValueError("Value out of range") + + + def add_port(self, interface): + """ + Add physical interface to 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): + """ + 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, cost): + """ + Set interface path cost, only relevant for STP enabled interfaces + + Example: + + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').path_cost(4) + """ + return self._write_sysfs('/sys/class/net/{}/brif/{}/path_cost' + .format(self._ifname, interface), cost) + + + def set_priority(self, interface, priority): + """ + Set interface path priority, only relevant for STP enabled interfaces + + Example: + + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').priority(4) + """ + return self._write_sysfs('/sys/class/net/{}/brif/{}/priority' + .format(self._ifname, interface), priority) + +class BondIf(Interface): + def __init__(self, ifname): + super().__init__(ifname, type='bond') + + @property + def xmit_hash_policy(self): + """ + Selects the transmit hash policy to use for slave selection in + balance-xor, 802.3ad, and tlb modes. Possible values are: layer2, + layer2+3, layer3+4, encap2+3, encap3+4. + + The default value is layer2 + + Example: + >>> from vyos.ifconfig import BondIf + >>> BondIf('bond0').xmit_hash_policy + 'layer3+4' + """ + # Linux Kernel appends has policy value to string, e.g. 'layer3+4 1', + # so remove the later part and only return the mode as string. + return self._read_sysfs('/sys/class/net/{}/bonding/xmit_hash_policy' + .format(self._ifname)).split(' ')[0] + + @xmit_hash_policy.setter + def xmit_hash_policy(self, mode): + """ + Selects the transmit hash policy to use for slave selection in + balance-xor, 802.3ad, and tlb modes. Possible values are: layer2, + layer2+3, layer3+4, encap2+3, encap3+4. + + The default value is layer2 + + Example: + >>> from vyos.ifconfig import Interface + >>> BondIf('bond0').xmit_hash_policy = 'layer2+3' + >>> BondIf('bond0').proxy_arp + '1' + """ + if not mode in ['layer2', 'layer2+3', 'layer3+4', 'encap2+3', 'encap3+4']: + raise ValueError() + return self._write_sysfs('/sys/class/net/{}/bonding/xmit_hash_policy' + .format(self._ifname), mode) + + @property + def arp_interval(self): + """ + Specifies the ARP link monitoring frequency in milliseconds. + + The ARP monitor works by periodically checking the slave devices to + determine whether they have sent or received traffic recently (the + precise criteria depends upon the bonding mode, and the state of the + slave). Regular traffic is generated via ARP probes issued for the + addresses specified by the arp_ip_target option. + + The default value is 0. + + Example: + >>> from vyos.ifconfig import BondIf + >>> BondIf('bond0').arp_interval + '0' + """ + return self._read_sysfs('/sys/class/net/{}/bonding/arp_interval' + .format(self._ifname)) + + @arp_interval.setter + def arp_interval(self, time): + """ + Specifies the IP addresses to use as ARP monitoring peers when + arp_interval is > 0. These are the targets of the ARP request sent to + determine the health of the link to the targets. Specify these values + in ddd.ddd.ddd.ddd format. Multiple IP addresses must be separated by + a comma. At least one IP address must be given for ARP monitoring to + function. The maximum number of targets that can be specified is 16. + + The default value is no IP addresses. + + Example: + >>> from vyos.ifconfig import Interface + >>> BondIf('bond0').arp_interval = '100' + >>> BondIf('bond0').arp_interval + '100' + """ + return self._write_sysfs('/sys/class/net/{}/bonding/arp_interval' + .format(self._ifname), time) + + @property + def arp_ip_target(self): + """ + Specifies the IP addresses to use as ARP monitoring peers when + arp_interval is > 0. These are the targets of the ARP request sent to + determine the health of the link to the targets. Specify these values + in ddd.ddd.ddd.ddd format. Multiple IP addresses must be separated by + a comma. At least one IP address must be given for ARP monitoring to + function. The maximum number of targets that can be specified is 16. + + The default value is no IP addresses. + + Example: + >>> from vyos.ifconfig import BondIf + >>> BondIf('bond0').arp_ip_target + '192.0.2.1' + """ + return self._read_sysfs('/sys/class/net/{}/bonding/arp_ip_target' + .format(self._ifname)) + + @arp_ip_target.setter + def arp_ip_target(self, target): + """ + Specifies the IP addresses to use as ARP monitoring peers when + arp_interval is > 0. These are the targets of the ARP request sent to + determine the health of the link to the targets. Specify these values + in ddd.ddd.ddd.ddd format. Multiple IP addresses must be separated by + a comma. At least one IP address must be given for ARP monitoring to + function. The maximum number of targets that can be specified is 16. + + The default value is no IP addresses. + + Example: + >>> from vyos.ifconfig import Interface + >>> BondIf('bond0').arp_ip_target = '192.0.2.1' + >>> BondIf('bond0').arp_ip_target + '192.0.2.1' + """ + return self._write_sysfs('/sys/class/net/{}/bonding/arp_ip_target' + .format(self._ifname), mode) -# TODO: dhcpv6-pd via dhclient |