diff options
-rw-r--r-- | debian/control | 1 | ||||
-rw-r--r-- | interface-definitions/interfaces-bridge.xml | 6 | ||||
-rw-r--r-- | python/vyos/configsession.py | 3 | ||||
-rw-r--r-- | python/vyos/ifconfig.py | 1061 | ||||
-rwxr-xr-x | src/conf_mode/interface-bridge.py | 130 | ||||
-rwxr-xr-x | src/conf_mode/interface-dummy.py | 35 | ||||
-rwxr-xr-x | src/conf_mode/interface-loopback.py | 19 | ||||
-rwxr-xr-x | src/conf_mode/ipsec-settings.py | 2 | ||||
-rwxr-xr-x | src/helpers/vyos-boot-config-loader.py | 101 | ||||
-rw-r--r-- | src/systemd/vyos-hostsd.service | 9 |
10 files changed, 996 insertions, 371 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..98633382c 100644 --- a/interface-definitions/interfaces-bridge.xml +++ b/interface-definitions/interfaces-bridge.xml @@ -47,14 +47,14 @@ </leafNode> <leafNode name="aging"> <properties> - <help>Interval addresses are retained</help> + <help>MAC address aging interval</help> <valueHelp> <format>0</format> - <description>Disable retaining address in bridge (always flood)</description> + <description>Disable MAC address learning (always flood)</description> </valueHelp> <valueHelp> <format>10-1000000</format> - <description>Address aging time for bridge seconds (default 300)</description> + <description>MAC address aging time in seconds (default: 300)</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-0 --range 10-1000000"/> 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 diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py index d5661be93..6e48a1382 100755 --- a/src/conf_mode/interface-bridge.py +++ b/src/conf_mode/interface-bridge.py @@ -16,21 +16,20 @@ # # -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 +56,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") @@ -82,8 +81,6 @@ def get_config(): # retrieve interface description if conf.exists('description'): bridge['description'] = conf.return_value('description') - else: - bridge['description'] = bridge['intf'] # Disable this bridge interface if conf.exists('disable'): @@ -107,7 +104,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 +178,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 +216,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..03afdc668 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 = { @@ -61,8 +61,6 @@ def get_config(): # retrieve interface description if conf.exists('description'): dummy['description'] = conf.return_value('description') - else: - dummy['description'] = dummy['intf'] # Disable this interface if conf.exists('disable'): @@ -83,45 +81,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..be47324c1 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 @@ -57,8 +57,6 @@ def get_config(): # retrieve interface description if conf.exists('description'): loopback['description'] = conf.return_value('description') - else: - loopback['description'] = loopback['intf'] # Determine interface addresses (currently effective) - to determine which # address is no longer valid and needs to be removed from the interface @@ -75,28 +73,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__': diff --git a/src/conf_mode/ipsec-settings.py b/src/conf_mode/ipsec-settings.py index 8d25e7abd..156bb2edd 100755 --- a/src/conf_mode/ipsec-settings.py +++ b/src/conf_mode/ipsec-settings.py @@ -62,7 +62,7 @@ conn {{ra_conn_name}} left={{outside_addr}} leftsubnet=%dynamic[/1701] rightsubnet=%dynamic - mark=%unique + mark_in=%unique auto=add ike=aes256-sha1-modp1024,3des-sha1-modp1024,3des-sha1-modp1024! dpddelay=15 diff --git a/src/helpers/vyos-boot-config-loader.py b/src/helpers/vyos-boot-config-loader.py new file mode 100755 index 000000000..06c95765f --- /dev/null +++ b/src/helpers/vyos-boot-config-loader.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +import os +import sys +import subprocess +import traceback + +from vyos.configsession import ConfigSession, ConfigSessionError +from vyos.configtree import ConfigTree + +STATUS_FILE = '/tmp/vyos-config-status' +TRACE_FILE = '/tmp/boot-config-trace' + +session = ConfigSession(os.getpid(), 'vyos-boot-config-loader') +env = session.get_session_env() + +default_file_name = env['vyatta_sysconfdir'] + '/config.boot.default' + +if len(sys.argv) < 1: + print("Must be called with argument.") + sys.exit(1) +else: + file_name = sys.argv[1] + +def write_config_status(status): + with open(STATUS_FILE, 'w') as f: + f.write('{0}\n'.format(status)) + +def trace_to_file(trace_file_name): + with open(trace_file_name, 'w') as trace_file: + traceback.print_exc(file=trace_file) + +def failsafe(): + try: + with open(default_file_name, 'r') as f: + config_file = f.read() + except Exception as e: + print("Catastrophic: no default config file " + "'{0}'".format(default_file_name)) + sys.exit(1) + + config = ConfigTree(config_file) + if not config.exists(['system', 'login', 'user', 'vyos', + 'authentication', 'encrypted-password']): + print("No password entry in default config file;") + print("unable to recover password for user 'vyos'.") + sys.exit(1) + else: + passwd = config.return_value(['system', 'login', 'user', 'vyos', + 'authentication', + 'encrypted-password']) + + cmd = ("useradd -s /bin/bash -G 'users,sudo' -m -N -p '{0}' " + "vyos".format(passwd)) + try: + subprocess.check_call(cmd, shell=True) + except subprocess.CalledProcessError as e: + sys.exit("{0}".format(e)) + + with open('/etc/motd', 'a+') as f: + f.write('\n\n') + f.write('!!!!!\n') + f.write('There were errors loading the initial configuration;\n') + f.write('please examine the errors in {0} and correct.' + '\n'.format(TRACE_FILE)) + f.write('!!!!!\n\n') + +try: + with open(file_name, 'r') as f: + config_file = f.read() +except Exception as e: + write_config_status(1) + failsafe() + trace_to_file(TRACE_FILE) + sys.exit("{0}".format(e)) + +try: + session.load_config(file_name) + session.commit() + write_config_status(0) +except ConfigSessionError as e: + write_config_status(1) + failsafe() + trace_to_file(TRACE_FILE) + sys.exit(1) diff --git a/src/systemd/vyos-hostsd.service b/src/systemd/vyos-hostsd.service index 3b0fadb5c..2444f5352 100644 --- a/src/systemd/vyos-hostsd.service +++ b/src/systemd/vyos-hostsd.service @@ -1,8 +1,7 @@ [Unit] Description=VyOS DNS configuration keeper -After=auditd.service time-sync.target -Before=network-pre.target vyos-router.service -Wants=network-pre.target +DefaultDependencies=no +After=systemd-remount-fs.service [Service] ExecStart=/usr/bin/python3 -u /usr/libexec/vyos/services/vyos-hostsd @@ -19,6 +18,4 @@ User=root Group=vyattacfg [Install] -# -WantedBy=network.target - +RequiredBy=cloud-init-local.service vyos-router.service |