diff options
Diffstat (limited to 'src/conf_mode/interfaces-openvpn.py')
-rwxr-xr-x | src/conf_mode/interfaces-openvpn.py | 111 |
1 files changed, 77 insertions, 34 deletions
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 836deb64b..029bc1d69 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -25,12 +25,12 @@ from time import sleep from shutil import rmtree from vyos.config import Config +from vyos.configdict import list_diff from vyos.ifconfig import VTunIf -from vyos.util import call, is_bridge_member, chown, chmod_600, chmod_755 -from vyos.validate import is_addr_assigned -from vyos import ConfigError from vyos.template import render - +from vyos.util import call, chown, chmod_600, chmod_755 +from vyos.validate import is_addr_assigned, is_bridge_member, is_ipv4 +from vyos import ConfigError user = 'openvpn' group = 'openvpn' @@ -39,6 +39,7 @@ default_config_data = { 'address': [], 'auth_user': '', 'auth_pass': '', + 'auth_user_pass_file': '', 'auth': False, 'bridge_member': [], 'compress_lzo': False, @@ -50,11 +51,13 @@ default_config_data = { 'hash': '', 'intf': '', 'ipv6_autoconf': 0, - 'ipv6_eui64_prefix': '', + 'ipv6_eui64_prefix': [], + 'ipv6_eui64_prefix_remove': [], 'ipv6_forwarding': 1, 'ipv6_dup_addr_detect': 1, 'ipv6_local_address': [], 'ipv6_remote_address': [], + 'is_bridge_member': False, 'ping_restart': '60', 'ping_interval': '10', 'local_address': [], @@ -66,6 +69,7 @@ default_config_data = { 'options': [], 'persistent_tunnel': False, 'protocol': 'udp', + 'protocol_real': '', 'redirect_gateway': '', 'remote_address': [], 'remote_host': [], @@ -193,10 +197,13 @@ def get_config(): raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') openvpn['intf'] = os.environ['VYOS_TAGNODE_VALUE'] + openvpn['auth_user_pass_file'] = f"/run/openvpn/{openvpn['intf']}.pw" # Check if interface instance has been removed if not conf.exists('interfaces openvpn ' + openvpn['intf']): openvpn['deleted'] = True + # check if interface is member if a bridge + openvpn['is_bridge_member'] = is_bridge_member(conf, openvpn['intf']) return openvpn # Check if we belong to any bridge interface @@ -309,9 +316,21 @@ def get_config(): if conf.exists('ipv6 address autoconf'): openvpn['ipv6_autoconf'] = 1 - # Get prefix for IPv6 addressing based on MAC address (EUI-64) + # Get prefixes for IPv6 addressing based on MAC address (EUI-64) if conf.exists('ipv6 address eui64'): - openvpn['ipv6_eui64_prefix'] = conf.return_value('ipv6 address eui64') + openvpn['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64') + + # Determine currently effective EUI64 addresses - to determine which + # address is no longer valid and needs to be removed + eff_addr = conf.return_effective_values('ipv6 address eui64') + openvpn['ipv6_eui64_prefix_remove'] = list_diff(eff_addr, openvpn['ipv6_eui64_prefix']) + + # Remove the default link-local address if set. + if conf.exists('ipv6 address no-default-link-local'): + openvpn['ipv6_eui64_prefix_remove'].append('fe80::/64') + else: + # add the link-local by default to make IPv6 work + openvpn['ipv6_eui64_prefix'].append('fe80::/64') # Disable IPv6 forwarding on this interface if conf.exists('ipv6 disable-forwarding'): @@ -553,6 +572,23 @@ def get_config(): if openvpn['mode'] == 'server' and not openvpn['server_topology']: openvpn['server_topology'] = 'net30' + # Convert protocol to real protocol used by openvpn. + # To make openvpn listen on both IPv4 and IPv6 we must use *6 protocols + # (https://community.openvpn.net/openvpn/ticket/360), unless local is IPv4 + # in which case it must use the standard protocols. + # Note: this will break openvpn if IPv6 is disabled on the system. + # This currently isn't supported, a check can be added in the future. + if openvpn['protocol'] == 'tcp-active': + openvpn['protocol_real'] = 'tcp6-client' + elif openvpn['protocol'] == 'tcp-passive': + openvpn['protocol_real'] = 'tcp6-server' + else: + openvpn['protocol_real'] = 'udp6' + + if is_ipv4(openvpn['local_host']): + # takes out the '6' + openvpn['protocol_real'] = openvpn['protocol_real'][:3] + openvpn['protocol_real'][4:] + # Set defaults where necessary. # If any of the input parameters are wrong, # this will return False and no defaults will be set. @@ -598,13 +634,11 @@ def get_config(): def verify(openvpn): if openvpn['deleted']: - interface = openvpn['intf'] - is_member, bridge = is_bridge_member(interface) - if is_member: - # can not use a f'' formatted-string here as bridge would not get - # expanded in the print statement - raise ConfigError('Can not delete interface "{0}" as it ' \ - 'is a member of bridge "{1}"!'.format(interface, bridge)) + if openvpn['is_bridge_member']: + interface = openvpn['intf'] + bridge = openvpn['is_bridge_member'] + raise ConfigError(f'Interface "{interface}" can not be deleted as it belongs to bridge "{bridge}"!') + return None @@ -917,18 +951,18 @@ def verify(openvpn): return None def generate(openvpn): - if openvpn['deleted'] or openvpn['disable']: - return None - interface = openvpn['intf'] directory = os.path.dirname(get_config_name(interface)) - # we can't know in advance which clients have been, - # remove all client configs + # we can't know in advance which clients have been removed, + # thus all client configs will be removed and re-added on demand ccd_dir = os.path.join(directory, 'ccd', interface) if os.path.isdir(ccd_dir): rmtree(ccd_dir, ignore_errors=True) + if openvpn['deleted'] or openvpn['disable']: + return None + # create config directory on demand directories = [] directories.append(f'{directory}/status') @@ -944,17 +978,16 @@ def generate(openvpn): fix_permissions.append(openvpn['tls_key']) # Generate User/Password authentication file - user_auth_file = f'/tmp/openvpn-{interface}-pw' if openvpn['auth']: - with open(user_auth_file, 'w') as f: + with open(openvpn['auth_user_pass_file'], 'w') as f: f.write('{}\n{}'.format(openvpn['auth_user'], openvpn['auth_pass'])) # also change permission on auth file - fix_permissions.append(user_auth_file) + fix_permissions.append(openvpn['auth_user_pass_file']) else: # delete old auth file if present - if os.path.isfile(user_auth_file): - os.remove(user_auth_file) + if os.path.isfile(openvpn['auth_user_pass_file']): + os.remove(openvpn['auth_user_pass_file']) # Generate client specific configuration for client in openvpn['client']: @@ -980,15 +1013,14 @@ def apply(openvpn): # Do some cleanup when OpenVPN is disabled/deleted if openvpn['deleted'] or openvpn['disable']: - # cleanup old configuration file - if os.path.isfile(get_config_name(interface)): - os.remove(get_config_name(interface)) + # cleanup old configuration files + cleanup = [] + cleanup.append(get_config_name(interface)) + cleanup.append(openvpn['auth_user_pass_file']) - # cleanup client config dir - directory = os.path.dirname(get_config_name(interface)) - ccd_dir = os.path.join(directory, 'ccd', interface) - if os.path.isdir(ccd_dir): - rmtree(ccd_dir, ignore_errors=True) + for file in cleanup: + if os.path.isfile(file): + os.unlink(file) return None @@ -1025,13 +1057,24 @@ def apply(openvpn): o.set_alias(openvpn['description']) # IPv6 address autoconfiguration o.set_ipv6_autoconf(openvpn['ipv6_autoconf']) - # IPv6 EUI-based address - o.set_ipv6_eui64_address(openvpn['ipv6_eui64_prefix']) # IPv6 forwarding o.set_ipv6_forwarding(openvpn['ipv6_forwarding']) # IPv6 Duplicate Address Detection (DAD) tries o.set_ipv6_dad_messages(openvpn['ipv6_dup_addr_detect']) + # IPv6 EUI-based addresses - only in TAP mode (TUN's have no MAC) + # If MAC has changed, old EUI64 addresses won't get deleted, + # but this isn't easy to solve, so leave them. + # This is even more difficult as openvpn uses a random MAC for the + # initial interface creation, unless set by 'lladdr'. + # NOTE: right now the interface is always deleted. For future + # compatibility when tap's are not deleted, leave the del_ in + if openvpn['mode'] == 'tap': + for addr in openvpn['ipv6_eui64_prefix_remove']: + o.del_ipv6_eui64_address(addr) + for addr in openvpn['ipv6_eui64_prefix']: + o.add_ipv6_eui64_address(addr) + except: pass |