diff options
-rw-r--r-- | Jenkinsfile | 8 | ||||
-rw-r--r-- | debian/control | 3 | ||||
-rw-r--r-- | interface-definitions/interfaces-openvpn.xml | 22 | ||||
-rw-r--r-- | interface-definitions/ipoe-server.xml | 326 | ||||
-rw-r--r-- | op-mode-definitions/openvpn.xml | 2 | ||||
-rw-r--r-- | python/vyos/ifconfig.py | 39 | ||||
-rw-r--r-- | python/vyos/remote.py | 26 | ||||
-rwxr-xr-x | src/conf_mode/dhcp_server.py | 142 | ||||
-rwxr-xr-x | src/conf_mode/interface-ethernet.py | 2 | ||||
-rwxr-xr-x | src/conf_mode/interface-openvpn.py | 9 | ||||
-rwxr-xr-x | src/conf_mode/interface-wireguard.py | 6 | ||||
-rwxr-xr-x | src/helpers/vyos-load-config.py | 90 | ||||
-rwxr-xr-x | src/op_mode/reset_openvpn.py | 72 | ||||
-rwxr-xr-x | src/services/vyos-hostsd | 4 | ||||
-rwxr-xr-x | src/system/on-dhcp-event.sh | 42 |
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" ;; *) |