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 | 
