summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile1
-rw-r--r--debian/control1
-rw-r--r--debian/vyos-1x.install1
-rw-r--r--interface-definitions/https.xml.in6
-rw-r--r--interface-definitions/interfaces-wirelessmodem.xml.in80
-rw-r--r--interface-definitions/vrf.xml.in10
-rw-r--r--op-mode-definitions/show-interfaces-pppoe.xml9
-rw-r--r--op-mode-definitions/show-interfaces-wirelessmodem.xml45
-rw-r--r--python/vyos/config.py15
-rw-r--r--python/vyos/configdict.py5
-rw-r--r--python/vyos/configtree.py62
-rw-r--r--python/vyos/ifconfig/wireless.py2
-rw-r--r--python/vyos/ifconfig_vlan.py2
-rw-r--r--python/vyos/migrator.py11
-rw-r--r--python/vyos/util.py2
-rwxr-xr-xsrc/completion/list_wlm_peers.sh6
-rwxr-xr-xsrc/conf_mode/https.py18
-rwxr-xr-xsrc/conf_mode/interfaces-ethernet.py2
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py9
-rwxr-xr-xsrc/conf_mode/interfaces-pppoe.py15
-rwxr-xr-xsrc/conf_mode/interfaces-wireless.py30
-rwxr-xr-xsrc/conf_mode/interfaces-wirelessmodem.py235
-rwxr-xr-xsrc/conf_mode/vpn_sstp.py12
-rwxr-xr-xsrc/conf_mode/vrf.py7
-rwxr-xr-xsrc/etc/ppp/ip-down.d/0020-wirelessmodem18
-rwxr-xr-xsrc/etc/ppp/ip-up.d/0020-wirelessmodem18
-rw-r--r--src/etc/udev/rules.d/99-vyos-wwan.rules11
-rwxr-xr-xsrc/helpers/run-config-migration.py19
-rwxr-xr-xsrc/helpers/vyos-load-config.py8
-rwxr-xr-xsrc/helpers/vyos-merge-config.py10
-rwxr-xr-xsrc/migration-scripts/interfaces/6-to-763
-rwxr-xr-xsrc/op_mode/reset_openvpn.py21
-rw-r--r--tests/data/config.valid4
33 files changed, 622 insertions, 136 deletions
diff --git a/Makefile b/Makefile
index 71c28f220..62bcd473f 100644
--- a/Makefile
+++ b/Makefile
@@ -70,6 +70,7 @@ interface_definitions: $(BUILD_DIR) $(obj)
rm -f $(TMPL_DIR)/interfaces/wireless/node.tag/ipv6/node.def
rm -f $(TMPL_DIR)/interfaces/wireless/node.tag/vif/node.tag/ip/node.def
rm -f $(TMPL_DIR)/interfaces/wireless/node.tag/vif/node.tag/ipv6/node.def
+ rm -f $(TMPL_DIR)/interfaces/wirelessmodem/node.tag/ipv6/node.def
rm -f $(TMPL_DIR)/protocols/node.def
rm -f $(TMPL_DIR)/protocols/static/node.def
rm -f $(TMPL_DIR)/system/node.def
diff --git a/debian/control b/debian/control
index 366e8df94..bccfc02d4 100644
--- a/debian/control
+++ b/debian/control
@@ -79,6 +79,7 @@ Depends: python3,
frr,
radvd,
dbus,
+ usb-modeswitch,
hostapd (>= 0.6.8),
wpasupplicant (>= 0.6.7),
iw,
diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install
index 5bb7ea507..5004d111f 100644
--- a/debian/vyos-1x.install
+++ b/debian/vyos-1x.install
@@ -3,6 +3,7 @@ etc/init.d
etc/ppp
etc/rsyslog.d
etc/systemd
+etc/udev
etc/vyos
lib/
opt/
diff --git a/interface-definitions/https.xml.in b/interface-definitions/https.xml.in
index 1d986b2b4..49bd25b82 100644
--- a/interface-definitions/https.xml.in
+++ b/interface-definitions/https.xml.in
@@ -111,6 +111,12 @@
<hidden/>
</properties>
</leafNode>
+ <leafNode name="virtual-host">
+ <properties>
+ <help>Restrict proxy to virtual host(s)</help>
+ <multi/>
+ </properties>
+ </leafNode>
</children>
</node>
<node name="certificates">
diff --git a/interface-definitions/interfaces-wirelessmodem.xml.in b/interface-definitions/interfaces-wirelessmodem.xml.in
new file mode 100644
index 000000000..cea8f4029
--- /dev/null
+++ b/interface-definitions/interfaces-wirelessmodem.xml.in
@@ -0,0 +1,80 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="interfaces">
+ <children>
+ <tagNode name="wirelessmodem" owner="${vyos_conf_scripts_dir}/interfaces-wirelessmodem.py">
+ <properties>
+ <help>Wireless Modem (WWAN) Interface</help>
+ <priority>350</priority>
+ <constraint>
+ <regex>wlm[0-9]+$</regex>
+ </constraint>
+ <constraintErrorMessage>Wireless Modem interface must be named wlmN</constraintErrorMessage>
+ <valueHelp>
+ <format>wlmN</format>
+ <description>Wireless modem interface name</description>
+ </valueHelp>
+ </properties>
+ <children>
+ <leafNode name="apn">
+ <properties>
+ <help>Access Point Name (APN)</help>
+ </properties>
+ </leafNode>
+ <node name="backup">
+ <properties>
+ <help>Insert backup default route</help>
+ </properties>
+ <children>
+ <leafNode name="distance">
+ <properties>
+ <help>Distance backup default route</help>
+ <valueHelp>
+ <format>1-255</format>
+ <description>Distance of the backup route (default: 10) </description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-255"/>
+ </constraint>
+ <constraintErrorMessage>Must be between (1-255)</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ #include <include/interface-description.xml.i>
+ #include <include/interface-disable.xml.i>
+ <leafNode name="device">
+ <properties>
+ <help>System device name (default: ttyUSB0)</help>
+ <valueHelp>
+ <format>ttyXXX</format>
+ <description>System TTY device name</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ #include <include/interface-disable-link-detect.xml.i>
+ #include <include/interface-mtu-68-9000.xml.i>
+ <node name="ipv6">
+ <children>
+ #include <include/ipv6-address.xml.i>
+ #include <include/ipv6-disable-forwarding.xml.i>
+ #include <include/ipv6-dup-addr-detect-transmits.xml.i>
+ </children>
+ </node>
+ <leafNode name="no-peer-dns">
+ <properties>
+ <help>Do not use peer supplied DNS server information</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ondemand">
+ <properties>
+ <help>Only dial when traffic is available</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/vrf.xml.in b/interface-definitions/vrf.xml.in
index f1895598e..76748e5ae 100644
--- a/interface-definitions/vrf.xml.in
+++ b/interface-definitions/vrf.xml.in
@@ -29,14 +29,14 @@
<leafNode name="table">
<properties>
<help>Routing table associated with this instance</help>
- <constraint>
- <validator name="numeric" argument="--range 1-2147483647"/>
- </constraint>
- <constraintErrorMessage>Invalid kernel table number</constraintErrorMessage>
<valueHelp>
- <format>1-2147483647</format>
+ <format>100-2147483647</format>
<description>Routing table ID</description>
</valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 100-2147483647"/>
+ </constraint>
+ <constraintErrorMessage>VRF routing table must be in range from 100 to 2147483647</constraintErrorMessage>
</properties>
</leafNode>
#include <include/interface-description.xml.i>
diff --git a/op-mode-definitions/show-interfaces-pppoe.xml b/op-mode-definitions/show-interfaces-pppoe.xml
index 3acb14486..591ec8f5b 100644
--- a/op-mode-definitions/show-interfaces-pppoe.xml
+++ b/op-mode-definitions/show-interfaces-pppoe.xml
@@ -27,6 +27,15 @@
</leafNode>
</children>
</node>
+ <leafNode name="statistics">
+ <properties>
+ <help>Show specified wirelessmodem interface statistics</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_pppoe_peers.sh</script>
+ </completionHelp>
+ </properties>
+ <command>/usr/sbin/pppstats $4</command>
+ </leafNode>
</children>
</tagNode>
</children>
diff --git a/op-mode-definitions/show-interfaces-wirelessmodem.xml b/op-mode-definitions/show-interfaces-wirelessmodem.xml
new file mode 100644
index 000000000..681f54f3d
--- /dev/null
+++ b/op-mode-definitions/show-interfaces-wirelessmodem.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="interfaces">
+ <children>
+ <tagNode name="wirelessmodem">
+ <properties>
+ <help>Show Wireless Modem (WWAN) interface information</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_wlm_peers.sh</script>
+ </completionHelp>
+ </properties>
+ <command>${vyatta_bindir}/vyatta-show-interfaces.pl --intf="$4"</command>
+ <children>
+ <node name="log">
+ <properties>
+ <help>Show PPPoE logs</help>
+ </properties>
+ <command>cat /var/log/vyatta/ppp_$4.log</command>
+ <children>
+ <leafNode name="tail">
+ <properties>
+ <help>Watch PPPoE logs</help>
+ </properties>
+ <command>tail --follow=name /var/log/vyatta/ppp_$4.log</command>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="statistics">
+ <properties>
+ <help>Show specified wirelessmodem interface statistics</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_wlm_peers.sh</script>
+ </completionHelp>
+ </properties>
+ <command>/usr/sbin/pppstats $4</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/python/vyos/config.py b/python/vyos/config.py
index 2342f7021..75055a603 100644
--- a/python/vyos/config.py
+++ b/python/vyos/config.py
@@ -238,6 +238,19 @@ class Config(object):
str: working configuration
"""
+ # show_config should be independent of CLI edit level.
+ # Set the CLI edit environment to the top level, and
+ # restore original on exit.
+ save_env = self.__session_env
+
+ env_str = self._run(self._make_command('getEditResetEnv', ''))
+ env_list = re.findall(r'([A-Z_]+)=\'([^;\s]+)\'', env_str)
+ root_env = os.environ
+ for k, v in env_list:
+ root_env[k] = v
+
+ self.__session_env = root_env
+
# FIXUP: by default, showConfig will give you a diff
# if there are uncommitted changes.
# The config parser obviously cannot work with diffs,
@@ -253,8 +266,10 @@ class Config(object):
path = " ".join(path)
try:
out = self._run(self._make_command('showConfig', path))
+ self.__session_env = save_env
return out
except VyOSError:
+ self.__session_env = save_env
return(default)
def get_config_dict(self, path=[], effective=False):
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index 66da52ff3..24fe174d2 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -123,6 +123,7 @@ def vlan_to_dict(conf):
'ip_enable_arp_accept': 0,
'ip_enable_arp_announce': 0,
'ip_enable_arp_ignore': 0,
+ 'ip_proxy_arp': 0,
'ipv6_autoconf': 0,
'ipv6_forwarding': 1,
'ipv6_dup_addr_detect': 1,
@@ -190,6 +191,10 @@ def vlan_to_dict(conf):
if conf.exists('ip enable-arp-ignore'):
vlan['ip_enable_arp_ignore'] = 1
+ # Enable Proxy ARP
+ if conf.exists('ip enable-proxy-arp'):
+ vlan['ip_proxy_arp'] = 1
+
# Enable acquisition of IPv6 address using stateless autoconfig (SLAAC)
if conf.exists('ipv6 address autoconf'):
vlan['ipv6_autoconf'] = 1
diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py
index 0274f3573..a0b0eb3c1 100644
--- a/python/vyos/configtree.py
+++ b/python/vyos/configtree.py
@@ -24,58 +24,10 @@ def escape_backslash(string: str) -> str:
result = p.sub(r'\\\\', string)
return result
-def strip_comments(s):
- """ Split a config string into the config section and the trailing comments """
- INITIAL = 0
- IN_COMMENT = 1
-
- i = len(s) - 1
-
- state = INITIAL
-
- config_end = 0
-
- # Find the first character of the comments section at the end,
- # if it exists
- while (i >= 0):
- c = s[i]
-
- if (state == INITIAL) and re.match(r'\s', c):
- # Ignore whitespace
- if (i != 0):
- i -= 1
- else:
- config_end = 0
- break
- elif (state == INITIAL) and not re.match(r'(\s|\/)', c):
- # Assume there are no (more) trailing comments,
- # this is an end of a node: either a brace of the last character
- # of a leaf node value
- config_end = i + 1
- break
- elif (state == INITIAL) and (c == '/'):
- # A comment begins, or it's a stray slash
- if (s[i-1] == '*'):
- state = IN_COMMENT
- i -= 2
- else:
- raise ValueError("Invalid syntax: stray slash at character {0}".format(i + 1))
- elif (state == IN_COMMENT) and (c == '*'):
- # A comment ends here
- try:
- if (s[i-1] == '/'):
- state = INITIAL
- i -= 2
- except:
- raise ValueError("Invalid syntax: malformed commend end at character {0}".format(i + 1))
- elif (state == IN_COMMENT) and (c != '*'):
- # Ignore everything inside comments, including braces
- i -= 1
- else:
- # Shouldn't happen
- raise ValueError("Invalid syntax at character {0}: invalid character {1}".format(i + 1, c))
-
- return (s[0:config_end], s[config_end+1:])
+def extract_version(s):
+ """ Extract the version string from the config string """
+ t = re.split('(^//)', s, maxsplit=1, flags=re.MULTILINE)
+ return (s, ''.join(t[1:]))
def check_path(path):
# Necessary type checking
@@ -174,7 +126,7 @@ class ConfigTree(object):
self.__destroy = self.__lib.destroy
self.__destroy.argtypes = [c_void_p]
- config_section, comments_section = strip_comments(config_string)
+ config_section, version_section = extract_version(config_string)
config_section = escape_backslash(config_section)
config = self.__from_string(config_section.encode())
if config is None:
@@ -182,7 +134,7 @@ class ConfigTree(object):
raise ValueError("Failed to parse config: {0}".format(msg))
else:
self.__config = config
- self.__comments = comments_section
+ self.__version = version_section
def __del__(self):
if self.__config is not None:
@@ -193,7 +145,7 @@ class ConfigTree(object):
def to_string(self):
config_string = self.__to_string(self.__config).decode()
- config_string = "{0}\n{1}".format(config_string, self.__comments)
+ config_string = "{0}\n{1}".format(config_string, self.__version)
return config_string
def to_commands(self):
diff --git a/python/vyos/ifconfig/wireless.py b/python/vyos/ifconfig/wireless.py
index 932d07d01..946ae1642 100644
--- a/python/vyos/ifconfig/wireless.py
+++ b/python/vyos/ifconfig/wireless.py
@@ -47,7 +47,7 @@ class WiFiIf(Interface):
self._cmd(cmd)
# wireless interface is administratively down by default
- self.set_state('down')
+ self.set_admin_state('down')
def _delete(self):
cmd = 'iw dev {ifname} del' \
diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py
index 00270cf58..ed22646c1 100644
--- a/python/vyos/ifconfig_vlan.py
+++ b/python/vyos/ifconfig_vlan.py
@@ -64,6 +64,8 @@ def apply_vlan_config(vlan, config):
vlan.set_arp_announce(config['ip_enable_arp_announce'])
# configure ARP ignore
vlan.set_arp_ignore(config['ip_enable_arp_ignore'])
+ # configure Proxy ARP
+ vlan.set_proxy_arp(config['ip_proxy_arp'])
# IPv6 address autoconfiguration
vlan.set_ipv6_autoconf(config['ipv6_autoconf'])
# IPv6 forwarding
diff --git a/python/vyos/migrator.py b/python/vyos/migrator.py
index f05228041..9a5fdef2f 100644
--- a/python/vyos/migrator.py
+++ b/python/vyos/migrator.py
@@ -25,7 +25,7 @@ class MigratorError(Exception):
pass
class Migrator(object):
- def __init__(self, config_file, force=False, set_vintage=None):
+ def __init__(self, config_file, force=False, set_vintage='vyos'):
self._config_file = config_file
self._force = force
self._set_vintage = set_vintage
@@ -61,9 +61,6 @@ class Migrator(object):
if self._set_vintage:
self._config_file_vintage = self._set_vintage
- if not self._config_file_vintage:
- self._config_file_vintage = vyos.defaults.cfg_vintage
-
if self._config_file_vintage not in ['vyatta', 'vyos']:
raise MigratorError("Unknown vintage.")
@@ -204,16 +201,12 @@ class Migrator(object):
return self._changed
class VirtualMigrator(Migrator):
- def __init__(self, config_file, vintage='vyos'):
- super().__init__(config_file, set_vintage = vintage)
-
def run(self):
cfg_file = self._config_file
cfg_versions = self.read_config_file_versions()
if not cfg_versions:
- raise MigratorError("Config file has no version information;"
- " virtual migration not possible.")
+ return
if self.update_vintage():
self._changed = True
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 635b11ee5..67aa87a3a 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -84,6 +84,8 @@ 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 """
+ 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))
diff --git a/src/completion/list_wlm_peers.sh b/src/completion/list_wlm_peers.sh
new file mode 100755
index 000000000..12dd00650
--- /dev/null
+++ b/src/completion/list_wlm_peers.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+if [ -d /etc/ppp/peers ]; then
+ cd /etc/ppp/peers
+ ls wlm*
+fi
diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py
index a0fe9cf2f..889b62cf4 100755
--- a/src/conf_mode/https.py
+++ b/src/conf_mode/https.py
@@ -96,6 +96,7 @@ server {
"""
default_server_block = {
+ 'id' : '',
'address' : '*',
'port' : '443',
'name' : ['_'],
@@ -117,6 +118,7 @@ def get_config():
else:
for vhost in conf.list_nodes('virtual-host'):
server_block = deepcopy(default_server_block)
+ server_block['id'] = vhost
if conf.exists(f'virtual-host {vhost} listen-address'):
addr = conf.return_value(f'virtual-host {vhost} listen-address')
server_block['address'] = addr
@@ -156,9 +158,21 @@ def get_config():
if conf.exists('api port'):
port = conf.return_value('api port')
api_data['port'] = port
+ if conf.exists('api virtual-host'):
+ vhosts = conf.return_values('api virtual-host')
+ api_data['vhost'] = vhosts[:]
+
if api_data:
- for block in server_block_list:
- block['api'] = api_data
+ # we do not want to include 'vhost' key as part of
+ # vyos.defaults.api_data, so check for key existence
+ vhost_list = api_data.get('vhost')
+ if vhost_list is None:
+ for block in server_block_list:
+ block['api'] = api_data
+ else:
+ for block in server_block_list:
+ if block['id'] in vhost_list:
+ block['api'] = api_data
https = {'server_block_list' : server_block_list, 'certbot': certbot}
return https
diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py
index 286cab88e..15e9b4185 100755
--- a/src/conf_mode/interfaces-ethernet.py
+++ b/src/conf_mode/interfaces-ethernet.py
@@ -359,7 +359,7 @@ def apply(eth):
# if custom mac is removed
if eth['mac']:
e.set_mac(eth['mac'])
- else:
+ elif eth['hw_id']:
e.set_mac(eth['hw_id'])
# Maximum Transmission Unit (MTU)
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index 17aa4697f..fb2d6e6d9 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -24,7 +24,6 @@ from stat import S_IRUSR,S_IRWXU,S_IRGRP,S_IXGRP,S_IROTH,S_IXOTH
from grp import getgrnam
from ipaddress import ip_address,ip_network,IPv4Interface
from netifaces import interfaces
-from psutil import pid_exists
from pwd import getpwnam
from subprocess import Popen, PIPE
from time import sleep
@@ -33,6 +32,7 @@ from shutil import rmtree
from vyos import ConfigError
from vyos.config import Config
from vyos.ifconfig import VTunIf
+from vyos.util import process_running
from vyos.validate import is_addr_assigned
user = 'openvpn'
@@ -977,17 +977,12 @@ def generate(openvpn):
return None
def apply(openvpn):
- pid = 0
pidfile = '/var/run/openvpn/{}.pid'.format(openvpn['intf'])
- if os.path.isfile(pidfile):
- pid = 0
- with open(pidfile, 'r') as f:
- pid = int(f.read())
# Always stop OpenVPN service. We can not send a SIGUSR1 for restart of the
# service as the configuration is not re-read. Stop daemon only if it's
# running - it could have died or killed by someone evil
- if pid_exists(pid):
+ if process_running(pidfile):
cmd = 'start-stop-daemon'
cmd += ' --stop '
cmd += ' --quiet'
diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py
index f948070ee..f318614db 100755
--- a/src/conf_mode/interfaces-pppoe.py
+++ b/src/conf_mode/interfaces-pppoe.py
@@ -325,17 +325,32 @@ def generate(pppoe):
os.unlink(ip_pre_up_script_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)
config_text = tmpl.render(pppoe)
with open(ip_pre_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:
diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py
index 2c67c39ae..b6e62b0aa 100755
--- a/src/conf_mode/interfaces-wireless.py
+++ b/src/conf_mode/interfaces-wireless.py
@@ -25,15 +25,15 @@ from grp import getgrnam
from re import findall
from subprocess import Popen, PIPE
-from psutil import pid_exists
from netifaces import interfaces
from netaddr import *
-from vyos.ifconfig import WiFiIf
-from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config
+from vyos import ConfigError
from vyos.configdict import list_diff, vlan_to_dict
from vyos.config import Config
-from vyos import ConfigError
+from vyos.ifconfig import WiFiIf
+from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config
+from vyos.util import process_running
user = 'root'
group = 'vyattacfg'
@@ -1364,15 +1364,9 @@ def verify(wifi):
return None
def generate(wifi):
- pid = 0
# always stop hostapd service first before reconfiguring it
pidfile = get_pid('hostapd', wifi['intf'])
- if os.path.isfile(pidfile):
- pid = 0
- with open(pidfile, 'r') as f:
- pid = int(f.read())
-
- if pid_exists(pid):
+ if process_running(pidfile):
cmd = 'start-stop-daemon'
cmd += ' --stop '
cmd += ' --quiet'
@@ -1382,12 +1376,7 @@ def generate(wifi):
# always stop wpa_supplicant service first before reconfiguring it
pidfile = get_pid('wpa_supplicant', wifi['intf'])
- if os.path.isfile(pidfile):
- pid = 0
- with open(pidfile, 'r') as f:
- pid = int(f.read())
-
- if pid_exists(pid):
+ if process_running(pidfile):
cmd = 'start-stop-daemon'
cmd += ' --stop '
cmd += ' --quiet'
@@ -1409,7 +1398,10 @@ def generate(wifi):
# http://wiki.stocksy.co.uk/wiki/Multiple_SSIDs_with_hostapd
# generate locally administered MAC address from used phy interface
with open('/sys/class/ieee80211/{}/addresses'.format(wifi['phy']), 'r') as f:
- tmp = EUI(f.read().rstrip()).value
+ # some PHYs tend to have multiple interfaces and thus supply multiple MAC
+ # addresses - we only need the first one for our calculation
+ tmp = f.readline().rstrip()
+ tmp = EUI(tmp).value
# mask last nibble from the MAC address
tmp &= 0xfffffffffff0
# set locally administered bit in MAC address
@@ -1496,7 +1488,7 @@ def apply(wifi):
# if custom mac is removed
if wifi['mac']:
w.set_mac(wifi['mac'])
- else:
+ elif wifi['hw_id']:
w.set_mac(wifi['hw_id'])
# configure ARP filter configuration
diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py
new file mode 100755
index 000000000..9efad3b8d
--- /dev/null
+++ b/src/conf_mode/interfaces-wirelessmodem.py
@@ -0,0 +1,235 @@
+#!/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 os
+
+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 vyos.config import Config
+from vyos import ConfigError
+
+# Please be careful if you edit the template.
+config_wwan_tmpl = """### Autogenerated by interfaces-wirelessmodem.py ###
+{% if description %}
+# {{ description }}
+{% endif %}
+ifname {{ intf }}
+ipparam "{{ intf }} {{ metric }}"
+linkname {{ intf }}
+{% if name_server -%}
+usepeerdns
+{%- endif %}
+# physical device
+/dev/{{ device }}
+lcp-echo-failure 0
+115200
+debug
+logfile {{ logfile }}
+nodefaultroute
+ipcp-max-failure 4
+ipcp-accept-local
+ipcp-accept-remote
+noauth
+crtscts
+lock
+persist
+{% if on_demand -%}
+demand
+{%- endif %}
+
+connect '/usr/sbin/chat -v -t6 -f {{ chat_script }}'
+
+"""
+
+# Please be careful if you edit the template.
+chat_wwan_tmpl = """
+ABORT 'NO DIAL TONE' ABORT 'NO ANSWER' ABORT 'NO CARRIER' ABORT DELAYED
+'' AT
+OK ATZ
+OK 'AT+CGDCONT=1,"IP","{{ apn }}"'
+OK ATD*99#
+CONNECT ''
+
+"""
+
+default_config_data = {
+ 'address': [],
+ 'apn': '',
+ 'chat_script': '',
+ 'deleted': False,
+ 'description': '',
+ 'device': 'ttyUSB0',
+ 'disable': False,
+ 'disable_link_detect': 1,
+ 'on_demand': False,
+ 'logfile': '',
+ 'metric': '10',
+ 'mtu': '1500',
+ 'name_server': True,
+ 'intf': ''
+}
+
+def subprocess_cmd(command):
+ p = Popen(command, stdout=PIPE, shell=True)
+ p.communicate()
+
+def check_kmod():
+ modules = ['option', 'usb_wwan', 'usbserial']
+ for module in modules:
+ if not os.path.exists(f'/sys/module/{module}'):
+ if os.system(f'modprobe {module}') != 0:
+ raise ConfigError(f'Loading Kernel module {module} failed')
+
+def get_config():
+ wwan = deepcopy(default_config_data)
+ conf = Config()
+
+ # determine tagNode instance
+ if 'VYOS_TAGNODE_VALUE' not in os.environ:
+ raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified')
+
+ wwan['intf'] = os.environ['VYOS_TAGNODE_VALUE']
+ wwan['logfile'] = f"/var/log/vyatta/ppp_{wwan['intf']}.log"
+ wwan['chat_script'] = f"/etc/ppp/peers/chat.{wwan['intf']}"
+
+ # Check if interface has been removed
+ if not conf.exists('interfaces wirelessmodem ' + wwan['intf']):
+ wwan['deleted'] = True
+ return wwan
+
+ # set new configuration level
+ conf.set_level('interfaces wirelessmodem ' + wwan['intf'])
+
+ # get metrick for backup default route
+ if conf.exists(['apn']):
+ wwan['apn'] = conf.return_value(['apn'])
+
+ # get metrick for backup default route
+ if conf.exists(['backup', 'distance']):
+ wwan['metric'] = conf.return_value(['backup', 'distance'])
+
+ # Retrieve interface description
+ if conf.exists(['description']):
+ wwan['description'] = conf.return_value(['description'])
+
+ # System device name
+ if conf.exists(['device']):
+ wwan['device'] = conf.return_value(['device'])
+
+ # disable interface
+ if conf.exists('disable'):
+ wwan['disable'] = True
+
+ # ignore link state changes
+ if conf.exists('disable-link-detect'):
+ wwan['disable_link_detect'] = 2
+
+ # Do not use DNS servers provided by the peer
+ if conf.exists(['mtu']):
+ wwan['mtu'] = conf.return_value(['mtu'])
+
+ # Do not use DNS servers provided by the peer
+ if conf.exists(['no-peer-dns']):
+ wwan['name_server'] = False
+
+ # Access concentrator name (only connect to this concentrator)
+ if conf.exists(['ondemand']):
+ wwan['on_demand'] = True
+
+ return wwan
+
+def verify(wwan):
+ if wwan['deleted']:
+ return None
+
+ if not wwan['apn']:
+ raise ConfigError(f"APN for {wwan['intf']} not configured")
+
+ # we can not use isfile() here as Linux device files are no regular files
+ # thus the check will return False
+ if not os.path.exists(f"/dev/{wwan['device']}"):
+ raise ConfigError(f"Device {wwan['device']} does not exist")
+
+ return None
+
+def generate(wwan):
+ config_file_wwan = f"/etc/ppp/peers/{wwan['intf']}"
+
+ # Always hang-up WWAN connection prior generating new configuration file
+ cmd = f"systemctl stop ppp@{wwan['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'])
+
+ 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)
+
+ return None
+
+def apply(wwan):
+ if wwan['deleted']:
+ # bail out early
+ return None
+
+ if not wwan['disable']:
+ # dial WWAN connection
+ cmd = f"systemctl start ppp@{wwan['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)
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ check_kmod()
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py
index 8e5c7587c..070437443 100755
--- a/src/conf_mode/vpn_sstp.py
+++ b/src/conf_mode/vpn_sstp.py
@@ -23,9 +23,9 @@ from subprocess import Popen, PIPE, check_output
from socket import socket, AF_INET, SOCK_STREAM
from copy import deepcopy
from stat import S_IRUSR, S_IWUSR, S_IRGRP
-from psutil import pid_exists
from vyos.config import Config
+from vyos.util import process_running
from vyos import ConfigError
pidfile = r'/var/run/accel_sstp.pid'
@@ -489,14 +489,8 @@ def generate(sstp):
return sstp
def apply(sstp):
- pid = 0
- if os.path.isfile(pidfile):
- pid = 0
- with open(pidfile, 'r') as f:
- pid = int(f.read())
-
if sstp is None:
- if pid_exists(pid):
+ if process_running(pidfile):
cmd = 'start-stop-daemon'
cmd += ' --stop '
cmd += ' --quiet'
@@ -509,7 +503,7 @@ def apply(sstp):
return None
- if not pid_exists(pid):
+ if not process_running(pidfile):
if os.path.exists(pidfile):
os.remove(pidfile)
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index 991c5cb2c..a74b79317 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -213,9 +213,10 @@ def apply(vrf_config):
_cmd(f'sysctl -wq net.ipv4.tcp_l3mdev_accept={bind_all}')
_cmd(f'sysctl -wq net.ipv4.udp_l3mdev_accept={bind_all}')
- for vrf_name in vrf_config['vrf_remove']:
- if os.path.isdir(f'/sys/class/net/{vrf_name}'):
- _cmd(f'ip link delete dev {vrf_name}')
+ for vrf in vrf_config['vrf_remove']:
+ name = vrf['name']
+ if os.path.isdir(f'/sys/class/net/{name}'):
+ _cmd(f'ip link delete dev {name}')
for vrf in vrf_config['vrf_add']:
name = vrf['name']
diff --git a/src/etc/ppp/ip-down.d/0020-wirelessmodem b/src/etc/ppp/ip-down.d/0020-wirelessmodem
new file mode 100755
index 000000000..c93c7cabe
--- /dev/null
+++ b/src/etc/ppp/ip-down.d/0020-wirelessmodem
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+tty=$2
+ipparam=$6
+
+# Only applicable for Wireless Modems (WWAN)
+if [ -z "$(echo $tty | egrep "tty(USB|ACM)")" ]; then
+ exit 0
+fi
+
+# device name and metric are received using ipparam
+device=`echo "$ipparam"|awk '{ print $1 }'`
+metric=`echo "$ipparam"|awk '{ print $2 }'`
+
+vtysh -c "conf t" -c "no ip route 0.0.0.0/0 ${device} ${metric}"
+
+DIALER_PID=$(cat /var/run/${device}.pid)
+logger -t pppd[$DIALER_PID] "removed default route via $device metric $metric"
diff --git a/src/etc/ppp/ip-up.d/0020-wirelessmodem b/src/etc/ppp/ip-up.d/0020-wirelessmodem
new file mode 100755
index 000000000..95549387b
--- /dev/null
+++ b/src/etc/ppp/ip-up.d/0020-wirelessmodem
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+tty=$2
+ipparam=$6
+
+# Only applicable for Wireless Modems (WWAN)
+if [ -z "$(echo $tty | egrep "tty(USB|ACM)")" ]; then
+ exit 0
+fi
+
+# device name and metric are received using ipparam
+device=`echo "$ipparam"|awk '{ print $1 }'`
+metric=`echo "$ipparam"|awk '{ print $2 }'`
+
+vtysh -c "conf t" -c "ip route 0.0.0.0/0 ${device} ${metric}"
+
+DIALER_PID=$(cat /var/run/${device}.pid)
+logger -t pppd[$DIALER_PID] "added default route via $device metric $metric"
diff --git a/src/etc/udev/rules.d/99-vyos-wwan.rules b/src/etc/udev/rules.d/99-vyos-wwan.rules
new file mode 100644
index 000000000..67f30a3dd
--- /dev/null
+++ b/src/etc/udev/rules.d/99-vyos-wwan.rules
@@ -0,0 +1,11 @@
+ACTION!="add|change", GOTO="mbim_to_qmi_rules_end"
+
+SUBSYSTEM!="usb", GOTO="mbim_to_qmi_rules_end"
+
+# ignore any device with only one configuration
+ATTR{bNumConfigurations}=="1", GOTO="mbim_to_qmi_rules_end"
+
+# force Sierra Wireless MC7710 to configuration #1
+ATTR{idVendor}=="1199",ATTR{idProduct}=="68a2",ATTR{bConfigurationValue}="1"
+
+LABEL="mbim_to_qmi_rules_end"
diff --git a/src/helpers/run-config-migration.py b/src/helpers/run-config-migration.py
index a57a19cdf..3c06e38f8 100755
--- a/src/helpers/run-config-migration.py
+++ b/src/helpers/run-config-migration.py
@@ -69,15 +69,22 @@ def main():
sys.exit(1)
if not virtual:
- migration = Migrator(config_file_name, force=force_on,
- set_vintage=vintage)
+ virtual_migration = VirtualMigrator(config_file_name)
+ virtual_migration.run()
+
+ migration = Migrator(config_file_name, force=force_on)
+ migration.run()
+
+ if not migration.config_changed():
+ os.remove(backup_file_name)
else:
- migration = VirtualMigrator(config_file_name)
+ virtual_migration = VirtualMigrator(config_file_name,
+ set_vintage=vintage)
- migration.run()
+ virtual_migration.run()
- if not migration._changed:
- os.remove(backup_file_name)
+ if not virtual_migration.config_changed():
+ os.remove(backup_file_name)
if __name__ == '__main__':
main()
diff --git a/src/helpers/vyos-load-config.py b/src/helpers/vyos-load-config.py
index 4e6d67efa..693529c23 100755
--- a/src/helpers/vyos-load-config.py
+++ b/src/helpers/vyos-load-config.py
@@ -28,7 +28,7 @@ import tempfile
import vyos.defaults
import vyos.remote
from vyos.config import Config, VyOSError
-from vyos.migrator import Migrator, MigratorError
+from vyos.migrator import Migrator, VirtualMigrator, MigratorError
system_config_file = 'config.boot'
@@ -73,6 +73,12 @@ with tempfile.NamedTemporaryFile() as fp:
with open(fp.name, 'w') as fd:
fd.write(config_file)
+ virtual_migration = VirtualMigrator(fp.name)
+ try:
+ virtual_migration.run()
+ except MigratorError as err:
+ sys.exit('{}'.format(err))
+
migration = Migrator(fp.name)
try:
migration.run()
diff --git a/src/helpers/vyos-merge-config.py b/src/helpers/vyos-merge-config.py
index c5216daa6..10a5ea4bc 100755
--- a/src/helpers/vyos-merge-config.py
+++ b/src/helpers/vyos-merge-config.py
@@ -21,9 +21,9 @@ import subprocess
import tempfile
import vyos.defaults
import vyos.remote
-import vyos.migrator
from vyos.config import Config
from vyos.configtree import ConfigTree
+from vyos.migrator import Migrator, VirtualMigrator
if (len(sys.argv) < 2):
@@ -61,9 +61,13 @@ with tempfile.NamedTemporaryFile() as file_to_migrate:
with open(file_to_migrate.name, 'w') as fd:
fd.write(config_file)
- migration = vyos.migrator.Migrator(file_to_migrate.name)
+ virtual_migration = VirtualMigrator(file_to_migrate.name)
+ virtual_migration.run()
+
+ migration = Migrator(file_to_migrate.name)
migration.run()
- if migration.config_changed():
+
+ if virtual_migration.config_changed() or migration.config_changed():
with open(file_to_migrate.name, 'r') as fd:
config_file = fd.read()
diff --git a/src/migration-scripts/interfaces/6-to-7 b/src/migration-scripts/interfaces/6-to-7
new file mode 100755
index 000000000..b4f59c363
--- /dev/null
+++ b/src/migration-scripts/interfaces/6-to-7
@@ -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 network provider name from CLI and rather use provider APN from CLI
+
+import sys
+from vyos.configtree import ConfigTree
+
+if __name__ == '__main__':
+ if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+ file_name = sys.argv[1]
+ with open(file_name, 'r') as f:
+ config_file = f.read()
+
+ config = ConfigTree(config_file)
+ base = ['interfaces', 'wirelessmodem']
+
+ if not config.exists(base):
+ # Nothing to do
+ sys.exit(0)
+
+ # list all individual interface types like dummy, ethernet and so on
+ for i in config.list_nodes(base):
+ iface = base + [i]
+
+ # only three carries have been supported in the past, thus
+ # this will be fairly simple \o/ - and only one (AT&T) did
+ # configure an APN
+ if config.exists(iface + ['network']):
+ network = config.return_value(iface + ['network'])
+ if network == "att":
+ apn = 'isp.cingular'
+ config.set(iface + ['apn'], value=apn)
+
+ config.delete(iface + ['network'])
+
+ # synchronize DNS configuration with PPPoE interfaces to have a
+ # uniform CLI experience
+ if config.exists(iface + ['no-dns']):
+ config.rename(iface + ['no-dns'], 'no-peer-dns')
+
+ 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/op_mode/reset_openvpn.py b/src/op_mode/reset_openvpn.py
index 176cd51cf..4c29fbbba 100755
--- a/src/op_mode/reset_openvpn.py
+++ b/src/op_mode/reset_openvpn.py
@@ -17,10 +17,10 @@
import sys
import os
-from psutil import pid_exists
from subprocess import Popen, PIPE
from time import sleep
from netifaces import interfaces
+from vyos.util import process_running
def get_config_name(intf):
cfg_file = r'/opt/vyatta/etc/openvpn/openvpn-{}.conf'.format(intf)
@@ -42,18 +42,13 @@ if __name__ == '__main__':
interface = sys.argv[1]
if os.path.isfile(get_config_name(interface)):
pidfile = '/var/run/openvpn/{}.pid'.format(interface)
- if os.path.isfile(pidfile):
- pid = 0
- with open(pidfile, 'r') as f:
- pid = int(f.read())
-
- if pid_exists(pid):
- cmd = 'start-stop-daemon'
- cmd += ' --stop'
- cmd += ' --oknodo'
- cmd += ' --quiet'
- cmd += ' --pidfile ' + pidfile
- subprocess_cmd(cmd)
+ if process_running(pidfile):
+ cmd = 'start-stop-daemon'
+ cmd += ' --stop'
+ cmd += ' --oknodo'
+ cmd += ' --quiet'
+ cmd += ' --pidfile ' + pidfile
+ subprocess_cmd(cmd)
# When stopping OpenVPN we need to wait for the 'old' interface to
# vanish from the Kernel, if it is not gone, OpenVPN will report:
diff --git a/tests/data/config.valid b/tests/data/config.valid
index a21c6a4d1..1fbdd1505 100644
--- a/tests/data/config.valid
+++ b/tests/data/config.valid
@@ -35,5 +35,5 @@ empty-node {
trailing-leaf-node-without-value
-/* Trailing commend */
-/* Another trailing comment */
+// Trailing comment
+// Another trailing comment