diff options
25 files changed, 296 insertions, 133 deletions
diff --git a/interface-definitions/interfaces-bonding.xml.in b/interface-definitions/interfaces-bonding.xml.in index 166f23285..07a6abd30 100644 --- a/interface-definitions/interfaces-bonding.xml.in +++ b/interface-definitions/interfaces-bonding.xml.in @@ -7,7 +7,7 @@ <help>Bonding Interface/Link Aggregation</help> <priority>320</priority> <constraint> - <regex>bond[0-9]+$</regex> + <regex>^bond[0-9]+$</regex> </constraint> <constraintErrorMessage>Bonding interface must be named bondN</constraintErrorMessage> <valueHelp> diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in index f41110a3f..818bc9c0e 100644 --- a/interface-definitions/interfaces-bridge.xml.in +++ b/interface-definitions/interfaces-bridge.xml.in @@ -5,9 +5,9 @@ <tagNode name="bridge" owner="${vyos_conf_scripts_dir}/interfaces-bridge.py"> <properties> <help>Bridge Interface</help> - <priority>470</priority> + <priority>489</priority> <constraint> - <regex>br[0-9]+$</regex> + <regex>^br[0-9]+$</regex> </constraint> <constraintErrorMessage>Bridge interface must be named brN</constraintErrorMessage> <valueHelp> diff --git a/interface-definitions/interfaces-dummy.xml.in b/interface-definitions/interfaces-dummy.xml.in index 5229e602a..135adfc10 100644 --- a/interface-definitions/interfaces-dummy.xml.in +++ b/interface-definitions/interfaces-dummy.xml.in @@ -7,7 +7,7 @@ <help>Dummy Interface</help> <priority>300</priority> <constraint> - <regex>dum[0-9]+$</regex> + <regex>^dum[0-9]+$</regex> </constraint> <constraintErrorMessage>Dummy interface must be named dumN</constraintErrorMessage> <valueHelp> diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in index 2b461cfaa..f8ec26d04 100644 --- a/interface-definitions/interfaces-ethernet.xml.in +++ b/interface-definitions/interfaces-ethernet.xml.in @@ -7,7 +7,7 @@ <help>Ethernet Interface</help> <priority>318</priority> <constraint> - <regex>((eth|lan)[0-9]+|(eno|ens|enp|enx).+)$</regex> + <regex>^((eth|lan)[0-9]+|(eno|ens|enp|enx).+)$</regex> </constraint> <constraintErrorMessage>Invalid Ethernet interface name</constraintErrorMessage> <valueHelp> diff --git a/interface-definitions/interfaces-geneve.xml.in b/interface-definitions/interfaces-geneve.xml.in index a6406ffc9..31a3ebb7a 100644 --- a/interface-definitions/interfaces-geneve.xml.in +++ b/interface-definitions/interfaces-geneve.xml.in @@ -7,7 +7,7 @@ <help>Generic Network Virtualization Encapsulation (GENEVE) Interface</help> <priority>460</priority> <constraint> - <regex>gnv[0-9]+$</regex> + <regex>^gnv[0-9]+$</regex> </constraint> <constraintErrorMessage>GENEVE interface must be named gnvN</constraintErrorMessage> <valueHelp> diff --git a/interface-definitions/interfaces-l2tpv3.xml.in b/interface-definitions/interfaces-l2tpv3.xml.in index 161a37df8..30dd9b604 100644 --- a/interface-definitions/interfaces-l2tpv3.xml.in +++ b/interface-definitions/interfaces-l2tpv3.xml.in @@ -5,9 +5,9 @@ <tagNode name="l2tpv3" owner="${vyos_conf_scripts_dir}/interfaces-l2tpv3.py"> <properties> <help>Layer 2 Tunnel Protocol Version 3 (L2TPv3) Interface</help> - <priority>800</priority> + <priority>485</priority> <constraint> - <regex>l2tpeth[0-9]+$</regex> + <regex>^l2tpeth[0-9]+$</regex> </constraint> <constraintErrorMessage>L2TPv3 interface must be named l2tpethN</constraintErrorMessage> <valueHelp> diff --git a/interface-definitions/interfaces-loopback.xml.in b/interface-definitions/interfaces-loopback.xml.in index ddbfad763..97d5bab90 100644 --- a/interface-definitions/interfaces-loopback.xml.in +++ b/interface-definitions/interfaces-loopback.xml.in @@ -7,7 +7,7 @@ <help>Loopback Interface</help> <priority>300</priority> <constraint> - <regex>lo$</regex> + <regex>^lo$</regex> </constraint> <constraintErrorMessage>Loopback interface must be named lo</constraintErrorMessage> <valueHelp> diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in index cd5b5f29e..92bac3fab 100644 --- a/interface-definitions/interfaces-openvpn.xml.in +++ b/interface-definitions/interfaces-openvpn.xml.in @@ -7,7 +7,7 @@ <help>OpenVPN Tunnel Interface</help> <priority>460</priority> <constraint> - <regex>vtun[0-9]+$</regex> + <regex>^vtun[0-9]+$</regex> </constraint> <constraintErrorMessage>OpenVPN tunnel interface must be named vtunN</constraintErrorMessage> <valueHelp> diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in index bbaff5f04..bc7995918 100644 --- a/interface-definitions/interfaces-pppoe.xml.in +++ b/interface-definitions/interfaces-pppoe.xml.in @@ -7,7 +7,7 @@ <help>Point-to-Point Protocol over Ethernet (PPPoE)</help> <priority>321</priority> <constraint> - <regex>pppoe[0-9]+$</regex> + <regex>^pppoe[0-9]+$</regex> <validator name="numeric" argument="--range 1-99"/> </constraint> <constraintErrorMessage>PPPoE interface must be named pppoeN</constraintErrorMessage> diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in index 772056bd2..0c56e4e4b 100644 --- a/interface-definitions/interfaces-pseudo-ethernet.xml.in +++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in @@ -7,7 +7,7 @@ <help>Pseudo Ethernet</help> <priority>319</priority> <constraint> - <regex>peth[0-9]+$</regex> + <regex>^peth[0-9]+$</regex> </constraint> <constraintErrorMessage>Pseudo Ethernet interface must be named pethN</constraintErrorMessage> <valueHelp> diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in index fe4a81f92..3ba82067f 100644 --- a/interface-definitions/interfaces-tunnel.xml.in +++ b/interface-definitions/interfaces-tunnel.xml.in @@ -7,7 +7,7 @@ <help>Tunnel interface</help> <priority>380</priority> <constraint> - <regex>tun[0-9]+$</regex> + <regex>^tun[0-9]+$</regex> </constraint> <constraintErrorMessage>tunnel interface must be named tunN</constraintErrorMessage> <valueHelp> diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index 70c45d1fd..3108817b3 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -7,7 +7,7 @@ <help>Virtual Extensible LAN (VXLAN) Interface</help> <priority>460</priority> <constraint> - <regex>vxlan[0-9]+$</regex> + <regex>^vxlan[0-9]+$</regex> </constraint> <constraintErrorMessage>VXLAN interface must be named vxlanN</constraintErrorMessage> <valueHelp> diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in index dd4a73efd..d461156b3 100644 --- a/interface-definitions/interfaces-wireguard.xml.in +++ b/interface-definitions/interfaces-wireguard.xml.in @@ -7,7 +7,7 @@ <help>WireGuard Interface</help> <priority>459</priority> <constraint> - <regex>wg[0-9]+$</regex> + <regex>^wg[0-9]+$</regex> </constraint> <constraintErrorMessage>WireGuard interface must be named wgN</constraintErrorMessage> <valueHelp> diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in index 2c224987e..12736510b 100644 --- a/interface-definitions/interfaces-wireless.xml.in +++ b/interface-definitions/interfaces-wireless.xml.in @@ -7,7 +7,7 @@ <help>Wireless (WiFi/WLAN) Network Interface</help> <priority>400</priority> <constraint> - <regex>wlan[0-9]+$</regex> + <regex>^wlan[0-9]+$</regex> </constraint> <constraintErrorMessage>Wireless interface must be named wlanN</constraintErrorMessage> <valueHelp> diff --git a/interface-definitions/interfaces-wirelessmodem.xml.in b/interface-definitions/interfaces-wirelessmodem.xml.in index cea8f4029..2ec9205e0 100644 --- a/interface-definitions/interfaces-wirelessmodem.xml.in +++ b/interface-definitions/interfaces-wirelessmodem.xml.in @@ -7,7 +7,7 @@ <help>Wireless Modem (WWAN) Interface</help> <priority>350</priority> <constraint> - <regex>wlm[0-9]+$</regex> + <regex>^wlm[0-9]+$</regex> </constraint> <constraintErrorMessage>Wireless Modem interface must be named wlmN</constraintErrorMessage> <valueHelp> @@ -43,6 +43,7 @@ </node> #include <include/interface-description.xml.i> #include <include/interface-disable.xml.i> + #include <include/interface-vrf.xml.i> <leafNode name="device"> <properties> <help>System device name (default: ttyUSB0)</help> diff --git a/interface-definitions/vrf.xml.in b/interface-definitions/vrf.xml.in index 76748e5ae..7c75bf824 100644 --- a/interface-definitions/vrf.xml.in +++ b/interface-definitions/vrf.xml.in @@ -17,9 +17,9 @@ <properties> <help>VRF instance name</help> <constraint> - <regex>[^/\s]{1,16}$</regex> + <validator name="vrf-name"/> </constraint> - <constraintErrorMessage>VRF instance name must be 16 characters or less</constraintErrorMessage> + <constraintErrorMessage>VRF instance name must be 16 characters or less and can not\nbe named as regular network interfaces</constraintErrorMessage> <valueHelp> <format>name</format> <description>Instance name</description> @@ -44,4 +44,4 @@ </tagNode> </children> </node> -</interfaceDefinition>
\ No newline at end of file +</interfaceDefinition> diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py index d08a8b528..1f9956af0 100644 --- a/python/vyos/ifconfig/__init__.py +++ b/python/vyos/ifconfig/__init__.py @@ -36,3 +36,4 @@ from vyos.ifconfig.tunnel import IP6IP6If from vyos.ifconfig.tunnel import SitIf from vyos.ifconfig.tunnel import Sit6RDIf from vyos.ifconfig.wireless import WiFiIf +from vyos.ifconfig.l2tpv3 import L2TPv3If diff --git a/python/vyos/ifconfig/l2tpv3.py b/python/vyos/ifconfig/l2tpv3.py index 07f1cf8a3..f0d64a53d 100644 --- a/python/vyos/ifconfig/l2tpv3.py +++ b/python/vyos/ifconfig/l2tpv3.py @@ -41,25 +41,27 @@ class L2TPv3If(Interface): } } options = Interface.options + \ - ['tunnel_id', 'peer_tunnel_id', 'local_port', 'remote_port', 'encapsulation', 'local_address', 'remote_address'] + ['tunnel_id', 'peer_tunnel_id', 'local_port', 'remote_port', + 'encapsulation', 'local_address', 'remote_address', 'session_id', + 'peer_session_id'] def _create(self): # create tunnel interface - cmd = 'ip l2tp add tunnel tunnel_id {} '.format(config['tunnel_id']) - cmd += 'peer_tunnel_id {} '.format(config['peer_tunnel_id']) - cmd += 'udp_sport {} '.format(config['local_port']) - cmd += 'udp_dport {} '.format(config['remote_port']) - cmd += 'encap {} '.format(config['encapsulation']) - cmd += 'local {} '.format(config['local_address']) - cmd += 'remote {} '.format(config['remote_address']) - self._cmd(cmd) + cmd = 'ip l2tp add tunnel tunnel_id {tunnel_id}' + cmd += ' peer_tunnel_id {peer_tunnel_id}' + cmd += ' udp_sport {local_port}' + cmd += ' udp_dport {remote_port}' + cmd += ' encap {encapsulation}' + cmd += ' local {local_address}' + cmd += ' remote {remote_address}' + self._cmd(cmd.format(**self.config)) # setup session - cmd = 'ip l2tp add session name {} '.format(self.config['ifname']) - cmd += 'tunnel_id {} '.format(config['tunnel_id']) - cmd += 'session_id {} '.format(config['session_id']) - cmd += 'peer_session_id {} '.format(config['peer_session_id']) - self._cmd(cmd) + cmd = 'ip l2tp add session name {ifname}' + cmd += ' tunnel_id {tunnel_id}' + cmd += ' session_id {session_id}' + cmd += ' peer_session_id {peer_session_id}' + self._cmd(cmd.format(**self.config)) # interface is always A/D down. It needs to be enabled explicitly self.set_admin_state('down') diff --git a/python/vyos/util.py b/python/vyos/util.py index 67aa87a3a..3970b8bf1 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -16,15 +16,6 @@ import os import re import sys -import psutil - -import vyos.defaults - -from getpass import getuser -from grp import getgrnam -from time import sleep -from subprocess import check_output -from ipaddress import ip_network def read_file(path): """ Read a file to string """ @@ -33,6 +24,27 @@ def read_file(path): return data +def chown_file(path, user, group): + """ change file owner """ + from pwd import getpwnam + from grp import getgrnam + + if os.path.isfile(path): + uid = getpwnam(user).pw_uid + gid = getgrnam(group).gr_gid + os.chown(path, uid, gid) + + +def chmod_x_file(path): + """ make file executable """ + from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP, S_IROTH, S_IXOTH + + if os.path.isfile(path): + bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | \ + S_IROTH | S_IXOTH + os.chmod(path, bitmask) + + def colon_separated_to_dict(data_string, uniquekeys=False): """ Converts a string containing newline-separated entries of colon-separated key-value pairs into a dict. @@ -84,11 +96,12 @@ def colon_separated_to_dict(data_string, uniquekeys=False): def process_running(pid_file): """ Checks if a process with PID in pid_file is running """ + from psutil import pid_exists if not os.path.isfile(pid_file): return False with open(pid_file, 'r') as f: pid = f.read().strip() - return psutil.pid_exists(int(pid)) + return pid_exists(int(pid)) def seconds_to_human(s, separator=""): @@ -132,7 +145,10 @@ def seconds_to_human(s, separator=""): def get_cfg_group_id(): - group_data = getgrnam(vyos.defaults.cfg_group) + from grp import getgrnam + from vyos.defaults import cfg_group + + group_data = getgrnam(cfg_group) return group_data.gr_gid @@ -162,18 +178,22 @@ def commit_in_progress(): # Since this will be used in scripts that modify the config outside of the CLI # framework, those knowingly have root permissions. # For everything else, we add a safeguard. + from subprocess import check_output + from psutil import process_iter, NoSuchProcess + from vyos.defaults import commit_lock + id = check_output(['/usr/bin/id', '-u']).decode().strip() if id != '0': raise OSError("This functions needs root permissions to return correct results") - for proc in psutil.process_iter(): + for proc in process_iter(): try: files = proc.open_files() if files: for f in files: - if f.path == vyos.defaults.commit_lock: + if f.path == commit_lock: return True - except psutil.NoSuchProcess as err: + except NoSuchProcess as err: # Process died before we could examine it pass # Default case @@ -182,7 +202,7 @@ def commit_in_progress(): def wait_for_commit_lock(): """ Not to be used in normal op mode scripts! """ - + from time import sleep # Very synchronous approach to multiprocessing while commit_in_progress(): sleep(1) @@ -206,17 +226,20 @@ def ask_yes_no(question, default=False) -> bool: def is_admin() -> bool: """Look if current user is in sudo group""" + from getpass import getuser + from grp import getgrnam current_user = getuser() (_, _, _, admin_group_members) = getgrnam('sudo') return current_user in admin_group_members def mac2eui64(mac, prefix=None): - ''' + """ Convert a MAC address to a EUI64 address or, with prefix provided, a full IPv6 address. Thankfully copied from https://gist.github.com/wido/f5e32576bb57b5cc6f934e177a37a0d3 - ''' + """ + from ipaddress import ip_network # http://tools.ietf.org/html/rfc4291#section-2.5.1 eui64 = re.sub(r'[.:-]', '', mac).lower() eui64 = eui64[0:6] + 'fffe' + eui64[6:] diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index f318614db..9c045534c 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -20,12 +20,10 @@ from sys import exit from copy import deepcopy from jinja2 import Template from subprocess import Popen, PIPE -from pwd import getpwnam -from grp import getgrnam -from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP, S_IROTH, S_IXOTH from vyos.config import Config from vyos.ifconfig import Interface +from vyos.util import chown_file, chmod_x_file from vyos import ConfigError from netifaces import interfaces @@ -112,15 +110,11 @@ if [ "$6" != "{{ intf }}" ]; then exit fi +{% if ipv6_autoconf -%} # add some info to syslog DIALER_PID=$(cat /var/run/{{ intf }}.pid) logger -t pppd[$DIALER_PID] "executing $0" -logger -t pppd[$DIALER_PID] "configuring dialer interface $6 via $2" - -echo "{{ description }}" > /sys/class/net/{{ intf }}/ifalias - -{% if ipv6_autoconf -%} - +logger -t pppd[$DIALER_PID] "configuring interface {{ intf }} via $2" # Configure interface-specific Host/Router behaviour. # Note: It is recommended to have the same setting on all interfaces; mixed @@ -150,12 +144,12 @@ echo 1 > /proc/sys/net/ipv6/conf/{{ intf }}/autoconfigure {% endif %} """ -config_pppoe_ip_pre_up_tmpl = """#!/bin/sh +config_pppoe_ip_up_tmpl = """#!/bin/sh # As PPPoE is an "on demand" interface we need to re-configure it when it # becomes up -if [ "$6" != "pppoe0" ]; then +if [ "$6" != "{{ intf }}" ]; then exit fi @@ -163,6 +157,8 @@ fi DIALER_PID=$(cat /var/run/{{ intf }}.pid) logger -t pppd[$DIALER_PID] "executing $0" +echo "{{ description }}" > /sys/class/net/{{ intf }}/ifalias + {% if vrf -%} logger -t pppd[$DIALER_PID] "configuring dialer interface $6 for VRF {{ vrf }}" ip link set dev {{ intf }} master {{ vrf }} @@ -305,61 +301,49 @@ def verify(pppoe): return None def generate(pppoe): - config_file_pppoe = f"/etc/ppp/peers/{pppoe['intf']}" - ip_pre_up_script_file = f"/etc/ppp/ip-pre-up.d/9999-vyos-vrf-{pppoe['intf']}" - ipv6_if_up_script_file = f"/etc/ppp/ipv6-up.d/50-vyos-{pppoe['intf']}-autoconf" + intf = pppoe['intf'] + config_file_pppoe = f'/etc/ppp/peers/{intf}' + ip_up_script_file = f'/etc/ppp/ip-up.d/9990-vyos-vrf-{intf}' + ipv6_if_up_script_file = f'/etc/ppp/ipv6-up.d/9990-vyos-autoconf-{intf}' + + config_files = [config_file_pppoe, ip_up_script_file, ipv6_if_up_script_file] + + # Ensure directories for config files exist - otherwise create them on demand + for file in config_files: + dirname = os.path.dirname(file) + if not os.path.isdir(dirname): + os.mkdir(dirname) # Always hang-up PPPoE connection prior generating new configuration file - cmd = f"systemctl stop ppp@{pppoe['intf']}.service" + cmd = f'systemctl stop ppp@{intf}.service' subprocess_cmd(cmd) if pppoe['deleted']: # Delete PPP configuration files - if os.path.exists(config_file_pppoe): - os.unlink(config_file_pppoe) - - if os.path.exists(ipv6_if_up_script_file): - os.unlink(ipv6_if_up_script_file) - - if os.path.exists(ip_pre_up_script_file): - os.unlink(ip_pre_up_script_file) + for file in config_files: + if os.path.exists(file): + os.unlink(file) else: - # PPP peers directory - dirname = os.path.dirname(config_file_pppoe) - if not os.path.isdir(dirname): - os.mkdir(dirname) - # Create PPP configuration files tmpl = Template(config_pppoe_tmpl) config_text = tmpl.render(pppoe) with open(config_file_pppoe, 'w') as f: f.write(config_text) - # PPP ip-pre-up.d scripting directory - dirname = os.path.dirname(ip_pre_up_script_file) - if not os.path.isdir(dirname): - os.mkdir(dirname) - - tmpl = Template(config_pppoe_ip_pre_up_tmpl) + tmpl = Template(config_pppoe_ip_up_tmpl) config_text = tmpl.render(pppoe) - with open(ip_pre_up_script_file, 'w') as f: + with open(ip_up_script_file, 'w') as f: f.write(config_text) - # PPP ipv6-up.d scripting directory - dirname = os.path.dirname(ipv6_if_up_script_file) - if not os.path.isdir(dirname): - os.mkdir(dirname) - tmpl = Template(config_pppoe_ipv6_up_tmpl) config_text = tmpl.render(pppoe) with open(ipv6_if_up_script_file, 'w') as f: f.write(config_text) - bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | \ - S_IROTH | S_IXOTH - os.chmod(ip_pre_up_script_file, bitmask) - os.chmod(ipv6_if_up_script_file, bitmask) + # make generated script file executable + chmod_x_file(ip_up_script_file) + chmod_x_file(ipv6_if_up_script_file) return None @@ -369,15 +353,13 @@ def apply(pppoe): return None if not pppoe['disable']: - # dial PPPoE connection - cmd = f"systemctl start ppp@{pppoe['intf']}.service" + # "dial" PPPoE connection + intf = pppoe['intf'] + cmd = f'systemctl start ppp@{intf}.service' subprocess_cmd(cmd) # make logfile owned by root / vyattacfg - if os.path.isfile(pppoe['logfile']): - uid = getpwnam('root').pw_uid - gid = getgrnam('vyattacfg').gr_gid - os.chown(pppoe['logfile'], uid, gid) + chown_file(pppoe['logfile'], 'root', 'vyattacfg') return None diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 4cbb51f4a..646e61c53 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -138,6 +138,9 @@ default_config_data = { 'ip': False, 'ipv6': False, 'nhrp': [], + 'ipv6_autoconf': 0, + 'ipv6_forwarding': 1, + 'ipv6_dad_transmits': 1, # internal 'tunnel': {}, # the following names are exactly matching the name @@ -183,6 +186,9 @@ mapping = { 'link_detect': ('disable-link-detect', False, 2), 'vrf': ('vrf', False, None), 'addresses-add': ('address', True, None), + 'ipv6_autoconf': ('ipv6 address autoconf', False, 1), + 'ipv6_forwarding': ('ipv6 disable-forwarding', False, 0), + 'ipv6_dad_transmits:': ('ipv6 dup-addr-detect-transmits', False, None) } def get_class (options): @@ -468,7 +474,8 @@ def apply(conf): tunnel.set_interface(option, options[option]) # set other interface properties - for option in ('alias', 'mtu', 'link_detect', 'multicast', 'allmulticast', 'vrf'): + for option in ('alias', 'mtu', 'link_detect', 'multicast', 'allmulticast', + 'vrf', 'ipv6_autoconf', 'ipv6_forwarding', 'ipv6_dad_transmits'): tunnel.set_interface(option, options[option]) # Configure interface address(es) diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py index 9efad3b8d..7aee5c9bd 100755 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ b/src/conf_mode/interfaces-wirelessmodem.py @@ -20,10 +20,10 @@ from sys import exit from copy import deepcopy from jinja2 import Template from subprocess import Popen, PIPE -from pwd import getpwnam -from grp import getgrnam +from netifaces import interfaces from vyos.config import Config +from vyos.util import chown_file, chmod_x_file from vyos import ConfigError # Please be careful if you edit the template. @@ -70,6 +70,32 @@ CONNECT '' """ +config_wwan_ip_up_tmpl = """#!/bin/sh +# As WWAN is an "on demand" interface we need to re-configure it when it +# becomes 'up' + +ipparam=$6 + +# device name and metric are received using ipparam +device=`echo "$ipparam"|awk '{ print $1 }'` + +if [ "$device" != "{{ intf }}" ]; then + exit +fi + +# add some info to syslog +DIALER_PID=$(cat /var/run/{{ intf }}.pid) +logger -t pppd[$DIALER_PID] "executing $0" + +echo "{{ description }}" > /sys/class/net/{{ intf }}/ifalias + +{% if vrf -%} +logger -t pppd[$DIALER_PID] "configuring interface {{ intf }} for VRF {{ vrf }}" +ip link set dev {{ intf }} master {{ vrf }} +{% endif %} + +""" + default_config_data = { 'address': [], 'apn': '', @@ -84,7 +110,8 @@ default_config_data = { 'metric': '10', 'mtu': '1500', 'name_server': True, - 'intf': '' + 'intf': '', + 'vrf': '' } def subprocess_cmd(command): @@ -154,6 +181,10 @@ def get_config(): if conf.exists(['ondemand']): wwan['on_demand'] = True + # retrieve VRF instance + if conf.exists('vrf'): + wwan['vrf'] = conf.return_value(['vrf']) + return wwan def verify(wwan): @@ -168,41 +199,58 @@ def verify(wwan): if not os.path.exists(f"/dev/{wwan['device']}"): raise ConfigError(f"Device {wwan['device']} does not exist") + vrf_name = wwan['vrf'] + if vrf_name and vrf_name not in interfaces(): + raise ConfigError(f'VRF {vrf_name} does not exist') + return None def generate(wwan): - config_file_wwan = f"/etc/ppp/peers/{wwan['intf']}" + intf = wwan['intf'] + config_file_wwan = f'/etc/ppp/peers/{intf}' + config_file_wwan_chat = wwan['chat_script'] + ip_up_script_file = f'/etc/ppp/ip-up.d/9991-vyos-vrf-{intf}' + + config_files = [config_file_wwan, config_file_wwan_chat, ip_up_script_file] + + # Ensure directories for config files exist - otherwise create them on demand + for file in config_files: + dirname = os.path.dirname(file) + if not os.path.isdir(dirname): + os.mkdir(dirname) # Always hang-up WWAN connection prior generating new configuration file - cmd = f"systemctl stop ppp@{wwan['intf']}.service" + cmd = f'systemctl stop ppp@{intf}.service' subprocess_cmd(cmd) if wwan['deleted']: # Delete PPP configuration files - if os.path.exists(config_file_wwan): - os.unlink(config_file_wwan) - if os.path.exists(wwan['chat_script']): - os.unlink(wwan['chat_script']) + for file in config_files: + if os.path.exists(file): + os.unlink(file) else: - # PPP peers directory - dirname = os.path.dirname(config_file_wwan) - if not os.path.isdir(dirname): - os.mkdir(dirname) - # Create PPP configuration files tmpl = Template(config_wwan_tmpl) config_text = tmpl.render(wwan) with open(config_file_wwan, 'w') as f: f.write(config_text) - # Create PPP chat script tmpl = Template(chat_wwan_tmpl) config_text = tmpl.render(wwan) with open(wwan['chat_script'], 'w') as f: f.write(config_text) + # Create ip-pre-up script + tmpl = Template(config_wwan_ip_up_tmpl) + config_text = tmpl.render(wwan) + with open(ip_up_script_file, 'w') as f: + f.write(config_text) + + # make generated script file executable + chmod_x_file(ip_up_script_file) + return None def apply(wwan): @@ -211,15 +259,13 @@ def apply(wwan): return None if not wwan['disable']: - # dial WWAN connection - cmd = f"systemctl start ppp@{wwan['intf']}.service" + # "dial" WWAN connection + intf = wwan['intf'] + cmd = f'systemctl start ppp@{intf}.service' subprocess_cmd(cmd) # make logfile owned by root / vyattacfg - if os.path.isfile(wwan['logfile']): - uid = getpwnam('root').pw_uid - gid = getgrnam('vyattacfg').gr_gid - os.chown(wwan['logfile'], uid, gid) + chown_file(wwan['logfile'], 'root', 'vyattacfg') return None diff --git a/src/migration-scripts/quagga/5-to-6 b/src/migration-scripts/quagga/5-to-6 new file mode 100755 index 000000000..a71851942 --- /dev/null +++ b/src/migration-scripts/quagga/5-to-6 @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 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/>. + +# * Remove parameter 'disable-network-import-check' which, as implemented, +# had no effect on boot. + +import sys + +from vyos.configtree import ConfigTree + + +if (len(sys.argv) < 2): + print("Must specify file name!") + sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +config = ConfigTree(config_file) + +if not config.exists(['protocols', 'bgp']): + # Nothing to do + sys.exit(0) +else: + # Check if BGP is actually configured and obtain the ASN + asn_list = config.list_nodes(['protocols', 'bgp']) + if asn_list: + # There's always just one BGP node, if any + asn = asn_list[0] + else: + # There's actually no BGP, just its empty shell + sys.exit(0) + + # Check if BGP parameter disable-network-import-check exists + param = ['protocols', 'bgp', asn, 'parameters', 'disable-network-import-check'] + if config.exists(param): + # Delete parameter + config.delete(param) + else: + # Do nothing + sys.exit(0) + + try: + with open(file_name, 'w') as f: + f.write(config.to_string()) + except OSError as e: + print("Failed to save the modified config: {}".format(e)) + sys.exit(1) diff --git a/src/validators/mac-address b/src/validators/mac-address index d6ec6d712..435920b84 100755 --- a/src/validators/mac-address +++ b/src/validators/mac-address @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2020 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 @@ -13,16 +13,14 @@ # # 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 sys import re +from sys import exit, argv -if len(sys.argv) == 2: - pattern = "^([0-9A-Fa-f]{2}[:]){5}([0-9A-Fa-f]{2})$" - if re.match(pattern, sys.argv[1]): - sys.exit(0) - else: - sys.exit(1) +if len(argv) == 2: + pattern = "^([0-9A-Fa-f]{2}[:]){5}([0-9A-Fa-f]{2})$" + if re.match(pattern, argv[1]): + exit(0) + else: + exit(1) diff --git a/src/validators/vrf-name b/src/validators/vrf-name new file mode 100755 index 000000000..11c453f4d --- /dev/null +++ b/src/validators/vrf-name @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 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 re +from sys import exit, argv + +if len(argv) == 2: + len = len(argv[1]) + # VRF instance name must be 16 characters or less, python range needs to be + # extended by one + if not len in range(1, 17): + exit(1) + + # Treat loopback interface "lo" explicitly. Adding "lo" explicitly to the + # following regex pattern would deny any VRF name starting with lo - thuse + # local-vrf would be illegal - and that we do not want. + if argv[1] == "lo": + exit(1) + + # VRF instances should not be named after regular interface names like bond0, + # br10 and so on - this can cause a lot of confusion/trouble + pattern = "^(?!(bond|br|dum|eth|lan|eno|ens|enp|enx|gnv|ipoe|l2tp|l2tpeth|" \ + "vtun|ppp|pppoe|peth|tun|vti|vxlan|wg|wlan|wlm)\d+(\.\d+(v.+)?)?$).*$" + if re.match(pattern, argv[1]): + exit(0) + +exit(1) |