diff options
-rw-r--r-- | data/templates/openvpn/client.conf.tmpl | 21 | ||||
-rw-r--r-- | data/templates/openvpn/server.conf.tmpl | 33 | ||||
-rw-r--r-- | interface-definitions/interfaces-openvpn.xml.in | 94 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-openvpn.py | 230 | ||||
-rwxr-xr-x | src/validators/ipv6 | 3 |
5 files changed, 320 insertions, 61 deletions
diff --git a/data/templates/openvpn/client.conf.tmpl b/data/templates/openvpn/client.conf.tmpl index 3099f2ca7..508d8da94 100644 --- a/data/templates/openvpn/client.conf.tmpl +++ b/data/templates/openvpn/client.conf.tmpl @@ -1,8 +1,9 @@ ### Autogenerated by interfaces-openvpn.py ### {% if ip -%} -ifconfig-push {{ ip }} {{ remote_netmask }} +ifconfig-push {{ ip[0] }} {{ remote_netmask }} {% endif -%} + {% for route in push_route -%} push "route {{ route }}" {% endfor -%} @@ -11,6 +12,24 @@ push "route {{ route }}" iroute {{ net }} {% endfor -%} +{# ipv6_remote is only set when IPv6 server is enabled #} +{% if ipv6_remote -%} +# IPv6 + +{%- if ipv6_ip %} +ifconfig-ipv6-push {{ ipv6_ip[0] }} {{ ipv6_remote }} +{%- endif %} + +{%- for route6 in ipv6_push_route %} +push "route-ipv6 {{ route6 }}" +{%- endfor %} + +{%- for net6 in ipv6_subnet %} +iroute {{ net6 }} +{%- endfor %} + +{% endif -%} + {% if disable -%} disable {% endif -%} diff --git a/data/templates/openvpn/server.conf.tmpl b/data/templates/openvpn/server.conf.tmpl index e2f9062a1..0f563dc2b 100644 --- a/data/templates/openvpn/server.conf.tmpl +++ b/data/templates/openvpn/server.conf.tmpl @@ -18,7 +18,7 @@ dev {{ intf }} persist-key iproute /usr/libexec/vyos/system/unpriv-ip -proto {% if 'tcp-active' in protocol -%}tcp-client{% elif 'tcp-passive' in protocol -%}tcp-server{% else %}udp{% endif %} +proto {% if 'tcp-active' in protocol -%}tcp6-client{% elif 'tcp-passive' in protocol -%}tcp6-server{% else %}udp6{% endif %} {%- if local_host %} local {{ local_host }} @@ -78,7 +78,7 @@ topology {% if server_topology == 'point-to-point' %}p2p{% else %}{{ server_topo mode server tls-server {%- else %} -server {{ server_subnet }} nopool +server {{ server_subnet[0] }} nopool {%- endif %} {%- if server_pool %} @@ -110,7 +110,26 @@ push "dhcp-option DNS {{ ns }}" {%- if server_domain -%} push "dhcp-option DOMAIN {{ server_domain }}" -{% endif %} +{% endif -%} + +{%- if server_ipv6_local %} +# IPv6 +push "tun-ipv6" +ifconfig-ipv6 {{ server_ipv6_local }}/{{ server_ipv6_prefixlen }} {{ server_ipv6_remote }} + +{%- if server_ipv6_pool %} +ifconfig-ipv6-pool {{ server_ipv6_pool_base }}/{{ server_ipv6_pool_prefixlen }} +{%- endif %} + +{%- for route6 in server_ipv6_push_route %} +push "route-ipv6 {{ route6 }}" +{%- endfor %} + +{%- for ns6 in server_ipv6_dns_nameserver %} +push "dhcp-option DNS6 {{ ns6 }}" +{%- endfor %} + +{%- endif %} {% else -%} # @@ -120,9 +139,13 @@ ping {{ ping_interval }} ping-restart {{ ping_restart }} {% if local_address_subnet -%} -ifconfig {{ local_address }} {{ local_address_subnet }} +ifconfig {{ local_address[0] }} {{ local_address_subnet }} {%- elif remote_address -%} -ifconfig {{ local_address }} {{ remote_address }} +ifconfig {{ local_address[0] }} {{ remote_address[0] }} +{%- endif %} + +{% if ipv6_local_address -%} +ifconfig-ipv6 {{ ipv6_local_address[0] }} {{ ipv6_remote_address[0] }} {%- endif %} {% endif -%} diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in index 574a3a58c..b5da8cf76 100644 --- a/interface-definitions/interfaces-openvpn.xml.in +++ b/interface-definitions/interfaces-openvpn.xml.in @@ -233,15 +233,15 @@ </node> <tagNode name="local-address"> <properties> - <help>Local IP address of tunnel</help> + <help>Local IP address of tunnel (IPv4 or IPv6)</help> <constraint> - <validator name="ipv4-address"/> + <validator name="ip-address"/> </constraint> </properties> <children> <leafNode name="subnet-mask"> <properties> - <help>Subnet-mask for local IP address of tunnel</help> + <help>Subnet-mask for local IP address of tunnel (IPv4 only)</help> <constraint> <validator name="ipv4-address"/> </constraint> @@ -256,8 +256,12 @@ <format>ipv4</format> <description>Local IPv4 address</description> </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Local IPv6 address</description> + </valueHelp> <constraint> - <validator name="ipv4-address"/> + <validator name="ip-address"/> </constraint> </properties> </leafNode> @@ -341,9 +345,14 @@ <format>ipv4</format> <description>Remote end IPv4 address</description> </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Remote end IPv6 address</description> + </valueHelp> <constraint> <validator name="ipv4-address"/> </constraint> + <multi/> </properties> </leafNode> <leafNode name="remote-host"> @@ -351,7 +360,11 @@ <help>Remote host to connect to (dynamic if not set)</help> <valueHelp> <format>ipv4</format> - <description>IP address of remote host</description> + <description>IPv4 address of remote host</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address of remote host</description> </valueHelp> <valueHelp> <format>txt</format> @@ -411,9 +424,14 @@ <format>ipv4</format> <description>Client IPv4 address</description> </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Client IPv6 address</description> + </valueHelp> <constraint> - <validator name="ipv4-address"/> + <validator name="ip-address"/> </constraint> + <multi/> </properties> </leafNode> <leafNode name="push-route"> @@ -423,21 +441,29 @@ <format>ipv4net</format> <description>IPv4 network and prefix length</description> </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 network and prefix length</description> + </valueHelp> <constraint> - <validator name="ipv4-prefix"/> + <validator name="ip-prefix"/> </constraint> <multi/> </properties> </leafNode> <leafNode name="subnet"> <properties> - <help>Subnet belonging to the client</help> + <help>Subnet belonging to the client (iroute)</help> <valueHelp> <format>ipv4net</format> <description>IPv4 network and prefix length belonging to the client</description> </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 network and prefix length belonging to the client</description> + </valueHelp> <constraint> - <validator name="ipv4-prefix"/> + <validator name="ip-prefix"/> </constraint> <multi/> </properties> @@ -446,7 +472,7 @@ </tagNode> <node name="client-ip-pool"> <properties> - <help>Pool of client IP addresses</help> + <help>Pool of client IPv4 addresses</help> </properties> <children> <leafNode name="disable"> @@ -496,6 +522,31 @@ </leafNode> </children> </node> + <node name="client-ipv6-pool"> + <properties> + <help>Pool of client IPv6 addresses</help> + </properties> + <children> + <leafNode name="base"> + <properties> + <help>Client IPv6 pool base address with optional prefix length</help> + <valueHelp> + <format>ipv6net</format> + <description>Client IPv6 pool base address with optional prefix length (defaults: base = server subnet + 0x1000, prefix length = server prefix length)</description> + </valueHelp> + <constraint> + <validator name="ipv6"/> + </constraint> + </properties> + </leafNode> + <leafNode name="disable"> + <properties> + <help>Disable client IPv6 pool</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> <leafNode name="domain-name"> <properties> <help>DNS suffix to be pushed to all clients</help> @@ -524,8 +575,12 @@ <format>ipv4</format> <description>DNS server IPv4 address</description> </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>DNS server IPv6 address</description> + </valueHelp> <constraint> - <validator name="ipv4-address"/> + <validator name="ip-address"/> </constraint> <multi/> </properties> @@ -537,8 +592,12 @@ <format>ipv4net</format> <description>IPv4 network and prefix length</description> </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 network and prefix length</description> + </valueHelp> <constraint> - <validator name="ipv4-prefix"/> + <validator name="ip-prefix"/> </constraint> <multi/> </properties> @@ -555,9 +614,14 @@ <format>ipv4net</format> <description>IPv4 network and prefix length</description> </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 network and prefix length</description> + </valueHelp> <constraint> - <validator name="ipv4-prefix"/> + <validator name="ip-prefix"/> </constraint> + <multi/> </properties> </leafNode> <leafNode name="topology"> @@ -568,7 +632,7 @@ </completionHelp> <valueHelp> <format>net30</format> - <description>net30 topology</description> + <description>net30 topology (default)</description> </valueHelp> <valueHelp> <format>point-to-point</format> @@ -579,7 +643,7 @@ <description>Subnet topology</description> </valueHelp> <constraint> - <regex>(subnet|point-to-point)</regex> + <regex>(subnet|point-to-point|net30)</regex> </constraint> </properties> </leafNode> diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 435e8a8f0..836deb64b 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -19,7 +19,7 @@ import re from copy import deepcopy from sys import exit,stderr -from ipaddress import IPv4Address,IPv4Network,summarize_address_range +from ipaddress import ip_address,ip_network,IPv4Address,IPv4Network,IPv6Address,IPv6Network,summarize_address_range from netifaces import interfaces from time import sleep from shutil import rmtree @@ -53,9 +53,11 @@ default_config_data = { 'ipv6_eui64_prefix': '', 'ipv6_forwarding': 1, 'ipv6_dup_addr_detect': 1, + 'ipv6_local_address': [], + 'ipv6_remote_address': [], 'ping_restart': '60', 'ping_interval': '10', - 'local_address': '', + 'local_address': [], 'local_address_subnet': '', 'local_host': '', 'local_port': '', @@ -65,7 +67,7 @@ default_config_data = { 'persistent_tunnel': False, 'protocol': 'udp', 'redirect_gateway': '', - 'remote_address': '', + 'remote_address': [], 'remote_host': [], 'remote_port': '', 'client': [], @@ -78,8 +80,17 @@ default_config_data = { 'server_pool_netmask': '', 'server_push_route': [], 'server_reject_unconfigured': False, - 'server_subnet': '', + 'server_subnet': [], 'server_topology': '', + 'server_ipv6_dns_nameserver': [], + 'server_ipv6_local': '', + 'server_ipv6_prefixlen': '', + 'server_ipv6_remote': '', + 'server_ipv6_pool': True, + 'server_ipv6_pool_base': '', + 'server_ipv6_pool_prefixlen': '', + 'server_ipv6_push_route': [], + 'server_ipv6_subnet': [], 'shared_secret_file': '', 'tls': False, 'tls_auth': '', @@ -119,8 +130,7 @@ def checkCertHeader(header, filename): def getDefaultServer(network, topology, devtype): """ - Gets the default server parameters for a "server" directive. - Currently only IPv4 routed but may be extended to support bridged and/or IPv6 in the future. + Gets the default server parameters for a IPv4 "server" directive. Logic from openvpn's src/openvpn/helper.c. Returns a dict with addresses or False if the input parameters were incorrect. """ @@ -198,6 +208,7 @@ def get_config(): # bridged server should not have a pool by default (but can be specified manually) if openvpn['bridge_member']: openvpn['server_pool'] = False + openvpn['server_ipv6_pool'] = False # set configuration level conf.set_level('interfaces openvpn ' + openvpn['intf']) @@ -276,9 +287,15 @@ def get_config(): # Local IP address of tunnel - even as it is a tag node - we can only work # on the first address if conf.exists('local-address'): - openvpn['local_address'] = conf.list_nodes('local-address')[0] - if conf.exists('local-address {} subnet-mask'.format(openvpn['local_address'])): - openvpn['local_address_subnet'] = conf.return_value('local-address {} subnet-mask'.format(openvpn['local_address'])) + for tmp in conf.list_nodes('local-address'): + tmp_ip = ip_address(tmp) + if tmp_ip.version == 4: + openvpn['local_address'].append(tmp) + if conf.exists('local-address {} subnet-mask'.format(tmp)): + openvpn['local_address_subnet'] = conf.return_value('local-address {} subnet-mask'.format(tmp)) + elif tmp_ip.version == 6: + # input IPv6 address could be expanded so get the compressed version + openvpn['ipv6_local_address'].append(str(tmp_ip)) # Local IP address to accept connections if conf.exists('local-host'): @@ -322,7 +339,12 @@ def get_config(): # IP address of remote end of tunnel if conf.exists('remote-address'): - openvpn['remote_address'] = conf.return_value('remote-address') + for tmp in conf.return_values('remote-address'): + tmp_ip = ip_address(tmp) + if tmp_ip.version == 4: + openvpn['remote_address'].append(tmp) + elif tmp_ip.version == 6: + openvpn['ipv6_remote_address'].append(str(tmp_ip)) # Remote host to connect to (dynamic if not set) if conf.exists('remote-host'): @@ -346,12 +368,18 @@ def get_config(): openvpn['server_topology'] = conf.return_value('server topology') # Server-mode subnet (from which client IPs are allocated) - server_network = None + server_network_v4 = None + server_network_v6 = None if conf.exists('server subnet'): - # server_network is used later in this function - server_network = IPv4Network(conf.return_value('server subnet')) - # convert the network in format: "192.0.2.0 255.255.255.0" for later use in template - openvpn['server_subnet'] = server_network.with_netmask.replace(r'/', ' ') + for tmp in conf.return_values('server subnet'): + tmp_ip = ip_network(tmp) + if tmp_ip.version == 4: + server_network_v4 = tmp_ip + # convert the network to format: "192.0.2.0 255.255.255.0" for later use in template + openvpn['server_subnet'].append(tmp_ip.with_netmask.replace(r'/', ' ')) + elif tmp_ip.version == 6: + server_network_v6 = tmp_ip + openvpn['server_ipv6_subnet'].append(str(tmp_ip)) # Client-specific settings for client in conf.list_nodes('server client'): @@ -360,7 +388,11 @@ def get_config(): data = { 'name': client, 'disable': False, - 'ip': '', + 'ip': [], + 'ipv6_ip': [], + 'ipv6_remote': '', + 'ipv6_push_route': [], + 'ipv6_subnet': [], 'push_route': [], 'subnet': [], 'remote_netmask': '' @@ -371,16 +403,28 @@ def get_config(): data['disable'] = True # IP address of the client - if conf.exists('ip'): - data['ip'] = conf.return_value('ip') + for tmp in conf.return_values('ip'): + tmp_ip = ip_address(tmp) + if tmp_ip.version == 4: + data['ip'].append(tmp) + elif tmp_ip.version == 6: + data['ipv6_ip'].append(str(tmp_ip)) # Route to be pushed to the client - for network in conf.return_values('push-route'): - data['push_route'].append(IPv4Network(network).with_netmask.replace(r'/', ' ')) + for tmp in conf.return_values('push-route'): + tmp_ip = ip_network(tmp) + if tmp_ip.version == 4: + data['push_route'].append(tmp_ip.with_netmask.replace(r'/', ' ')) + elif tmp_ip.version == 6: + data['ipv6_push_route'].append(str(tmp_ip)) # Subnet belonging to the client - for network in conf.return_values('subnet'): - data['subnet'].append(IPv4Network(network).with_netmask.replace(r'/', ' ')) + for tmp in conf.return_values('subnet'): + tmp_ip = ip_network(tmp) + if tmp_ip.version == 4: + data['subnet'].append(tmp_ip.with_netmask.replace(r'/', ' ')) + elif tmp_ip.version == 6: + data['ipv6_subnet'].append(str(tmp_ip)) # Append to global client list openvpn['client'].append(data) @@ -407,6 +451,18 @@ def get_config(): conf.set_level('interfaces openvpn ' + openvpn['intf']) + # Server client IPv6 pool + if conf.exists('server client-ipv6-pool'): + conf.set_level('interfaces openvpn ' + openvpn['intf'] + ' server client-ipv6-pool') + openvpn['server_ipv6_pool'] = not conf.exists('disable') + if conf.exists('base'): + tmp = conf.return_value('base').split('/') + openvpn['server_ipv6_pool_base'] = str(IPv6Address(tmp[0])) + if 1 < len(tmp): + openvpn['server_ipv6_pool_prefixlen'] = tmp[1] + + conf.set_level('interfaces openvpn ' + openvpn['intf']) + # DNS suffix to be pushed to all clients if conf.exists('server domain-name'): openvpn['server_domain'] = conf.return_value('server domain-name') @@ -417,12 +473,21 @@ def get_config(): # Domain Name Server (DNS) if conf.exists('server name-server'): - openvpn['server_dns_nameserver'] = conf.return_values('server name-server') + for tmp in conf.return_values('server name-server'): + tmp_ip = ip_address(tmp) + if tmp_ip.version == 4: + openvpn['server_dns_nameserver'].append(tmp) + elif tmp_ip.version == 6: + openvpn['server_ipv6_dns_nameserver'].append(str(tmp_ip)) # Route to be pushed to all clients if conf.exists('server push-route'): - for network in conf.return_values('server push-route'): - openvpn['server_push_route'].append(IPv4Network(network).with_netmask.replace(r'/', ' ')) + for tmp in conf.return_values('server push-route'): + tmp_ip = ip_network(tmp) + if tmp_ip.version == 4: + openvpn['server_push_route'].append(tmp_ip.with_netmask.replace(r'/', ' ')) + elif tmp_ip.version == 6: + openvpn['server_ipv6_push_route'].append(str(tmp_ip)) # Reject connections from clients that are not explicitly configured if conf.exists('server reject-unconfigured-clients'): @@ -491,9 +556,9 @@ def get_config(): # Set defaults where necessary. # If any of the input parameters are wrong, # this will return False and no defaults will be set. - if server_network and openvpn['server_topology'] and openvpn['type']: + if server_network_v4 and openvpn['server_topology'] and openvpn['type']: default_server = None - default_server = getDefaultServer(server_network, openvpn['server_topology'], openvpn['type']) + default_server = getDefaultServer(server_network_v4, openvpn['server_topology'], openvpn['type']) if default_server: # server-bridge doesn't require a pool so don't set defaults for it if openvpn['server_pool'] and not openvpn['bridge_member']: @@ -509,6 +574,26 @@ def get_config(): for client in openvpn['client']: client['remote_netmask'] = default_server['client_remote_netmask'] + if server_network_v6: + if not openvpn['server_ipv6_local']: + openvpn['server_ipv6_local'] = server_network_v6[1] + if not openvpn['server_ipv6_prefixlen']: + openvpn['server_ipv6_prefixlen'] = server_network_v6.prefixlen + if not openvpn['server_ipv6_remote']: + openvpn['server_ipv6_remote'] = server_network_v6[2] + + if openvpn['server_ipv6_pool'] and server_network_v6.prefixlen < 112: + if not openvpn['server_ipv6_pool_base']: + openvpn['server_ipv6_pool_base'] = server_network_v6[0x1000] + if not openvpn['server_ipv6_pool_prefixlen']: + openvpn['server_ipv6_pool_prefixlen'] = openvpn['server_ipv6_prefixlen'] + + for client in openvpn['client']: + client['ipv6_remote'] = openvpn['server_ipv6_local'] + + if openvpn['redirect_gateway']: + openvpn['redirect_gateway'] += ' ipv6' + return openvpn def verify(openvpn): @@ -562,26 +647,45 @@ def verify(openvpn): raise ConfigError('encryption ncp-ciphers cannot be specified in site-to-site mode, only server or client') if openvpn['mode'] == 'site-to-site' and not openvpn['bridge_member']: - if not openvpn['local_address']: + if not (openvpn['local_address'] or openvpn['ipv6_local_address']): raise ConfigError('Must specify "local-address" or "bridge member interface"') + if len(openvpn['local_address']) > 1 or len(openvpn['ipv6_local_address']) > 1: + raise ConfigError('Cannot specify more than 1 IPv4 and 1 IPv6 "local-address"') + + if len(openvpn['remote_address']) > 1 or len(openvpn['ipv6_remote_address']) > 1: + raise ConfigError('Cannot specify more than 1 IPv4 and 1 IPv6 "remote-address"') + for host in openvpn['remote_host']: - if host == openvpn['remote_address']: + if host in openvpn['remote_address'] or host in openvpn['ipv6_remote_address']: raise ConfigError('"remote-address" cannot be the same as "remote-host"') + if openvpn['local_address'] and not (openvpn['remote_address'] or openvpn['local_address_subnet']): + raise ConfigError('IPv4 "local-address" requires IPv4 "remote-address" or IPv4 "local-address subnet"') + + if openvpn['remote_address'] and not openvpn['local_address']: + raise ConfigError('IPv4 "remote-address" requires IPv4 "local-address"') + + if openvpn['ipv6_local_address'] and not openvpn['ipv6_remote_address']: + raise ConfigError('IPv6 "local-address" requires IPv6 "remote-address"') + + if openvpn['ipv6_remote_address'] and not openvpn['ipv6_local_address']: + raise ConfigError('IPv6 "remote-address" requires IPv6 "local-address"') + if openvpn['type'] == 'tun': - if not openvpn['remote_address']: + if not (openvpn['remote_address'] or openvpn['ipv6_remote_address']): raise ConfigError('Must specify "remote-address"') - if openvpn['local_address'] == openvpn['remote_address']: + if ( (openvpn['local_address'] and openvpn['local_address'] == openvpn['remote_address']) or + (openvpn['ipv6_local_address'] and openvpn['ipv6_local_address'] == openvpn['ipv6_remote_address']) ): raise ConfigError('"local-address" and "remote-address" cannot be the same') - if openvpn['local_address'] == openvpn['local_host']: + if openvpn['local_host'] in openvpn['local_address'] or openvpn['local_host'] in openvpn['ipv6_local_address']: raise ConfigError('"local-address" cannot be the same as "local-host"') else: # checks for client-server or site-to-site bridged - if openvpn['local_address'] or openvpn['remote_address']: + if openvpn['local_address'] or openvpn['ipv6_local_address'] or openvpn['remote_address'] or openvpn['ipv6_remote_address']: raise ConfigError('Cannot specify "local-address" or "remote-address" in client-server or bridge mode') # @@ -603,8 +707,15 @@ def verify(openvpn): if not openvpn['tls_dh'] and not checkCertHeader('-----BEGIN EC PRIVATE KEY-----', openvpn['tls_key']): raise ConfigError('Must specify "tls dh-file" when not using EC keys in server mode') + if len(openvpn['server_subnet']) > 1 or len(openvpn['server_ipv6_subnet']) > 1: + raise ConfigError('Cannot specify more than 1 IPv4 and 1 IPv6 server subnet') + + for client in openvpn['client']: + if len(client['ip']) > 1 or len(client['ipv6_ip']) > 1: + raise ConfigError(f'Server client "{client["name"]}": cannot specify more than 1 IPv4 and 1 IPv6 IP') + if openvpn['server_subnet']: - subnet = IPv4Network(openvpn['server_subnet'].replace(' ', '/')) + subnet = IPv4Network(openvpn['server_subnet'][0].replace(' ', '/')) if openvpn['type'] == 'tun' and subnet.prefixlen > 29: raise ConfigError('Server subnets smaller than /29 with device type "tun" are not supported') @@ -612,8 +723,8 @@ def verify(openvpn): raise ConfigError('Server subnets smaller than /30 with device type "tap" are not supported') for client in openvpn['client']: - if client['ip'] and not IPv4Address(client['ip']) in subnet: - raise ConfigError(f'Client IP "{client["ip"]}" not in server subnet "{subnet}"') + if client['ip'] and not IPv4Address(client['ip'][0]) in subnet: + raise ConfigError(f'Client "{client["name"]}" IP {client["ip"][0]} not in server subnet {subnet}') else: if not openvpn['bridge_member']: @@ -627,17 +738,56 @@ def verify(openvpn): v4PoolStop = IPv4Address(openvpn['server_pool_stop']) if v4PoolStart > v4PoolStop: raise ConfigError(f'Server client-ip-pool start address {v4PoolStart} is larger than stop address {v4PoolStop}') - if (int(v4PoolStop) - int(v4PoolStart) >= 65536): - raise ConfigError(f'Server client-ip-pool is too large [{v4PoolStart} -> {v4PoolStop}], maximum is 65536 addresses.') + + v4PoolSize = int(v4PoolStop) - int(v4PoolStart) + if v4PoolSize >= 65536: + raise ConfigError(f'Server client-ip-pool is too large [{v4PoolStart} -> {v4PoolStop} = {v4PoolSize}], maximum is 65536 addresses.') v4PoolNets = list(summarize_address_range(v4PoolStart, v4PoolStop)) for client in openvpn['client']: if client['ip']: for v4PoolNet in v4PoolNets: - if IPv4Address(client['ip']) in v4PoolNet: - print(f'Warning: Client "{client["name"]}" IP {client["ip"]} is in server IP pool, it is not reserved for this client.', + if IPv4Address(client['ip'][0]) in v4PoolNet: + print(f'Warning: Client "{client["name"]}" IP {client["ip"][0]} is in server IP pool, it is not reserved for this client.', + file=stderr) + + if openvpn['server_ipv6_subnet']: + if not openvpn['server_subnet']: + raise ConfigError('IPv6 server requires an IPv4 server subnet') + + if openvpn['server_ipv6_pool']: + if not openvpn['server_pool']: + raise ConfigError('IPv6 server pool requires an IPv4 server pool') + + if int(openvpn['server_ipv6_pool_prefixlen']) >= 112: + raise ConfigError('IPv6 server pool must be larger than /112') + + v6PoolStart = IPv6Address(openvpn['server_ipv6_pool_base']) + v6PoolStop = IPv6Network((v6PoolStart, openvpn['server_ipv6_pool_prefixlen']), strict=False)[-1] # don't remove the parentheses, it's a 2-tuple + v6PoolSize = int(v6PoolStop) - int(v6PoolStart) if int(openvpn['server_ipv6_pool_prefixlen']) > 96 else 65536 + if v6PoolSize < v4PoolSize: + raise ConfigError(f'IPv6 server pool must be at least as large as the IPv4 pool (current sizes: IPv6={v6PoolSize} IPv4={v4PoolSize})') + + v6PoolNets = list(summarize_address_range(v6PoolStart, v6PoolStop)) + for client in openvpn['client']: + if client['ipv6_ip']: + for v6PoolNet in v6PoolNets: + if IPv6Address(client['ipv6_ip'][0]) in v6PoolNet: + print(f'Warning: Client "{client["name"]}" IP {client["ipv6_ip"][0]} is in server IP pool, it is not reserved for this client.', file=stderr) + else: + if openvpn['server_ipv6_push_route']: + raise ConfigError('IPv6 push-route requires an IPv6 server subnet') + + for client in openvpn ['client']: + if client['ipv6_ip']: + raise ConfigError(f'Server client "{client["name"]}" IPv6 IP requires an IPv6 server subnet') + if client['ipv6_push_route']: + raise ConfigError(f'Server client "{client["name"]} IPv6 push-route requires an IPv6 server subnet"') + if client['ipv6_subnet']: + raise ConfigError(f'Server client "{client["name"]} IPv6 subnet requires an IPv6 server subnet"') + else: # checks for both client and site-to-site go here if openvpn['server_reject_unconfigured']: diff --git a/src/validators/ipv6 b/src/validators/ipv6 new file mode 100755 index 000000000..f18d4a63e --- /dev/null +++ b/src/validators/ipv6 @@ -0,0 +1,3 @@ +#!/bin/sh + +ipaddrcheck --is-ipv6 $1 |