summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/conf_mode/container.py2
-rwxr-xr-xsrc/conf_mode/dns_dynamic.py36
-rwxr-xr-xsrc/conf_mode/service_ipoe-server.py102
-rwxr-xr-xsrc/conf_mode/service_pppoe-server.py18
-rwxr-xr-xsrc/conf_mode/vpn_l2tp.py47
-rwxr-xr-xsrc/conf_mode/vpn_pptp.py39
-rwxr-xr-xsrc/conf_mode/vpn_sstp.py13
-rwxr-xr-xsrc/migration-scripts/dns-dynamic/0-to-140
-rwxr-xr-xsrc/migration-scripts/dns-dynamic/2-to-345
-rwxr-xr-xsrc/migration-scripts/ipoe-server/1-to-287
-rwxr-xr-xsrc/migration-scripts/l2tp/4-to-577
-rwxr-xr-xsrc/migration-scripts/pppoe-server/6-to-7111
-rwxr-xr-xsrc/migration-scripts/pptp/2-to-364
-rwxr-xr-xsrc/migration-scripts/sstp/4-to-560
-rwxr-xr-xsrc/validators/ipv4-range-mask59
15 files changed, 630 insertions, 170 deletions
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index 53e25f422..59d11c5a3 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -355,7 +355,7 @@ def generate_run_arguments(name, container_config):
else:
ip_param += f' --ip {address}'
- return f'{container_base_cmd} --net {networks} {ip_param} {entrypoint} {image} {command} {command_arguments}'.strip()
+ return f'{container_base_cmd} --no-healthcheck --net {networks} {ip_param} {entrypoint} {image} {command} {command_arguments}'.strip()
def generate(container):
# bail out early - looks like removal from running config
diff --git a/src/conf_mode/dns_dynamic.py b/src/conf_mode/dns_dynamic.py
index c4dcb76ed..99fa8feee 100755
--- a/src/conf_mode/dns_dynamic.py
+++ b/src/conf_mode/dns_dynamic.py
@@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
-
+import re
from sys import exit
from vyos.base import Warning
@@ -30,6 +30,9 @@ airbag.enable()
config_file = r'/run/ddclient/ddclient.conf'
systemd_override = r'/run/systemd/system/ddclient.service.d/override.conf'
+# Dynamic interfaces that might not exist when the configuration is loaded
+dynamic_interfaces = ('pppoe', 'sstpc')
+
# Protocols that require zone
zone_necessary = ['cloudflare', 'digitalocean', 'godaddy', 'hetzner', 'gandi',
'nfsn', 'nsupdate']
@@ -86,17 +89,29 @@ def verify(dyndns):
if field not in config:
raise ConfigError(f'"{field.replace("_", "-")}" {error_msg_req}')
- # If dyndns address is an interface, ensure that it exists
+ # If dyndns address is an interface, ensure
+ # that the interface exists (or just warn if dynamic interface)
# and that web-options are not set
if config['address'] != 'web':
# exclude check interface for dynamic interfaces
- interface_filter = ('pppoe', 'sstpc')
- if config['address'].startswith(interface_filter):
- Warning(f'interface {config["address"]} does not exist!')
+ if config['address'].startswith(dynamic_interfaces):
+ Warning(f'Interface "{config["address"]}" does not exist yet and cannot '
+ f'be used for Dynamic DNS service "{service}" until it is up!')
else:
verify_interface_exists(config['address'])
if 'web_options' in config:
- raise ConfigError(f'"web-options" is applicable only when using HTTP(S) web request to obtain the IP address')
+ raise ConfigError(f'"web-options" is applicable only when using HTTP(S) '
+ f'web request to obtain the IP address')
+
+ # Warn if using checkip.dyndns.org, as it does not support HTTPS
+ # See: https://github.com/ddclient/ddclient/issues/597
+ if 'web_options' in config:
+ if 'url' not in config['web_options']:
+ raise ConfigError(f'"url" in "web-options" {error_msg_req} '
+ f'with protocol "{config["protocol"]}"')
+ elif re.search("^(https?://)?checkip\.dyndns\.org", config['web_options']['url']):
+ Warning(f'"checkip.dyndns.org" does not support HTTPS requests for IP address '
+ f'lookup. Please use a different IP address lookup service.')
# RFC2136 uses 'key' instead of 'password'
if config['protocol'] != 'nsupdate' and 'password' not in config:
@@ -124,13 +139,16 @@ def verify(dyndns):
if config['ip_version'] == 'both':
if config['protocol'] not in dualstack_supported:
- raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} with protocol "{config["protocol"]}"')
+ raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} '
+ f'with protocol "{config["protocol"]}"')
# dyndns2 protocol in ddclient honors dual stack only for dyn.com (dyndns.org)
if config['protocol'] == 'dyndns2' and 'server' in config and config['server'] not in dyndns_dualstack_servers:
- raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} for "{config["server"]}" with protocol "{config["protocol"]}"')
+ raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} '
+ f'for "{config["server"]}" with protocol "{config["protocol"]}"')
if {'wait_time', 'expiry_time'} <= config.keys() and int(config['expiry_time']) < int(config['wait_time']):
- raise ConfigError(f'"expiry-time" must be greater than "wait-time" for Dynamic DNS service "{service}"')
+ raise ConfigError(f'"expiry-time" must be greater than "wait-time" for '
+ f'Dynamic DNS service "{service}"')
return None
diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py
index b70e32373..36f00dec5 100755
--- a/src/conf_mode/service_ipoe-server.py
+++ b/src/conf_mode/service_ipoe-server.py
@@ -15,17 +15,17 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
-import jmespath
from sys import exit
from vyos.config import Config
from vyos.configdict import get_accel_dict
-from vyos.configverify import verify_accel_ppp_base_service
from vyos.configverify import verify_interface_exists
from vyos.template import render
from vyos.utils.process import call
from vyos.utils.dict import dict_search
+from vyos.accel_ppp_util import get_pools_in_order
+from vyos.accel_ppp_util import verify_accel_ppp_ip_pool
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -35,87 +35,6 @@ ipoe_conf = '/run/accel-pppd/ipoe.conf'
ipoe_chap_secrets = '/run/accel-pppd/ipoe.chap-secrets'
-def get_pools_in_order(data: dict) -> list:
- """Return a list of dictionaries representing pool data in the order
- in which they should be allocated. Pool must be defined before we can
- use it with 'next-pool' option.
-
- Args:
- data: A dictionary of pool data, where the keys are pool names and the
- values are dictionaries containing the 'subnet' key and the optional
- 'next_pool' key.
-
- Returns:
- list: A list of dictionaries
-
- Raises:
- ValueError: If a 'next_pool' key references a pool name that
- has not been defined.
- ValueError: If a circular reference is found in the 'next_pool' keys.
-
- Example:
- config_data = {
- ... 'first-pool': {
- ... 'next_pool': 'second-pool',
- ... 'subnet': '192.0.2.0/25'
- ... },
- ... 'second-pool': {
- ... 'next_pool': 'third-pool',
- ... 'subnet': '203.0.113.0/25'
- ... },
- ... 'third-pool': {
- ... 'subnet': '198.51.100.0/24'
- ... },
- ... 'foo': {
- ... 'subnet': '100.64.0.0/24',
- ... 'next_pool': 'second-pool'
- ... }
- ... }
-
- % get_pools_in_order(config_data)
- [{'third-pool': {'subnet': '198.51.100.0/24'}},
- {'second-pool': {'next_pool': 'third-pool', 'subnet': '203.0.113.0/25'}},
- {'first-pool': {'next_pool': 'second-pool', 'subnet': '192.0.2.0/25'}},
- {'foo': {'next_pool': 'second-pool', 'subnet': '100.64.0.0/24'}}]
- """
- pools = []
- unresolved_pools = {}
-
- for pool, pool_config in data.items():
- if 'next_pool' not in pool_config:
- pools.insert(0, {pool: pool_config})
- else:
- unresolved_pools[pool] = pool_config
-
- while unresolved_pools:
- resolved_pools = []
-
- for pool, pool_config in unresolved_pools.items():
- next_pool_name = pool_config['next_pool']
-
- if any(p for p in pools if next_pool_name in p):
- index = next(
- (i for i, p in enumerate(pools) if next_pool_name in p),
- None)
- pools.insert(index + 1, {pool: pool_config})
- resolved_pools.append(pool)
- elif next_pool_name in unresolved_pools:
- # next pool not yet resolved
- pass
- else:
- raise ValueError(
- f"Pool '{next_pool_name}' not defined in configuration data"
- )
-
- if not resolved_pools:
- raise ValueError("Circular reference in configuration data")
-
- for pool in resolved_pools:
- unresolved_pools.pop(pool)
-
- return pools
-
-
def get_config(config=None):
if config:
conf = config
@@ -128,18 +47,11 @@ def get_config(config=None):
# retrieve common dictionary keys
ipoe = get_accel_dict(conf, base, ipoe_chap_secrets)
- if jmespath.search('client_ip_pool.name', ipoe):
- dict_named_pools = jmespath.search('client_ip_pool.name', ipoe)
+ if dict_search('client_ip_pool', ipoe):
# Multiple named pools require ordered values T5099
- ipoe['ordered_named_pools'] = get_pools_in_order(dict_named_pools)
- # T5099 'next-pool' option
- if jmespath.search('client_ip_pool.name.*.next_pool', ipoe):
- for pool, pool_config in ipoe['client_ip_pool']['name'].items():
- if 'next_pool' in pool_config:
- ipoe['first_named_pool'] = pool
- ipoe['first_named_pool_subnet'] = pool_config
- break
+ ipoe['ordered_named_pools'] = get_pools_in_order(dict_search('client_ip_pool', ipoe))
+ ipoe['server_type'] = 'ipoe'
return ipoe
@@ -156,9 +68,7 @@ def verify(ipoe):
raise ConfigError('Option "client-subnet" incompatible with "vlan"!'
'Use "ipoe client-ip-pool" instead.')
- #verify_accel_ppp_base_service(ipoe, local_users=False)
- # IPoE server does not have 'gateway' option in the CLI
- # we cannot use configverify.py verify_accel_ppp_base_service for ipoe-server
+ verify_accel_ppp_ip_pool(ipoe)
if dict_search('authentication.mode', ipoe) == 'radius':
if not dict_search('authentication.radius.server', ipoe):
diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py
index 87660c127..7c624f034 100755
--- a/src/conf_mode/service_pppoe-server.py
+++ b/src/conf_mode/service_pppoe-server.py
@@ -21,13 +21,16 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import get_accel_dict
from vyos.configdict import is_node_changed
-from vyos.configverify import verify_accel_ppp_base_service
from vyos.configverify import verify_interface_exists
from vyos.template import render
from vyos.utils.process import call
from vyos.utils.dict import dict_search
+from vyos.accel_ppp_util import verify_accel_ppp_base_service
+from vyos.accel_ppp_util import verify_accel_ppp_ip_pool
+from vyos.accel_ppp_util import get_pools_in_order
from vyos import ConfigError
from vyos import airbag
+
airbag.enable()
pppoe_conf = r'/run/accel-pppd/pppoe.conf'
@@ -45,6 +48,10 @@ def get_config(config=None):
# retrieve common dictionary keys
pppoe = get_accel_dict(conf, base, pppoe_chap_secrets)
+ if dict_search('client_ip_pool', pppoe):
+ # Multiple named pools require ordered values T5099
+ pppoe['ordered_named_pools'] = get_pools_in_order(dict_search('client_ip_pool', pppoe))
+
# reload-or-restart does not implemented in accel-ppp
# use this workaround until it will be implemented
# https://phabricator.accel-ppp.org/T3
@@ -53,7 +60,7 @@ def get_config(config=None):
is_node_changed(conf, base + ['interface'])]
if any(conditions):
pppoe.update({'restart_required': {}})
-
+ pppoe['server_type'] = 'pppoe'
return pppoe
def verify(pppoe):
@@ -72,12 +79,7 @@ def verify(pppoe):
for interface in pppoe['interface']:
verify_interface_exists(interface)
- # local ippool and gateway settings config checks
- if not (dict_search('client_ip_pool.subnet', pppoe) or
- (dict_search('client_ip_pool.name', pppoe) or
- (dict_search('client_ip_pool.start', pppoe) and
- dict_search('client_ip_pool.stop', pppoe)))):
- print('Warning: No PPPoE client pool defined')
+ verify_accel_ppp_ip_pool(pppoe)
if dict_search('authentication.radius.dynamic_author.server', pppoe):
if not dict_search('authentication.radius.dynamic_author.key', pppoe):
diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py
index 6232ce64a..9a022d93c 100755
--- a/src/conf_mode/vpn_l2tp.py
+++ b/src/conf_mode/vpn_l2tp.py
@@ -21,15 +21,16 @@ from copy import deepcopy
from stat import S_IRUSR, S_IWUSR, S_IRGRP
from sys import exit
-from ipaddress import ip_network
-
from vyos.config import Config
from vyos.template import is_ipv4
from vyos.template import render
from vyos.utils.process import call
from vyos.utils.system import get_half_cpus
+from vyos.utils.dict import dict_search
from vyos.utils.network import check_port_availability
from vyos.utils.network import is_listen_port_bind_service
+from vyos.accel_ppp_util import verify_accel_ppp_ip_pool
+from vyos.accel_ppp_util import get_pools_in_order
from vyos import ConfigError
from vyos import airbag
@@ -43,7 +44,7 @@ default_config_data = {
'auth_ppp_mppe': 'prefer',
'auth_proto': ['auth_mschap_v2'],
'chap_secrets_file': l2tp_chap_secrets, # used in Jinja2 template
- 'client_ip_pool': None,
+ 'client_ip_pool': {},
'client_ip_subnets': [],
'client_ipv6_pool': [],
'client_ipv6_pool_configured': False,
@@ -246,13 +247,14 @@ def get_config(config=None):
conf.set_level(base_path)
if conf.exists(['client-ip-pool']):
- if conf.exists(['client-ip-pool', 'start']) and conf.exists(['client-ip-pool', 'stop']):
- start = conf.return_value(['client-ip-pool', 'start'])
- stop = conf.return_value(['client-ip-pool', 'stop'])
- l2tp['client_ip_pool'] = start + '-' + re.search('[0-9]+$', stop).group(0)
+ for pool_name in conf.list_nodes(['client-ip-pool']):
+ l2tp['client_ip_pool'][pool_name] = {}
+ l2tp['client_ip_pool'][pool_name]['range'] = conf.return_value(['client-ip-pool', pool_name, 'range'])
+ l2tp['client_ip_pool'][pool_name]['next_pool'] = conf.return_value(['client-ip-pool', pool_name, 'next-pool'])
- if conf.exists(['client-ip-pool', 'subnet']):
- l2tp['client_ip_subnets'] = conf.return_values(['client-ip-pool', 'subnet'])
+ if dict_search('client_ip_pool', l2tp):
+ # Multiple named pools require ordered values T5099
+ l2tp['ordered_named_pools'] = get_pools_in_order(dict_search('client_ip_pool', l2tp))
if conf.exists(['client-ipv6-pool', 'prefix']):
l2tp['client_ipv6_pool_configured'] = True
@@ -281,23 +283,15 @@ def get_config(config=None):
l2tp['client_ipv6_delegate_prefix'].append(tmp)
+ if conf.exists(['default-pool']):
+ l2tp['default_pool'] = conf.return_value(['default-pool'])
+
if conf.exists(['mtu']):
l2tp['mtu'] = conf.return_value(['mtu'])
# gateway address
if conf.exists(['gateway-address']):
l2tp['gateway_address'] = conf.return_value(['gateway-address'])
- else:
- # calculate gw-ip-address
- if conf.exists(['client-ip-pool', 'start']):
- # use start ip as gw-ip-address
- l2tp['gateway_address'] = conf.return_value(['client-ip-pool', 'start'])
-
- elif conf.exists(['client-ip-pool', 'subnet']):
- # use first ip address from first defined pool
- subnet = conf.return_values(['client-ip-pool', 'subnet'])[0]
- subnet = ip_network(subnet)
- l2tp['gateway_address'] = str(list(subnet.hosts())[0])
# LNS secret
if conf.exists(['lns', 'shared-secret']):
@@ -330,9 +324,13 @@ def get_config(config=None):
if conf.exists(['ppp-options', 'ipv6-peer-intf-id']):
l2tp['ppp_ipv6_peer_intf_id'] = conf.return_value(['ppp-options', 'ipv6-peer-intf-id'])
+ l2tp['server_type'] = 'l2tp'
return l2tp
+
+
+
def verify(l2tp):
if not l2tp:
return None
@@ -366,10 +364,11 @@ def verify(l2tp):
not is_listen_port_bind_service(int(port), 'accel-pppd'):
raise ConfigError(f'"{proto}" port "{port}" is used by another service')
- # check for the existence of a client ip pool
- if not (l2tp['client_ip_pool'] or l2tp['client_ip_subnets']):
- raise ConfigError(
- "set vpn l2tp remote-access client-ip-pool requires subnet or start/stop IP pool")
+ if l2tp['auth_mode'] == 'local' or l2tp['auth_mode'] == 'noauth':
+ if not l2tp['client_ip_pool']:
+ raise ConfigError(
+ "L2TP local auth mode requires local client-ip-pool to be configured!")
+ verify_accel_ppp_ip_pool(l2tp)
# check ipv6
if l2tp['client_ipv6_delegate_prefix'] and not l2tp['client_ipv6_pool']:
diff --git a/src/conf_mode/vpn_pptp.py b/src/conf_mode/vpn_pptp.py
index d542f57fe..6243c3ed3 100755
--- a/src/conf_mode/vpn_pptp.py
+++ b/src/conf_mode/vpn_pptp.py
@@ -21,10 +21,14 @@ from copy import deepcopy
from stat import S_IRUSR, S_IWUSR, S_IRGRP
from sys import exit
+
from vyos.config import Config
from vyos.template import render
from vyos.utils.system import get_half_cpus
from vyos.utils.process import call
+from vyos.utils.dict import dict_search
+from vyos.accel_ppp_util import verify_accel_ppp_ip_pool
+from vyos.accel_ppp_util import get_pools_in_order
from vyos import ConfigError
from vyos import airbag
@@ -54,7 +58,7 @@ default_pptp = {
'outside_addr': '',
'dnsv4': [],
'wins': [],
- 'client_ip_pool': '',
+ 'client_ip_pool': {},
'mtu': '1436',
'auth_proto' : ['auth_mschap_v2'],
'ppp_mppe' : 'prefer',
@@ -205,22 +209,24 @@ def get_config(config=None):
conf.set_level(base_path)
if conf.exists(['client-ip-pool']):
- if conf.exists(['client-ip-pool', 'start']) and conf.exists(['client-ip-pool', 'stop']):
- start = conf.return_value(['client-ip-pool', 'start'])
- stop = conf.return_value(['client-ip-pool', 'stop'])
- pptp['client_ip_pool'] = start + '-' + re.search('[0-9]+$', stop).group(0)
+ for pool_name in conf.list_nodes(['client-ip-pool']):
+ pptp['client_ip_pool'][pool_name] = {}
+ pptp['client_ip_pool'][pool_name]['range'] = conf.return_value(['client-ip-pool', pool_name, 'range'])
+ pptp['client_ip_pool'][pool_name]['next_pool'] = conf.return_value(['client-ip-pool', pool_name, 'next-pool'])
+
+ if dict_search('client_ip_pool', pptp):
+ # Multiple named pools require ordered values T5099
+ pptp['ordered_named_pools'] = get_pools_in_order(dict_search('client_ip_pool', pptp))
+
+ if conf.exists(['default-pool']):
+ pptp['default_pool'] = conf.return_value(['default-pool'])
if conf.exists(['mtu']):
pptp['mtu'] = conf.return_value(['mtu'])
# gateway address
if conf.exists(['gateway-address']):
- pptp['gw_ip'] = conf.return_value(['gateway-address'])
- else:
- # calculate gw-ip-address
- if conf.exists(['client-ip-pool', 'start']):
- # use start ip as gw-ip-address
- pptp['gateway_address'] = conf.return_value(['client-ip-pool', 'start'])
+ pptp['gateway_address'] = conf.return_value(['gateway-address'])
if conf.exists(['authentication', 'require']):
# clear default list content, now populate with actual CLI values
@@ -238,6 +244,7 @@ def get_config(config=None):
if conf.exists(['authentication', 'mppe']):
pptp['ppp_mppe'] = conf.return_value(['authentication', 'mppe'])
+ pptp['server_type'] = 'pptp'
return pptp
@@ -248,21 +255,25 @@ def verify(pptp):
if pptp['auth_mode'] == 'local':
if not pptp['local_users']:
raise ConfigError('PPTP local auth mode requires local users to be configured!')
-
for user in pptp['local_users']:
username = user['name']
if not user['password']:
raise ConfigError(f'Password required for local user "{username}"')
-
elif pptp['auth_mode'] == 'radius':
if len(pptp['radius_server']) == 0:
raise ConfigError('RADIUS authentication requires at least one server')
-
for radius in pptp['radius_server']:
if not radius['key']:
server = radius['server']
raise ConfigError(f'Missing RADIUS secret key for server "{ server }"')
+ if pptp['auth_mode'] == 'local' or pptp['auth_mode'] == 'noauth':
+ if not pptp['client_ip_pool']:
+ raise ConfigError(
+ "PPTP local auth mode requires local client-ip-pool to be configured!")
+
+ verify_accel_ppp_ip_pool(pptp)
+
if len(pptp['dnsv4']) > 2:
raise ConfigError('Not more then two IPv4 DNS name-servers can be configured')
diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py
index e98d8385b..ac053cc76 100755
--- a/src/conf_mode/vpn_sstp.py
+++ b/src/conf_mode/vpn_sstp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2022 VyOS maintainers and contributors
+# Copyright (C) 2018-2023 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
@@ -21,13 +21,15 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import get_accel_dict
from vyos.configdict import dict_merge
-from vyos.configverify import verify_accel_ppp_base_service
from vyos.pki import wrap_certificate
from vyos.pki import wrap_private_key
from vyos.template import render
from vyos.utils.process import call
from vyos.utils.network import check_port_availability
from vyos.utils.dict import dict_search
+from vyos.accel_ppp_util import verify_accel_ppp_base_service
+from vyos.accel_ppp_util import verify_accel_ppp_ip_pool
+from vyos.accel_ppp_util import get_pools_in_order
from vyos.utils.network import is_listen_port_bind_service
from vyos.utils.file import write_file
from vyos import ConfigError
@@ -53,13 +55,17 @@ def get_config(config=None):
# retrieve common dictionary keys
sstp = get_accel_dict(conf, base, sstp_chap_secrets)
+ if dict_search('client_ip_pool', sstp):
+ # Multiple named pools require ordered values T5099
+ sstp['ordered_named_pools'] = get_pools_in_order(dict_search('client_ip_pool', sstp))
if sstp:
sstp['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'),
get_first_key=True,
no_tag_node_value_mangle=True)
-
+ sstp['server_type'] = 'sstp'
return sstp
+
def verify(sstp):
if not sstp:
return None
@@ -75,6 +81,7 @@ def verify(sstp):
if 'client_ip_pool' not in sstp and 'client_ipv6_pool' not in sstp:
raise ConfigError('Client IP subnet required')
+ verify_accel_ppp_ip_pool(sstp)
#
# SSL certificate checks
#
diff --git a/src/migration-scripts/dns-dynamic/0-to-1 b/src/migration-scripts/dns-dynamic/0-to-1
index d80e8d44a..b7674a9c8 100755
--- a/src/migration-scripts/dns-dynamic/0-to-1
+++ b/src/migration-scripts/dns-dynamic/0-to-1
@@ -25,8 +25,10 @@
# to "service dns dynamic address <address> service <config> username ..."
# - apply global 'ipv6-enable' to per <config> 'ip-version: ipv6'
# - apply service protocol mapping upfront, they are not 'auto-detected' anymore
+# - migrate web-options url to stricter format
import sys
+import re
from vyos.configtree import ConfigTree
service_protocol_mapping = {
@@ -81,20 +83,42 @@ for address in config.list_nodes(new_base_path):
config.rename(new_base_path + [address, 'service', svc_cfg, 'login'], 'username')
# Apply global 'ipv6-enable' to per <config> 'ip-version: ipv6'
if config.exists(new_base_path + [address, 'ipv6-enable']):
- config.set(new_base_path + [address, 'service', svc_cfg, 'ip-version'],
- value='ipv6', replace=False)
+ config.set(new_base_path + [address, 'service', svc_cfg, 'ip-version'], 'ipv6')
config.delete(new_base_path + [address, 'ipv6-enable'])
# Apply service protocol mapping upfront, they are not 'auto-detected' anymore
if svc_cfg in service_protocol_mapping:
config.set(new_base_path + [address, 'service', svc_cfg, 'protocol'],
- value=service_protocol_mapping.get(svc_cfg), replace=False)
+ service_protocol_mapping.get(svc_cfg))
- # Migrate "service dns dynamic interface <interface> use-web"
- # to "service dns dynamic address <address> web-options"
- # Also, rename <address> to 'web' literal for backward compatibility
+ # If use-web is set, then:
+ # Move "service dns dynamic address <address> <service|rfc2136> <service> ..."
+ # to "service dns dynamic address web <service|rfc2136> <service>-<address> ..."
+ # Move "service dns dynamic address web use-web ..."
+ # to "service dns dynamic address web web-options ..."
+ # Note: The config is named <service>-<address> to avoid name conflict with old entries
if config.exists(new_base_path + [address, 'use-web']):
- config.rename(new_base_path + [address], 'web')
- config.rename(new_base_path + ['web', 'use-web'], 'web-options')
+ for svc_type in ['rfc2136', 'service']:
+ if config.exists(new_base_path + [address, svc_type]):
+ config.set(new_base_path + ['web', svc_type])
+ config.set_tag(new_base_path + ['web', svc_type])
+ for svc_cfg in config.list_nodes(new_base_path + [address, svc_type]):
+ config.copy(new_base_path + [address, svc_type, svc_cfg],
+ new_base_path + ['web', svc_type, f'{svc_cfg}-{address}'])
+
+ # Multiple web-options were not supported, so copy only the first one
+ # Also, migrate web-options url to stricter format and transition
+ # checkip.dyndns.org to https://domains.google.com/checkip for better
+ # TLS support (see: https://github.com/ddclient/ddclient/issues/597)
+ if not config.exists(new_base_path + ['web', 'web-options']):
+ config.copy(new_base_path + [address, 'use-web'], new_base_path + ['web', 'web-options'])
+ if config.exists(new_base_path + ['web', 'web-options', 'url']):
+ url = config.return_value(new_base_path + ['web', 'web-options', 'url'])
+ if re.search("^(https?://)?checkip\.dyndns\.org", url):
+ config.set(new_base_path + ['web', 'web-options', 'url'], 'https://domains.google.com/checkip')
+ if not url.startswith(('http://', 'https://')):
+ config.set(new_base_path + ['web', 'web-options', 'url'], f'https://{url}')
+
+ config.delete(new_base_path + [address])
try:
with open(file_name, 'w') as f:
diff --git a/src/migration-scripts/dns-dynamic/2-to-3 b/src/migration-scripts/dns-dynamic/2-to-3
index 187c2a895..4e0aa37d5 100755
--- a/src/migration-scripts/dns-dynamic/2-to-3
+++ b/src/migration-scripts/dns-dynamic/2-to-3
@@ -21,10 +21,27 @@
# to "service dns dynamic name <service> address <interface> protocol 'nsupdate'"
# - migrate "service dns dynamic address <interface> service <service> ..."
# to "service dns dynamic name <service> address <interface> ..."
+# - normalize the all service names to conform with name constraints
import sys
+import re
+from unicodedata import normalize
from vyos.configtree import ConfigTree
+def normalize_name(name):
+ """Normalize service names to conform with name constraints.
+
+ This is necessary as part of migration because there were no constraints in
+ the old name format.
+ """
+ # Normalize unicode characters to ASCII (NFKD)
+ # Replace all separators with hypens, strip leading and trailing hyphens
+ name = normalize('NFKD', name).encode('ascii', 'ignore').decode()
+ name = re.sub(r'(\s|_|\W)+', '-', name).strip('-')
+
+ return name
+
+
if len(sys.argv) < 2:
print("Must specify file name!")
sys.exit(1)
@@ -64,22 +81,36 @@ for address in config.list_nodes(address_path):
for svc_type in ['service', 'rfc2136']:
if config.exists(address_path_tag + [svc_type]):
- # Move RFC2136 as service configuration, rename to avoid name conflict and set protocol to 'nsupdate'
+ # Set protocol to 'nsupdate' for RFC2136 configuration
if svc_type == 'rfc2136':
- for rfc_cfg_old in config.list_nodes(address_path_tag + ['rfc2136']):
- rfc_cfg_new = f'{rfc_cfg_old}-rfc2136'
- config.rename(address_path_tag + ['rfc2136', rfc_cfg_old], rfc_cfg_new)
- config.set(address_path_tag + ['rfc2136', rfc_cfg_new, 'protocol'], 'nsupdate')
+ for rfc_cfg in config.list_nodes(address_path_tag + ['rfc2136']):
+ config.set(address_path_tag + ['rfc2136', rfc_cfg, 'protocol'], 'nsupdate')
# Add address as config value in each service before moving the service path
- # And then copy the services from 'address <interface> service <service>' to 'name <service>'
+ # And then copy the services from 'address <interface> service <service>'
+ # to 'name (service|rfc2136)-<service>-<address>'
+ # Note: The new service is named (service|rfc2136)-<service>-<address>
+ # to avoid name conflict with old entries
for svc_cfg in config.list_nodes(address_path_tag + [svc_type]):
config.set(address_path_tag + [svc_type, svc_cfg, 'address'], address)
- config.copy(address_path_tag + [svc_type, svc_cfg], name_path + [svc_cfg])
+ config.copy(address_path_tag + [svc_type, svc_cfg],
+ name_path + ['-'.join([svc_type, svc_cfg, address])])
# Finally cleanup the old address path
config.delete(address_path)
+# Normalize the all service names to conform with name constraints
+index = 1
+for name in config.list_nodes(name_path):
+ new_name = normalize_name(name)
+ if new_name != name:
+ # Append index if there is still a name conflicts after normalization
+ # For example, "foo-?(" and "foo-!)" both normalize to "foo-"
+ if config.exists(name_path + [new_name]):
+ new_name = f'{new_name}-{index}'
+ index += 1
+ config.rename(name_path + [name], new_name)
+
try:
with open(file_name, 'w') as f:
f.write(config.to_string())
diff --git a/src/migration-scripts/ipoe-server/1-to-2 b/src/migration-scripts/ipoe-server/1-to-2
new file mode 100755
index 000000000..c8cec6835
--- /dev/null
+++ b/src/migration-scripts/ipoe-server/1-to-2
@@ -0,0 +1,87 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 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/>.
+
+# - changed cli of all named pools
+# - moved gateway-address from pool to global configuration with / netmask
+# gateway can exist without pool if radius is used
+# and Framed-ip-address is transmited
+# - There are several gateway-addresses in ipoe
+# - default-pool by migration.
+# 1. The first pool that contains next-poll.
+# 2. Else, the first pool in the list
+
+import os
+
+from sys import argv
+from sys import exit
+from vyos.configtree import ConfigTree
+
+
+if len(argv) < 2:
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['service', 'ipoe-server']
+pool_base = base + ['client-ip-pool']
+if not config.exists(base):
+ exit(0)
+
+if not config.exists(pool_base):
+ exit(0)
+default_pool = ''
+gateway = ''
+
+#named pool migration
+namedpools_base = pool_base + ['name']
+
+for pool_name in config.list_nodes(namedpools_base):
+ pool_path = namedpools_base + [pool_name]
+ if config.exists(pool_path + ['subnet']):
+ subnet = config.return_value(pool_path + ['subnet'])
+ config.set(pool_base + [pool_name, 'range'], value=subnet)
+ # Get netmask from subnet
+ mask = subnet.split("/")[1]
+ if config.exists(pool_path + ['next-pool']):
+ next_pool = config.return_value(pool_path + ['next-pool'])
+ config.set(pool_base + [pool_name, 'next-pool'], value=next_pool)
+ if not default_pool:
+ default_pool = pool_name
+ if config.exists(pool_path + ['gateway-address']) and mask:
+ gateway = f'{config.return_value(pool_path + ["gateway-address"])}/{mask}'
+ config.set(base + ['gateway-address'], value=gateway, replace=False)
+
+if not default_pool and config.list_nodes(namedpools_base):
+ default_pool = config.list_nodes(namedpools_base)[0]
+
+config.delete(namedpools_base)
+
+if default_pool:
+ config.set(base + ['default-pool'], value=default_pool)
+# format as tag node
+config.set_tag(pool_base)
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/migration-scripts/l2tp/4-to-5 b/src/migration-scripts/l2tp/4-to-5
new file mode 100755
index 000000000..fe8ab357e
--- /dev/null
+++ b/src/migration-scripts/l2tp/4-to-5
@@ -0,0 +1,77 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 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/>.
+
+# - move all pool to named pools
+# 'start-stop' migrate to namedpool 'default-range-pool'
+# 'subnet' migrate to namedpool 'default-subnet-pool'
+# 'default-subnet-pool' is the next pool for 'default-range-pool'
+
+import os
+
+from sys import argv
+from sys import exit
+from vyos.configtree import ConfigTree
+
+
+if len(argv) < 2:
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['vpn', 'l2tp', 'remote-access']
+pool_base = base + ['client-ip-pool']
+if not config.exists(base):
+ exit(0)
+
+if not config.exists(pool_base):
+ exit(0)
+default_pool = ''
+range_pool_name = 'default-range-pool'
+subnet_pool_name = 'default-subnet-pool'
+if config.exists(pool_base + ['subnet']):
+ subnet = config.return_value(pool_base + ['subnet'])
+ config.delete(pool_base + ['subnet'])
+ config.set(pool_base + [subnet_pool_name, 'range'], value=subnet)
+ default_pool = subnet_pool_name
+
+if config.exists(pool_base + ['start']) and config.exists(pool_base + ['stop']):
+ start_ip = config.return_value(pool_base + ['start'])
+ stop_ip = config.return_value(pool_base + ['stop'])
+ ip_range = f'{start_ip}-{stop_ip}'
+ config.delete(pool_base + ['start'])
+ config.delete(pool_base + ['stop'])
+ config.set(pool_base + [range_pool_name, 'range'], value=ip_range)
+ if default_pool:
+ config.set(pool_base + [range_pool_name, 'next-pool'],
+ value=subnet_pool_name)
+ default_pool = range_pool_name
+
+if default_pool:
+ config.set(base + ['default-pool'], value=default_pool)
+# format as tag node
+config.set_tag(pool_base)
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/migration-scripts/pppoe-server/6-to-7 b/src/migration-scripts/pppoe-server/6-to-7
new file mode 100755
index 000000000..34996d8fe
--- /dev/null
+++ b/src/migration-scripts/pppoe-server/6-to-7
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 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/>.
+
+# - move all pool to named pools
+# 'start-stop' migrate to namedpool 'default-range-pool'
+# 'subnet' migrate to namedpool 'default-subnet-pool'
+# 'default-subnet-pool' is the next pool for 'default-range-pool'
+# - There is only one gateway-address, take the first which is configured
+# - default-pool by migration.
+# 1. If authentication mode = 'local' then it is first named pool.
+# If there are not named pools, namedless pool will be default.
+# 2. If authentication mode = 'radius' then namedless pool will be default
+
+import os
+
+from sys import argv
+from sys import exit
+from vyos.configtree import ConfigTree
+
+
+if len(argv) < 2:
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['service', 'pppoe-server']
+pool_base = base + ['client-ip-pool']
+if not config.exists(base):
+ exit(0)
+
+if not config.exists(pool_base):
+ exit(0)
+default_pool = ''
+range_pool_name = 'default-range-pool'
+subnet_pool_name = 'default-subnet-pool'
+#Default nameless pools migrations
+if config.exists(pool_base + ['subnet']):
+ subnet = config.return_value(pool_base + ['subnet'])
+ config.delete(pool_base + ['subnet'])
+ config.set(pool_base + [subnet_pool_name, 'range'], value=subnet)
+ default_pool = subnet_pool_name
+
+if config.exists(pool_base + ['start']) and config.exists(pool_base + ['stop']):
+ start_ip = config.return_value(pool_base + ['start'])
+ stop_ip = config.return_value(pool_base + ['stop'])
+ ip_range = f'{start_ip}-{stop_ip}'
+ config.delete(pool_base + ['start'])
+ config.delete(pool_base + ['stop'])
+ config.set(pool_base + [range_pool_name, 'range'], value=ip_range)
+ if default_pool:
+ config.set(pool_base + [range_pool_name, 'next-pool'],
+ value=subnet_pool_name)
+ default_pool = range_pool_name
+
+gateway = ''
+if config.exists(base + ['gateway-address']):
+ gateway = config.return_value(base + ['gateway-address'])
+
+#named pool migration
+namedpools_base = pool_base + ['name']
+if config.exists(namedpools_base):
+ if config.exists(base + ['authentication', 'mode']):
+ if config.return_value(base + ['authentication', 'mode']) == 'local':
+ if config.list_nodes(namedpools_base):
+ default_pool = config.list_nodes(namedpools_base)[0]
+
+ for pool_name in config.list_nodes(namedpools_base):
+ pool_path = namedpools_base + [pool_name]
+ if config.exists(pool_path + ['subnet']):
+ subnet = config.return_value(pool_path + ['subnet'])
+ config.set(pool_base + [pool_name, 'range'], value=subnet)
+ if config.exists(pool_path + ['next-pool']):
+ next_pool = config.return_value(pool_path + ['next-pool'])
+ config.set(pool_base + [pool_name, 'next-pool'], value=next_pool)
+ if not gateway:
+ if config.exists(pool_path + ['gateway-address']):
+ gateway = config.return_value(pool_path + ['gateway-address'])
+
+ config.delete(namedpools_base)
+
+if gateway:
+ config.set(base + ['gateway-address'], value=gateway)
+if default_pool:
+ config.set(base + ['default-pool'], value=default_pool)
+# format as tag node
+config.set_tag(pool_base)
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/migration-scripts/pptp/2-to-3 b/src/migration-scripts/pptp/2-to-3
new file mode 100755
index 000000000..98dc5c2a6
--- /dev/null
+++ b/src/migration-scripts/pptp/2-to-3
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 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/>.
+
+# - move all pool to named pools
+# 'start-stop' migrate to namedpool 'default-range-pool'
+# 'default-subnet-pool' is the next pool for 'default-range-pool'
+
+import os
+
+from sys import argv
+from sys import exit
+from vyos.configtree import ConfigTree
+
+
+if len(argv) < 2:
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['vpn', 'pptp', 'remote-access']
+pool_base = base + ['client-ip-pool']
+if not config.exists(base):
+ exit(0)
+
+if not config.exists(pool_base):
+ exit(0)
+
+range_pool_name = 'default-range-pool'
+
+if config.exists(pool_base + ['start']) and config.exists(pool_base + ['stop']):
+ start_ip = config.return_value(pool_base + ['start'])
+ stop_ip = config.return_value(pool_base + ['stop'])
+ ip_range = f'{start_ip}-{stop_ip}'
+ config.delete(pool_base + ['start'])
+ config.delete(pool_base + ['stop'])
+ config.set(pool_base + [range_pool_name, 'range'], value=ip_range)
+ config.set(base + ['default-pool'], value=range_pool_name)
+# format as tag node
+config.set_tag(pool_base)
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/migration-scripts/sstp/4-to-5 b/src/migration-scripts/sstp/4-to-5
new file mode 100755
index 000000000..0f332e04f
--- /dev/null
+++ b/src/migration-scripts/sstp/4-to-5
@@ -0,0 +1,60 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 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/>.
+
+# - move all pool to named pools
+# 'subnet' migrate to namedpool 'default-subnet-pool'
+# 'default-subnet-pool' is the next pool for 'default-range-pool'
+
+import os
+
+from sys import argv
+from sys import exit
+from vyos.configtree import ConfigTree
+
+
+if len(argv) < 2:
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['vpn', 'sstp']
+pool_base = base + ['client-ip-pool']
+if not config.exists(base):
+ exit(0)
+
+if not config.exists(pool_base):
+ exit(0)
+
+subnet_pool_name = 'default-subnet-pool'
+if config.exists(pool_base + ['subnet']):
+ subnet = config.return_value(pool_base + ['subnet'])
+ config.delete(pool_base + ['subnet'])
+ config.set(pool_base + [subnet_pool_name, 'range'], value=subnet)
+ config.set(base + ['default-pool'], value=subnet_pool_name)
+# format as tag node
+config.set_tag(pool_base)
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/validators/ipv4-range-mask b/src/validators/ipv4-range-mask
new file mode 100755
index 000000000..7bb4539af
--- /dev/null
+++ b/src/validators/ipv4-range-mask
@@ -0,0 +1,59 @@
+#!/bin/bash
+
+# snippet from https://stackoverflow.com/questions/10768160/ip-address-converter
+ip2dec () {
+ local a b c d ip=$@
+ IFS=. read -r a b c d <<< "$ip"
+ printf '%d\n' "$((a * 256 ** 3 + b * 256 ** 2 + c * 256 + d))"
+}
+
+error_exit() {
+ echo "Error: $1 is not a valid IPv4 address range or these IPs are not under /$2"
+ exit 1
+}
+
+# Check if address range is under the same netmask
+# -m - mask
+# -r - IP range in format x.x.x.x-y.y.y.y
+while getopts m:r: flag
+do
+ case "${flag}" in
+ m) mask=${OPTARG};;
+ r) range=${OPTARG}
+ esac
+done
+if [[ "${range}" =~ "-" ]]&&[[ ! -z ${mask} ]]; then
+ # This only works with real bash (<<<) - split IP addresses into array with
+ # hyphen as delimiter
+ readarray -d - -t strarr <<< ${range}
+
+ ipaddrcheck --is-ipv4-single ${strarr[0]}
+ if [ $? -gt 0 ]; then
+ error_exit ${range} ${mask}
+ fi
+
+ ipaddrcheck --is-ipv4-single ${strarr[1]}
+ if [ $? -gt 0 ]; then
+ error_exit ${range} ${mask}
+ fi
+
+ ${vyos_validators_dir}/numeric --range 0-32 ${mask} > /dev/null
+ if [ $? -ne 0 ]; then
+ error_exit ${range} ${mask}
+ fi
+
+ is_in_24=$( grepcidr ${strarr[0]}"/"${mask} <(echo ${strarr[1]}) )
+ if [ -z $is_in_24 ]; then
+ error_exit ${range} ${mask}
+ fi
+
+ start=$(ip2dec ${strarr[0]})
+ stop=$(ip2dec ${strarr[1]})
+ if [ $start -ge $stop ]; then
+ error_exit ${range} ${mask}
+ fi
+
+ exit 0
+fi
+
+error_exit ${range} ${mask}