summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Jenkinsfile8
-rw-r--r--debian/control3
-rw-r--r--interface-definitions/interfaces-openvpn.xml22
-rw-r--r--interface-definitions/ipoe-server.xml326
-rw-r--r--op-mode-definitions/openvpn.xml2
-rw-r--r--python/vyos/ifconfig.py39
-rw-r--r--python/vyos/remote.py26
-rwxr-xr-xsrc/conf_mode/dhcp_server.py142
-rwxr-xr-xsrc/conf_mode/interface-ethernet.py2
-rwxr-xr-xsrc/conf_mode/interface-openvpn.py9
-rwxr-xr-xsrc/conf_mode/interface-wireguard.py6
-rwxr-xr-xsrc/helpers/vyos-load-config.py90
-rwxr-xr-xsrc/op_mode/reset_openvpn.py72
-rwxr-xr-xsrc/services/vyos-hostsd4
-rwxr-xr-xsrc/system/on-dhcp-event.sh42
15 files changed, 502 insertions, 291 deletions
diff --git a/Jenkinsfile b/Jenkinsfile
index b11267ec2..7529d949e 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -87,7 +87,8 @@ pipeline {
steps {
script {
dir('build') {
- git branch: getGitBranchName(), url: getGitRepoURL()
+ git branch: getGitBranchName(),
+ url: getGitRepoURL()
}
}
}
@@ -96,7 +97,10 @@ pipeline {
steps {
script {
dir('build') {
- sh "dpkg-buildpackage -b -us -uc -tc"
+ def commitId = sh(returnStdout: true, script: 'git rev-parse --short=11 HEAD').trim()
+ currentBuild.description = sprintf('Git SHA1: %s', commitId[-11..-1])
+
+ sh 'dpkg-buildpackage -b -us -uc -tc'
}
}
}
diff --git a/debian/control b/debian/control
index dce463157..f7fafd828 100644
--- a/debian/control
+++ b/debian/control
@@ -52,8 +52,7 @@ Depends: python3,
wireguard,
tftpd-hpa,
igmpproxy,
- vyos-accel-ppp,
- vyos-accel-ppp-ipoe-kmod,
+ accel-ppp,
mdns-repeater,
udp-broadcast-relay,
pdns-recursor,
diff --git a/interface-definitions/interfaces-openvpn.xml b/interface-definitions/interfaces-openvpn.xml
index fb2564cbd..365d80558 100644
--- a/interface-definitions/interfaces-openvpn.xml
+++ b/interface-definitions/interfaces-openvpn.xml
@@ -106,7 +106,7 @@
<properties>
<help>Data Encryption Algorithm</help>
<completionHelp>
- <list>des 3des bf128 bf256 aes128 aes192 aes256</list>
+ <list>des 3des bf128 bf256 aes128 aes128gcm aes192 aes192gcm aes256 aes256gcm</list>
</completionHelp>
<valueHelp>
<format>des</format>
@@ -126,18 +126,30 @@
</valueHelp>
<valueHelp>
<format>aes128</format>
- <description>AES algorithm with 128-bit key</description>
+ <description>AES algorithm with 128-bit key CBC</description>
+ </valueHelp>
+ <valueHelp>
+ <format>aes128gcm</format>
+ <description>AES algorithm with 128-bit key GCM</description>
</valueHelp>
<valueHelp>
<format>aes192</format>
- <description>AES algorithm with 192-bit key</description>
+ <description>AES algorithm with 192-bit key CBC</description>
+ </valueHelp>
+ <valueHelp>
+ <format>aes192gcm</format>
+ <description>AES algorithm with 192-bit key GCM</description>
</valueHelp>
<valueHelp>
<format>aes256</format>
- <description>AES algorithm with 256-bit key</description>
+ <description>AES algorithm with 256-bit key CBC</description>
+ </valueHelp>
+ <valueHelp>
+ <format>aes256gcm</format>
+ <description>AES algorithm with 256-bit key GCM</description>
</valueHelp>
<constraint>
- <regex>(des|3des|bf128|bf256|aes128|aes192|aes256)</regex>
+ <regex>(des|3des|bf128|bf256|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm)</regex>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/ipoe-server.xml b/interface-definitions/ipoe-server.xml
index fd84439b5..48f3e0fd9 100644
--- a/interface-definitions/ipoe-server.xml
+++ b/interface-definitions/ipoe-server.xml
@@ -50,7 +50,7 @@
</valueHelp>
<valueHelp>
<format>vlan</format>
- <description>One VLAN per client</description>
+ <description>One VLAN per client</description>
</valueHelp>
</properties>
</leafNode>
@@ -97,7 +97,7 @@
<validator name="numeric" argument="--range 1-4096"/>
</constraint>
<constraintErrorMessage>VLAN ID needs to be between 1 and 4096</constraintErrorMessage>
- <multi />
+ <multi/>
</properties>
</leafNode>
<leafNode name="vlan-range">
@@ -106,7 +106,7 @@
<constraint>
<regex>(409[0-6]|40[0-8][0-9]|[1-3][0-9]{3}|[1-9][0-9]{0,2})-(409[0-6]|40[0-8][0-9]|[1-3][0-9]{3}|[1-9][0-9]{0,2})</regex>
</constraint>
- <multi />
+ <multi/>
</properties>
</leafNode>
</children>
@@ -173,13 +173,13 @@
<leafNode name="prefix">
<properties>
<help>Format: ipv6prefix/mask,prefix_len (e.g.: fc00:0:1::/48,64 - divides prefix into /64 subnets for clients)</help>
- <multi />
+ <multi/>
</properties>
</leafNode>
<leafNode name="delegate-prefix">
<properties>
<help>Format: ipv6prefix/mask,prefix_len (delegates prefix to clients via DHCPv6 prefix delegation</help>
- <multi />
+ <multi/>
</properties>
</leafNode>
</children>
@@ -211,164 +211,164 @@
<description>Authentication disabled</description>
</valueHelp>
</properties>
- </leafNode>
- <tagNode name="interface">
- <properties>
- <help>Network interface the client mac will appear on</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
- </completionHelp>
- </properties>
- <children>
- <tagNode name="mac-address">
- <properties>
- <help>Client mac address allowed to receive an IP address</help>
- <valueHelp>
- <format>h:h:h:h:h:h</format>
- <description>Hardware (MAC) address</description>
- </valueHelp>
- <constraint>
- <validator name="mac-address"/>
- </constraint>
- </properties>
- <children>
- <node name="rate-limit">
- <properties>
- <help>Upload/Download speed limits</help>
- </properties>
- <children>
- <leafNode name="upload">
- <properties>
- <help>Upload bandwidth limit in kbits/sec</help>
- <constraint>
- <validator name="numeric" argument="--range 1-65535"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="download">
- <properties>
- <help>Download bandwidth limit in kbits/sec</help>
- <constraint>
- <validator name="numeric" argument="--range 1-65535"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
- </node>
- <leafNode name="vlan-id">
- <properties>
- <help>VLAN-ID of the client network</help>
- <constraint>
- <validator name="numeric" argument="--range 1-4096"/>
- </constraint>
- <constraintErrorMessage>VLAN ID needs to be between 1 and 4096</constraintErrorMessage>
- </properties>
- </leafNode>
- </children>
- </tagNode>
- </children>
- </tagNode>
- <tagNode name="radius-server">
- <properties>
- <help>IP address of RADIUS server</help>
- <valueHelp>
- <format>ipv4</format>
- <description>IP address of RADIUS server</description>
- </valueHelp>
- </properties>
- <children>
- <leafNode name="secret">
- <properties>
- <help>Key for accessing the specified server</help>
- </properties>
- </leafNode>
- <leafNode name="req-limit">
- <properties>
- <help>Maximum number of simultaneous requests to server (default: unlimited)</help>
- </properties>
- </leafNode>
- <leafNode name="fail-time">
- <properties>
- <help>If server doesn't responds mark it as unavailable for this amount of time in seconds</help>
- </properties>
- </leafNode>
- </children>
- </tagNode>
- <node name="radius-settings">
- <properties>
- <help>RADIUS settings</help>
- </properties>
- <children>
- <leafNode name="timeout">
- <properties>
- <help>Timeout to wait response from server (seconds)</help>
- </properties>
- </leafNode>
- <leafNode name="acct-timeout">
- <properties>
- <help>Timeout to wait reply for Interim-Update packets. (default 3 seconds)</help>
- </properties>
- </leafNode>
- <leafNode name="max-try">
- <properties>
- <help>Maximum number of tries to send Access-Request/Accounting-Request queries</help>
- </properties>
- </leafNode>
- <leafNode name="nas-identifier">
- <properties>
- <help>Value to send to RADIUS server in NAS-Identifier attribute and to be matched in DM/CoA requests.</help>
- </properties>
- </leafNode>
- <leafNode name="nas-ip-address">
- <properties>
- <help>Value to send to RADIUS server in NAS-IP-Address attribute and to be matched in DM/CoA requests. Also DM/CoA server will bind to that address.</help>
- <valueHelp>
- <format>ipv4</format>
- <description>IPv4 address of the DAE Server</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address"/>
- </constraint>
- </properties>
- </leafNode>
- <node name="dae-server">
- <properties>
- <help>IPv4 address and port to bind Dynamic Authorization Extension server (DM/CoA)</help>
- </properties>
- <children>
- <leafNode name="ip-address">
- <properties>
- <help>IP address for Dynamic Authorization Extension server (DM/CoA)</help>
- <valueHelp>
- <format>ipv4</format>
- <description>IPv4 address of the DAE Server</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="port">
- <properties>
- <help>Port for Dynamic Authorization Extension server (DM/CoA)</help>
- <valueHelp>
- <format>number</format>
- <description>port number</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-65535"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="secret">
- <properties>
- <help>Secret for Dynamic Authorization Extension server (DM/CoA)</help>
- </properties>
- </leafNode>
- </children>
- </node>
- </children>
- </node>
+ </leafNode>
+ <tagNode name="interface">
+ <properties>
+ <help>Network interface the client mac will appear on</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <tagNode name="mac-address">
+ <properties>
+ <help>Client mac address allowed to receive an IP address</help>
+ <valueHelp>
+ <format>h:h:h:h:h:h</format>
+ <description>Hardware (MAC) address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="mac-address"/>
+ </constraint>
+ </properties>
+ <children>
+ <node name="rate-limit">
+ <properties>
+ <help>Upload/Download speed limits</help>
+ </properties>
+ <children>
+ <leafNode name="upload">
+ <properties>
+ <help>Upload bandwidth limit in kbits/sec</help>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="download">
+ <properties>
+ <help>Download bandwidth limit in kbits/sec</help>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="vlan-id">
+ <properties>
+ <help>VLAN-ID of the client network</help>
+ <constraint>
+ <validator name="numeric" argument="--range 1-4096"/>
+ </constraint>
+ <constraintErrorMessage>VLAN ID needs to be between 1 and 4096</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </tagNode>
+ <tagNode name="radius-server">
+ <properties>
+ <help>IP address of RADIUS server</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IP address of RADIUS server</description>
+ </valueHelp>
+ </properties>
+ <children>
+ <leafNode name="secret">
+ <properties>
+ <help>Key for accessing the specified server</help>
+ </properties>
+ </leafNode>
+ <leafNode name="req-limit">
+ <properties>
+ <help>Maximum number of simultaneous requests to server (default: unlimited)</help>
+ </properties>
+ </leafNode>
+ <leafNode name="fail-time">
+ <properties>
+ <help>If server doesn't responds mark it as unavailable for this amount of time in seconds</help>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="radius-settings">
+ <properties>
+ <help>RADIUS settings</help>
+ </properties>
+ <children>
+ <leafNode name="timeout">
+ <properties>
+ <help>Timeout to wait response from server (seconds)</help>
+ </properties>
+ </leafNode>
+ <leafNode name="acct-timeout">
+ <properties>
+ <help>Timeout to wait reply for Interim-Update packets. (default 3 seconds)</help>
+ </properties>
+ </leafNode>
+ <leafNode name="max-try">
+ <properties>
+ <help>Maximum number of tries to send Access-Request/Accounting-Request queries</help>
+ </properties>
+ </leafNode>
+ <leafNode name="nas-identifier">
+ <properties>
+ <help>Value to send to RADIUS server in NAS-Identifier attribute and to be matched in DM/CoA requests.</help>
+ </properties>
+ </leafNode>
+ <leafNode name="nas-ip-address">
+ <properties>
+ <help>Value to send to RADIUS server in NAS-IP-Address attribute and to be matched in DM/CoA requests. Also DM/CoA server will bind to that address.</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address of the DAE Server</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <node name="dae-server">
+ <properties>
+ <help>IPv4 address and port to bind Dynamic Authorization Extension server (DM/CoA)</help>
+ </properties>
+ <children>
+ <leafNode name="ip-address">
+ <properties>
+ <help>IP address for Dynamic Authorization Extension server (DM/CoA)</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address of the DAE Server</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="port">
+ <properties>
+ <help>Port for Dynamic Authorization Extension server (DM/CoA)</help>
+ <valueHelp>
+ <format>1-65535</format>
+ <description>port number</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="secret">
+ <properties>
+ <help>Secret for Dynamic Authorization Extension server (DM/CoA)</help>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
</children>
</node>
</children>
diff --git a/op-mode-definitions/openvpn.xml b/op-mode-definitions/openvpn.xml
index 368cc9115..d7c4fc101 100644
--- a/op-mode-definitions/openvpn.xml
+++ b/op-mode-definitions/openvpn.xml
@@ -68,7 +68,7 @@
<script>sudo ${vyos_completion_dir}/list_interfaces.py --type openvpn</script>
</completionHelp>
</properties>
- <command>sudo kill -SIGUSR1 $(cat /var/run/openvpn/$4.pid)</command>
+ <command>sudo ${vyos_op_scripts_dir}/reset_openvpn.py $4</command>
</tagNode>
</children>
</node>
diff --git a/python/vyos/ifconfig.py b/python/vyos/ifconfig.py
index a77cde5e7..4ac605b54 100644
--- a/python/vyos/ifconfig.py
+++ b/python/vyos/ifconfig.py
@@ -142,7 +142,7 @@ class Interface:
# 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)
+ return self._cmd(cmd)
def get_mtu(self):
"""
@@ -205,7 +205,7 @@ class Interface:
# Assemble command executed on system. Unfortunately there is no way
# of altering the MAC address via sysfs
cmd = 'ip link set dev {} address {}'.format(self._ifname, mac)
- self._cmd(cmd)
+ return self._cmd(cmd)
def set_arp_cache_tmo(self, tmo):
@@ -293,7 +293,21 @@ class Interface:
# Assemble command executed on system. Unfortunately there is no way
# to up/down an interface via sysfs
cmd = 'ip link set dev {} {}'.format(self._ifname, state)
- self._cmd(cmd)
+ tmp = self._cmd(cmd)
+
+ # better safe then sorry - wait until the interface is really up
+ # but only for a given period of time to avoid potential deadlocks!
+ cnt = 0
+ while self.get_state() != state:
+ cnt += 1
+ if cnt == 50:
+ print('Interface {} could not be brought up in time ...'.format(self._ifname))
+ break
+
+ # sleep 250ms
+ sleep(0.250)
+
+ return tmp
def set_proxy_arp(self, enable):
"""
@@ -402,7 +416,7 @@ class Interface:
else:
if not is_intf_addr_assigned(self._ifname, addr):
cmd = 'ip addr add "{}" dev "{}"'.format(addr, self._ifname)
- self._cmd(cmd)
+ return self._cmd(cmd)
def del_addr(self, addr):
"""
@@ -433,7 +447,7 @@ class Interface:
else:
if is_intf_addr_assigned(self._ifname, addr):
cmd = 'ip addr del "{}" dev "{}"'.format(addr, self._ifname)
- self._cmd(cmd)
+ return self._cmd(cmd)
# replace dhcpv4/v6 with systemd.networkd?
def _set_dhcp(self):
@@ -470,7 +484,7 @@ class Interface:
# 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)
+ return self._cmd(cmd)
def _del_dhcp(self):
@@ -559,7 +573,7 @@ class Interface:
# 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)
+ return self._cmd(cmd)
def _del_dhcpv6(self):
@@ -582,8 +596,7 @@ class Interface:
return None
# stop dhclient
- cmd = 'start-stop-daemon --stop --quiet --pidfile {}'.format(
- self._dhcpv6_pid_file)
+ cmd = 'start-stop-daemon --stop --quiet --pidfile {}'.format(self._dhcpv6_pid_file)
self._cmd(cmd)
# accept router announcements on this interface
@@ -802,7 +815,7 @@ class BridgeIf(Interface):
>>> BridgeIf('br0').add_port('eth1')
"""
cmd = 'ip link set dev {} master {}'.format(interface, self._ifname)
- self._cmd(cmd)
+ return self._cmd(cmd)
def del_port(self, interface):
"""
@@ -813,7 +826,7 @@ class BridgeIf(Interface):
>>> BridgeIf('br0').del_port('eth1')
"""
cmd = 'ip link set dev {} nomaster'.format(interface)
- self._cmd(cmd)
+ return self._cmd(cmd)
class VLANIf(Interface):
"""
@@ -972,7 +985,7 @@ class EthernetIf(VLANIf):
self._ifname, enable)
try:
# An exception will be thrown if the settings are not changed
- self._cmd(cmd)
+ return self._cmd(cmd)
except CalledProcessError:
pass
@@ -1376,7 +1389,7 @@ class WireGuardIf(Interface):
"""
cmd = "wg set {0} peer {1} remove".format(
self._ifname, str(peerkey))
- self._cmd(cmd)
+ return self._cmd(cmd)
class VXLANIf(Interface, ):
diff --git a/python/vyos/remote.py b/python/vyos/remote.py
index 49936ec08..f8a21f068 100644
--- a/python/vyos/remote.py
+++ b/python/vyos/remote.py
@@ -121,16 +121,34 @@ def get_remote_config(remote_file):
if request['protocol'] in ('scp', 'sftp'):
check_and_add_host_key(request['host'])
+ redirect_opt = ''
+
+ if request['protocol'] in ('http', 'https'):
+ redirect_opt = '-L'
+ # Try header first, and look for 'OK' or 'Moved' codes:
+ curl_cmd = 'curl {0} -q -I {1}'.format(redirect_opt, remote_file)
+ try:
+ curl_output = subprocess.check_output(curl_cmd, shell=True,
+ universal_newlines=True)
+ except subprocess.CalledProcessError:
+ sys.exit(1)
+
+ return_vals = re.findall(r'^HTTP\/\d+\.?\d\s+(\d+)\s+(.*)$',
+ curl_output, re.MULTILINE)
+ for val in return_vals:
+ if int(val[0]) not in [200, 301, 302]:
+ print('HTTP error: {0} {1}'.format(*val))
+ sys.exit(1)
+
if request['user'] and not request['passwd']:
curl_cmd = 'curl -# -u {0} {1}'.format(request['user'], remote_file)
else:
- curl_cmd = 'curl -# {0}'.format(remote_file)
+ curl_cmd = 'curl {0} -# {1}'.format(redirect_opt, remote_file)
- config_file = None
try:
config_file = subprocess.check_output(curl_cmd, shell=True,
universal_newlines=True)
- except subprocess.CalledProcessError as err:
- print("Called process error: {}.".format(err))
+ except subprocess.CalledProcessError:
+ config_file = None
return config_file
diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py
index 3e1381cd0..f19bcb250 100755
--- a/src/conf_mode/dhcp_server.py
+++ b/src/conf_mode/dhcp_server.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018 VyOS maintainers and contributors
+# Copyright (C) 2018-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
@@ -13,18 +13,16 @@
#
# 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 os
-import ipaddress
import jinja2
import socket
import struct
import vyos.validate
+from ipaddress import ip_address, ip_network
from vyos.config import Config
from vyos import ConfigError
@@ -253,6 +251,68 @@ default_config_data = {
'shared_network': [],
}
+def dhcp_slice_range(exclude_list, range_list):
+ """
+ This function is intended to slice a DHCP range. What does it mean?
+
+ Lets assume we have a DHCP range from '192.0.2.1' to '192.0.2.100'
+ but want to exclude address '192.0.2.74' and '192.0.2.75'. We will
+ pass an input 'range_list' in the format:
+ [{'start' : '192.0.2.1', 'stop' : '192.0.2.100' }]
+ and we will receive an output list of:
+ [{'start' : '192.0.2.1' , 'stop' : '192.0.2.73' },
+ {'start' : '192.0.2.76', 'stop' : '192.0.2.100' }]
+ The resulting list can then be used in turn to build the proper dhcpd
+ configuration file.
+ """
+ output = []
+ # exclude list must be sorted for this to work
+ exclude_list = sorted(exclude_list)
+ for ra in range_list:
+ range_start = ra['start']
+ range_stop = ra['stop']
+ range_last_exclude = ''
+
+ for e in exclude_list:
+ if (ip_address(e) >= ip_address(range_start)) and \
+ (ip_address(e) <= ip_address(range_stop)):
+ range_last_exclude = e
+
+ for e in exclude_list:
+ if (ip_address(e) >= ip_address(range_start)) and \
+ (ip_address(e) <= ip_address(range_stop)):
+
+ # Build new IP address range ending one IP address before exclude address
+ r = {
+ 'start' : range_start,
+ 'stop' : str(ip_address(e) -1)
+ }
+ # On the next run our IP address range will start one address after the exclude address
+ range_start = str(ip_address(e) + 1)
+
+ # on subsequent exclude addresses we can not
+ # append them to our output
+ if not (ip_address(r['start']) > ip_address(r['stop'])):
+ # Everything is fine, add range to result
+ output.append(r)
+
+ # Take care of last IP address range spanning from the last exclude
+ # address (+1) to the end of the initial configured range
+ if ip_address(e) == ip_address(range_last_exclude):
+ r = {
+ 'start': str(ip_address(e) + 1),
+ 'stop': str(range_stop)
+ }
+ output.append(r)
+ else:
+ # if we have no exclude in the whole range - we just take the range
+ # as it is
+ if not range_last_exclude:
+ if ra not in output:
+ output.append(ra)
+
+ return output
+
def get_config():
dhcp = default_config_data
conf = Config()
@@ -327,8 +387,8 @@ def get_config():
conf.set_level('service dhcp-server shared-network-name {0} subnet {1}'.format(network, net))
subnet = {
'network': net,
- 'address': str(ipaddress.ip_network(net).network_address),
- 'netmask': str(ipaddress.ip_network(net).netmask),
+ 'address': str(ip_network(net).network_address),
+ 'netmask': str(ip_network(net).netmask),
'bootfile_name': '',
'bootfile_server': '',
'client_prefix_length': '',
@@ -460,54 +520,12 @@ def get_config():
# IP address that needs to be excluded from DHCP lease range
if conf.exists('exclude'):
- # We have no need to store the exclude addresses. Exclude addresses
- # are recalculated into several ranges
- exclude = []
subnet['exclude'] = conf.return_values('exclude')
- for addr in subnet['exclude']:
- exclude.append(ipaddress.ip_address(addr))
-
- # sort excluded IP addresses ascending
- exclude = sorted(exclude)
-
- # calculate multipe ranges based on the excluded IP addresses
- output = []
- for range in subnet['range']:
- range_start = range['start']
- range_stop = range['stop']
-
- for i in exclude:
- # Excluded IP address must be in out specified range
- if (i >= ipaddress.ip_address(range_start)) and (i <= ipaddress.ip_address(range_stop)):
- # Build up new IP address range ending one IP address before
- # our exclude address
- range = {
- 'start': str(range_start),
- 'stop': str(i - 1)
- }
- # Our next IP address range will start one address after
- # our exclude address
- range_start = i + 1
- output.append(range)
-
- # Take care of last IP address range spanning from the last exclude
- # address (+1) to the end of the initial configured range
- if i is exclude[-1]:
- last = {
- 'start': str(i + 1),
- 'stop': str(range_stop)
- }
- output.append(last)
- else:
- # IP address not inside search range, take range is it is
- output.append(range)
-
- # We successfully build up a new list containing several IP address
- # ranges, replace IP address range in our dictionary
- subnet['range'] = output
+ subnet['range'] = dhcp_slice_range(subnet['exclude'], subnet['range'])
# Static DHCP leases
if conf.exists('static-mapping'):
+ addresses_for_exclude = []
for mapping in conf.list_nodes('static-mapping'):
conf.set_level('service dhcp-server shared-network-name {0} subnet {1} static-mapping {2}'.format(network, net, mapping))
mapping = {
@@ -525,6 +543,7 @@ def get_config():
# IP address used for this DHCP client
if conf.exists('ip-address'):
mapping['ip_address'] = conf.return_value('ip-address')
+ addresses_for_exclude.append(mapping['ip_address'])
# MAC address of requesting DHCP client
if conf.exists('mac-address'):
@@ -543,6 +562,13 @@ def get_config():
# append static-mapping configuration to subnet list
subnet['static_mapping'].append(mapping)
+ # Now we have all static DHCP leases - we also need to slice them
+ # out of our DHCP ranges to avoid ISC DHCPd warnings as:
+ # dhcpd: Dynamic and static leases present for 192.0.2.51.
+ # dhcpd: Remove host declaration DMZ_PC1 or remove 192.0.2.51
+ # dhcpd: from the dynamic address pool for DMZ
+ subnet['range'] = dhcp_slice_range(addresses_for_exclude, subnet['range'])
+
# Reset config level to matching hirachy
conf.set_level('service dhcp-server shared-network-name {0} subnet {1}'.format(network, net))
@@ -562,7 +588,7 @@ def get_config():
# Option format is:
# <netmask>, <network-byte1>, <network-byte2>, <network-byte3>, <router-byte1>, <router-byte2>, <router-byte3>
# where bytes with the value 0 are omitted.
- net = ipaddress.ip_network(subnet['static_subnet'])
+ net = ip_network(subnet['static_subnet'])
# add netmask
string = str(net.prefixlen) + ','
# add network bytes
@@ -682,17 +708,17 @@ def verify(dhcp):
raise ConfigError('DHCP range stop address for start {0} is not defined!'.format(start))
# Start address must be inside network
- if not ipaddress.ip_address(start) in ipaddress.ip_network(subnet['network']):
+ if not ip_address(start) in ip_network(subnet['network']):
raise ConfigError('DHCP range start address {0} is not in subnet {1}\n' \
'specified for shared network {2}!'.format(start, subnet['network'], network['name']))
# Stop address must be inside network
- if not ipaddress.ip_address(stop) in ipaddress.ip_network(subnet['network']):
+ if not ip_address(stop) in ip_network(subnet['network']):
raise ConfigError('DHCP range stop address {0} is not in subnet {1}\n' \
'specified for shared network {2}!'.format(stop, subnet['network'], network['name']))
# Stop address must be greater or equal to start address
- if not ipaddress.ip_address(stop) >= ipaddress.ip_address(start):
+ if not ip_address(stop) >= ip_address(start):
raise ConfigError('DHCP range stop address {0} must be greater or equal\n' \
'to the range start address {1}!'.format(stop, start))
@@ -712,7 +738,7 @@ def verify(dhcp):
# Exclude addresses must be in bound
for exclude in subnet['exclude']:
- if not ipaddress.ip_address(exclude) in ipaddress.ip_network(subnet['network']):
+ if not ip_address(exclude) in ip_network(subnet['network']):
raise ConfigError('Exclude IP address {0} is outside of the DHCP lease network {1}\n' \
'under shared network {2}!'.format(exclude, subnet['network'], network['name']))
@@ -735,7 +761,7 @@ def verify(dhcp):
if mapping['ip_address']:
# Static IP address must be in bound
- if not ipaddress.ip_address(mapping['ip_address']) in ipaddress.ip_network(subnet['network']):
+ if not ip_address(mapping['ip_address']) in ip_network(subnet['network']):
raise ConfigError('DHCP static lease IP address {0} for static mapping {1}\n' \
'in shared network {2} is outside DHCP lease subnet {3}!' \
.format(mapping['ip_address'], mapping['name'], network['name'], subnet['network']))
@@ -758,9 +784,9 @@ def verify(dhcp):
subnets.append(subnet['network'])
# Check for overlapping subnets
- net = ipaddress.ip_network(subnet['network'])
+ net = ip_network(subnet['network'])
for n in subnets:
- net2 = ipaddress.ip_network(n)
+ net2 = ip_network(n)
if (net != net2):
if net.overlaps(net2):
raise ConfigError('DHCP conflicting subnet ranges: {0} overlaps {1}'.format(net, net2))
diff --git a/src/conf_mode/interface-ethernet.py b/src/conf_mode/interface-ethernet.py
index 99450b19e..317da5772 100755
--- a/src/conf_mode/interface-ethernet.py
+++ b/src/conf_mode/interface-ethernet.py
@@ -254,7 +254,7 @@ def verify(eth):
for bond in conf.list_nodes('interfaces bonding'):
if conf.exists('interfaces bonding ' + bond + ' member interface'):
bond_member = conf.return_values('interfaces bonding ' + bond + ' member interface')
- if eth['name'] in bond_member:
+ if eth['intf'] in bond_member:
if eth['address']:
raise ConfigError('Can not assign address to interface {} which is a member of {}').format(eth['intf'], bond)
diff --git a/src/conf_mode/interface-openvpn.py b/src/conf_mode/interface-openvpn.py
index a988e1ab1..5345bf7a2 100755
--- a/src/conf_mode/interface-openvpn.py
+++ b/src/conf_mode/interface-openvpn.py
@@ -207,10 +207,16 @@ keysize 128
{%- elif 'bf256' in encryption %}
cipher bf-cbc
keysize 25
+{%- elif 'aes128gcm' in encryption %}
+cipher aes-128-gcm
{%- elif 'aes128' in encryption %}
cipher aes-128-cbc
+{%- elif 'aes192gcm' in encryption %}
+cipher aes-192-gcm
{%- elif 'aes192' in encryption %}
cipher aes-192-cbc
+{%- elif 'aes256gcm' in encryption %}
+cipher aes-256-gcm
{%- elif 'aes256' in encryption %}
cipher aes-256-cbc
{% endif %}
@@ -729,6 +735,9 @@ def verify(openvpn):
# TLS/encryption
#
if openvpn['shared_secret_file']:
+ if openvpn['encryption'] in ['aes128gcm', 'aes192gcm', 'aes256gcm']:
+ raise ConfigError('GCM encryption with shared-secret-key-file is not supported')
+
if not checkCertHeader('-----BEGIN OpenVPN Static key V1-----', openvpn['shared_secret_file']):
raise ConfigError('Specified shared-secret-key-file "{}" is not valid'.format(openvpn['shared_secret_file']))
diff --git a/src/conf_mode/interface-wireguard.py b/src/conf_mode/interface-wireguard.py
index 0be8b7b0f..0dcce6b1c 100755
--- a/src/conf_mode/interface-wireguard.py
+++ b/src/conf_mode/interface-wireguard.py
@@ -224,10 +224,10 @@ def apply(c):
intfc.add_addr(ip)
# interface mtu
- intfc.mtu = int(c['mtu'])
+ intfc.set_mtu(int(c['mtu']))
# ifalias for snmp from description
- intfc.ifalias = str(c['descr'])
+ intfc.set_alias(str(c['descr']))
# remove peers
if c['peer_remove']:
@@ -267,7 +267,7 @@ def apply(c):
intfc.update()
# interface state
- intfc.state = c['state']
+ intfc.set_state(c['state'])
return None
diff --git a/src/helpers/vyos-load-config.py b/src/helpers/vyos-load-config.py
new file mode 100755
index 000000000..4e6d67efa
--- /dev/null
+++ b/src/helpers/vyos-load-config.py
@@ -0,0 +1,90 @@
+#!/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/>.
+#
+#
+
+"""Load config file from within config session.
+Config file specified by URI or path (without scheme prefix).
+Example: load https://somewhere.net/some.config
+ or
+ load /tmp/some.config
+"""
+
+import sys
+import tempfile
+import vyos.defaults
+import vyos.remote
+from vyos.config import Config, VyOSError
+from vyos.migrator import Migrator, MigratorError
+
+system_config_file = 'config.boot'
+
+class LoadConfig(Config):
+ """A subclass for calling 'loadFile'.
+ This does not belong in config.py, and only has a single caller.
+ """
+ def load_config(self, file_path):
+ cmd = [self._cli_shell_api, 'loadFile', file_path]
+ self._run(cmd)
+
+if len(sys.argv) > 1:
+ file_name = sys.argv[1]
+else:
+ file_name = system_config_file
+
+configdir = vyos.defaults.directories['config']
+
+protocols = ['scp', 'sftp', 'http', 'https', 'ftp', 'tftp']
+
+if any(x in file_name for x in protocols):
+ config_file = vyos.remote.get_remote_config(file_name)
+ if not config_file:
+ sys.exit("No config file by that name.")
+else:
+ canonical_path = '{0}/{1}'.format(configdir, file_name)
+ try:
+ with open(canonical_path, 'r') as f:
+ config_file = f.read()
+ except OSError as err1:
+ try:
+ with open(file_name, 'r') as f:
+ config_file = f.read()
+ except OSError as err2:
+ sys.exit('{0}\n{1}'.format(err1, err2))
+
+config = LoadConfig()
+
+print("Loading configuration from '{}'".format(file_name))
+
+with tempfile.NamedTemporaryFile() as fp:
+ with open(fp.name, 'w') as fd:
+ fd.write(config_file)
+
+ migration = Migrator(fp.name)
+ try:
+ migration.run()
+ except MigratorError as err:
+ sys.exit('{}'.format(err))
+
+ try:
+ config.load_config(fp.name)
+ except VyOSError as err:
+ sys.exit('{}'.format(err))
+
+if config.session_changed():
+ print("Load complete. Use 'commit' to make changes effective.")
+else:
+ print("No configuration changes to commit.")
diff --git a/src/op_mode/reset_openvpn.py b/src/op_mode/reset_openvpn.py
new file mode 100755
index 000000000..7043ac261
--- /dev/null
+++ b/src/op_mode/reset_openvpn.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 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 sys
+import os
+
+from psutil import pid_exists
+from subprocess import Popen, PIPE
+from time import sleep
+from netifaces import interfaces
+
+def get_config_name(intf):
+ cfg_file = r'/opt/vyatta/etc/openvpn/openvpn-{}.conf'.format(intf)
+ return cfg_file
+
+def get_pid_file(intf):
+ pid_file = r'/var/run/openvpn/{}.pid'.format(intf)
+ return pid_file
+
+def subprocess_cmd(command):
+ p = Popen(command, stdout=PIPE, shell=True)
+ p.communicate()
+
+if __name__ == '__main__':
+ if (len(sys.argv) < 1):
+ print("Must specify OpenVPN interface name!")
+ sys.exit(1)
+
+ 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 --stop --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:
+ # ERROR: Cannot ioctl TUNSETIFF vtun10: Device or resource busy (errno=16)
+ while interface in interfaces():
+ sleep(0.250) # 250ms
+
+ # re-start OpenVPN process
+ cmd = 'start-stop-daemon --start --quiet'
+ cmd += ' --pidfile ' + get_pid_file(interface)
+ cmd += ' --exec /usr/sbin/openvpn'
+ # now pass arguments to openvpn binary
+ cmd += ' --'
+ cmd += ' --config ' + get_config_name(interface)
+
+ subprocess_cmd(cmd)
+ else:
+ print("OpenVPN interface {} does not exist!".format(interface))
+ sys.exit(1)
diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd
index e7ecd8573..5c2ea71c8 100755
--- a/src/services/vyos-hostsd
+++ b/src/services/vyos-hostsd
@@ -166,9 +166,9 @@ def delete_name_servers(data, tag):
def set_host_name(state, data):
if data['host_name']:
state['host_name'] = data['host_name']
- if data['domain_name']:
+ if 'domain_name' in data:
state['domain_name'] = data['domain_name']
- if data['search_domains']:
+ if 'search_domains' in data:
state['search_domains'] = data['search_domains']
def get_name_servers(state, tag):
diff --git a/src/system/on-dhcp-event.sh b/src/system/on-dhcp-event.sh
index 02bbd4c3c..70a563d4c 100755
--- a/src/system/on-dhcp-event.sh
+++ b/src/system/on-dhcp-event.sh
@@ -37,50 +37,18 @@ fi
case "$action" in
commit) # add mapping for new lease
- echo "- new lease event, setting static mapping for host "\
- "$client_fqdn_name (MAC=$client_mac, IP=$client_ip)"
- #
- # grep fails miserably with \t in the search expression.
- # In the following line one <Ctrl-V> <TAB> is used after $client_search_expr
- # followed by a single space
- grep -q " $client_search_expr #on-dhcp-event " $file
- if [ $? == 0 ]; then
- echo pattern found, removing
- wc1=`cat $file | wc -l`
- sudo sed -i "/ $client_search_expr\t #on-dhcp-event /d" $file
- wc2=`cat $file | wc -l`
- if [ "$wc1" -eq "$wc2" ]; then
- echo No change
- fi
- else
- echo pattern NOT found
- fi
-
- # check if hostname already exists (e.g. a static host mapping)
- # if so don't overwrite
- grep -q " $client_search_expr " $file
+ grep -q " $client_search_expr " $file
if [ $? == 0 ]; then
echo host $client_fqdn_name already exists, exiting
exit 1
fi
-
- line="$client_ip\t $client_fqdn_name\t #on-dhcp-event $client_mac"
- sudo sh -c "echo -e '$line' >> $file"
- ((changes++))
- echo Entry was added
+ # add host
+ /usr/bin/vyos-hostsd-client --add-hosts --tag "DHCP-$client_ip" --host "$client_fqdn_name,$client_ip"
;;
release) # delete mapping for released address
- echo "- lease release event, deleting static mapping for host $client_fqdn_name"
- wc1=`cat $file | wc -l`
- sudo sed -i "/ $client_search_expr\t #on-dhcp-event /d" $file
- wc2=`cat $file | wc -l`
- if [ "$wc1" -eq "$wc2" ]; then
- echo No change
- else
- echo Entry was removed
- ((changes++))
- fi
+ # delete host
+ /usr/bin/vyos-hostsd-client --delete-hosts --tag "DHCP-$client_ip"
;;
*)