summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/conf_mode/interfaces-loopback.py4
-rwxr-xr-xsrc/conf_mode/le_cert.py4
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py10
-rwxr-xr-xsrc/conf_mode/system-option.py3
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py42
-rw-r--r--src/etc/sysctl.d/30-vyos-router.conf7
-rwxr-xr-xsrc/migration-scripts/interfaces/19-to-20130
-rwxr-xr-xsrc/migration-scripts/interfaces/20-to-21130
-rwxr-xr-xsrc/op_mode/ikev2_profile_generator.py171
-rwxr-xr-xsrc/op_mode/show_dhcp.py7
-rwxr-xr-xsrc/op_mode/show_dhcpv6.py6
-rwxr-xr-xsrc/services/vyos-configd7
-rw-r--r--src/systemd/isc-dhcp-server.service6
13 files changed, 359 insertions, 168 deletions
diff --git a/src/conf_mode/interfaces-loopback.py b/src/conf_mode/interfaces-loopback.py
index 30a27abb4..193334443 100755
--- a/src/conf_mode/interfaces-loopback.py
+++ b/src/conf_mode/interfaces-loopback.py
@@ -45,8 +45,8 @@ def generate(loopback):
return None
def apply(loopback):
- l = LoopbackIf(loopback['ifname'])
- if 'deleted' in loopback.keys():
+ l = LoopbackIf(**loopback)
+ if 'deleted' in loopback:
l.remove()
else:
l.update(loopback)
diff --git a/src/conf_mode/le_cert.py b/src/conf_mode/le_cert.py
index 755c89966..6e169a3d5 100755
--- a/src/conf_mode/le_cert.py
+++ b/src/conf_mode/le_cert.py
@@ -22,6 +22,7 @@ from vyos.config import Config
from vyos import ConfigError
from vyos.util import cmd
from vyos.util import call
+from vyos.util import is_systemd_service_running
from vyos import airbag
airbag.enable()
@@ -87,8 +88,7 @@ def generate(cert):
# certbot will attempt to reload nginx, even with 'certonly';
# start nginx if not active
- ret = call('systemctl is-active --quiet nginx.service')
- if ret:
+ if not is_systemd_service_running('nginx.service'):
call('systemctl start nginx.service')
request_certbot(cert)
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index 1a2fabded..9ecfd07fe 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -57,6 +57,11 @@ def get_config(config=None):
if not conf.exists(base):
bgp.update({'deleted' : ''})
+ if not vrf:
+ # We are running in the default VRF context, thus we can not delete
+ # our main BGP instance if there are dependent BGP VRF instances.
+ bgp['dependent_vrfs'] = conf.get_config_dict(['vrf', 'name'],
+ key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True)
return bgp
# We also need some additional information from the config, prefix-lists
@@ -96,6 +101,11 @@ def verify_remote_as(peer_config, bgp_config):
def verify(bgp):
if not bgp or 'deleted' in bgp:
+ if 'dependent_vrfs' in bgp:
+ for vrf, vrf_options in bgp['dependent_vrfs'].items():
+ if dict_search('protocols.bgp', vrf_options) != None:
+ raise ConfigError('Cannot delete default BGP instance, ' \
+ 'dependent VRF instance(s) exist!')
return None
if 'local_as' not in bgp:
diff --git a/src/conf_mode/system-option.py b/src/conf_mode/system-option.py
index 454611c55..55cf6b142 100755
--- a/src/conf_mode/system-option.py
+++ b/src/conf_mode/system-option.py
@@ -24,6 +24,7 @@ from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.template import render
from vyos.util import cmd
+from vyos.util import is_systemd_service_running
from vyos.validate import is_addr_assigned
from vyos.xml import defaults
from vyos import ConfigError
@@ -114,7 +115,7 @@ def apply(options):
if 'performance' in options:
cmd('systemctl restart tuned.service')
# wait until daemon has started before sending configuration
- while (int(os.system('systemctl is-active --quiet tuned.service')) != 0):
+ while (not is_systemd_service_running('tuned.service')):
sleep(0.250)
cmd('tuned-adm profile network-{performance}'.format(**options))
else:
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index f1c6b216b..11ff12e94 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -49,13 +49,14 @@ airbag.enable()
dhcp_wait_attempts = 2
dhcp_wait_sleep = 1
-swanctl_dir = '/etc/swanctl'
-ipsec_conf = '/etc/ipsec.conf'
-ipsec_secrets = '/etc/ipsec.secrets'
-charon_conf = '/etc/strongswan.d/charon.conf'
-charon_dhcp_conf = '/etc/strongswan.d/charon/dhcp.conf'
-interface_conf = '/etc/strongswan.d/interfaces_use.conf'
-swanctl_conf = f'{swanctl_dir}/swanctl.conf'
+swanctl_dir = '/etc/swanctl'
+ipsec_conf = '/etc/ipsec.conf'
+ipsec_secrets = '/etc/ipsec.secrets'
+charon_conf = '/etc/strongswan.d/charon.conf'
+charon_dhcp_conf = '/etc/strongswan.d/charon/dhcp.conf'
+charon_radius_conf = '/etc/strongswan.d/charon/eap-radius.conf'
+interface_conf = '/etc/strongswan.d/interfaces_use.conf'
+swanctl_conf = f'{swanctl_dir}/swanctl.conf'
default_install_routes = 'yes'
@@ -110,6 +111,12 @@ def get_config(config=None):
ipsec['remote_access']['connection'][rw] = dict_merge(default_values,
ipsec['remote_access']['connection'][rw])
+ if 'remote_access' in ipsec and 'radius' in ipsec['remote_access'] and 'server' in ipsec['remote_access']['radius']:
+ default_values = defaults(base + ['remote-access', 'radius', 'server'])
+ for server in ipsec['remote_access']['radius']['server']:
+ ipsec['remote_access']['radius']['server'][server] = dict_merge(default_values,
+ ipsec['remote_access']['radius']['server'][server])
+
ipsec['dhcp_no_address'] = {}
ipsec['install_routes'] = 'no' if conf.exists(base + ["options", "disable-route-autoinstall"]) else default_install_routes
ipsec['interface_change'] = leaf_node_changed(conf, base + ['interface'])
@@ -243,6 +250,11 @@ def verify(ipsec):
if 'ike_group' in ra_conf:
if 'ike_group' not in ipsec or ra_conf['ike_group'] not in ipsec['ike_group']:
raise ConfigError(f"Invalid ike-group on {name} remote-access config")
+
+ ike = ra_conf['ike_group']
+ if dict_search(f'ike_group.{ike}.key_exchange', ipsec) != 'ikev2':
+ raise ConfigError('IPSec remote-access connections requires IKEv2!')
+
else:
raise ConfigError(f"Missing ike-group on {name} remote-access config")
@@ -263,13 +275,19 @@ def verify(ipsec):
if 'pre_shared_secret' not in ra_conf['authentication']:
raise ConfigError(f"Missing pre-shared-key on {name} remote-access config")
+
+ if 'client_mode' in ra_conf['authentication']:
+ if ra_conf['authentication']['client_mode'] == 'eap-radius':
+ if 'radius' not in ipsec['remote_access'] or 'server' not in ipsec['remote_access']['radius'] or len(ipsec['remote_access']['radius']['server']) == 0:
+ raise ConfigError('RADIUS authentication requires at least one server')
+
if 'pool' in ra_conf:
if 'dhcp' in ra_conf['pool'] and len(ra_conf['pool']) > 1:
raise ConfigError(f'Can not use both DHCP and a predefined address pool for "{name}"!')
for pool in ra_conf['pool']:
if pool == 'dhcp':
- if dict_search('options.remote_access.dhcp.server', ipsec) == None:
+ if dict_search('remote_access.dhcp.server', ipsec) == None:
raise ConfigError('IPSec DHCP server is not configured!')
elif 'pool' not in ipsec['remote_access'] or pool not in ipsec['remote_access']['pool']:
@@ -297,6 +315,10 @@ def verify(ipsec):
if v4_addr_and_exclude or v6_addr_and_exclude:
raise ConfigError('Must use both IPv4 or IPv6 addresses for pool prefix and exclude prefixes!')
+ if 'radius' in ipsec['remote_access'] and 'server' in ipsec['remote_access']['radius']:
+ for server, server_config in ipsec['remote_access']['radius']['server'].items():
+ if 'key' not in server_config:
+ raise ConfigError(f'Missing RADIUS secret key for server "{server}"')
if 'site_to_site' in ipsec and 'peer' in ipsec['site_to_site']:
for peer, peer_conf in ipsec['site_to_site']['peer'].items():
@@ -449,7 +471,8 @@ def generate(ipsec):
cleanup_pki_files()
if not ipsec:
- for config_file in [ipsec_conf, ipsec_secrets, charon_dhcp_conf, interface_conf, swanctl_conf]:
+ for config_file in [ipsec_conf, ipsec_secrets, charon_dhcp_conf,
+ charon_radius_conf, interface_conf, swanctl_conf]:
if os.path.isfile(config_file):
os.unlink(config_file)
render(charon_conf, 'ipsec/charon.tmpl', {'install_routes': default_install_routes})
@@ -518,6 +541,7 @@ def generate(ipsec):
render(ipsec_secrets, 'ipsec/ipsec.secrets.tmpl', ipsec)
render(charon_conf, 'ipsec/charon.tmpl', ipsec)
render(charon_dhcp_conf, 'ipsec/charon/dhcp.conf.tmpl', ipsec)
+ render(charon_radius_conf, 'ipsec/charon/eap-radius.conf.tmpl', ipsec)
render(interface_conf, 'ipsec/interfaces_use.conf.tmpl', ipsec)
render(swanctl_conf, 'ipsec/swanctl.conf.tmpl', ipsec)
diff --git a/src/etc/sysctl.d/30-vyos-router.conf b/src/etc/sysctl.d/30-vyos-router.conf
index 8265e12dc..e03d3a29c 100644
--- a/src/etc/sysctl.d/30-vyos-router.conf
+++ b/src/etc/sysctl.d/30-vyos-router.conf
@@ -72,6 +72,12 @@ net.ipv4.conf.default.send_redirects=1
# Increase size of buffer for netlink
net.core.rmem_max=2097152
+# Remove IPv4 and IPv6 routes from forward information base when link goes down
+net.ipv4.conf.all.ignore_routes_with_linkdown=1
+net.ipv4.conf.default.ignore_routes_with_linkdown=1
+net.ipv6.conf.all.ignore_routes_with_linkdown=1
+net.ipv6.conf.default.ignore_routes_with_linkdown=1
+
# Enable packet forwarding for IPv6
net.ipv6.conf.all.forwarding=1
@@ -81,6 +87,7 @@ net.ipv6.route.max_size = 262144
# Do not forget IPv6 addresses when a link goes down
net.ipv6.conf.default.keep_addr_on_down=1
net.ipv6.conf.all.keep_addr_on_down=1
+net.ipv6.route.skip_notify_on_dev_down=1
# Default value of 20 seems to interfere with larger OSPF and VRRP setups
net.ipv4.igmp_max_memberships = 512
diff --git a/src/migration-scripts/interfaces/19-to-20 b/src/migration-scripts/interfaces/19-to-20
index 06e07572f..e96663e54 100755
--- a/src/migration-scripts/interfaces/19-to-20
+++ b/src/migration-scripts/interfaces/19-to-20
@@ -18,62 +18,6 @@ from sys import argv
from sys import exit
from vyos.configtree import ConfigTree
-def migrate_ospf(config, path, interface):
- path = path + ['ospf']
- if config.exists(path):
- new_base = ['protocols', 'ospf', 'interface']
- config.set(new_base)
- config.set_tag(new_base)
- config.copy(path, new_base + [interface])
- config.delete(path)
-
- # if "ip ospf" was the only setting, we can clean out the empty
- # ip node afterwards
- if len(config.list_nodes(path[:-1])) == 0:
- config.delete(path[:-1])
-
-def migrate_ospfv3(config, path, interface):
- path = path + ['ospfv3']
- if config.exists(path):
- new_base = ['protocols', 'ospfv3', 'interface']
- config.set(new_base)
- config.set_tag(new_base)
- config.copy(path, new_base + [interface])
- config.delete(path)
-
- # if "ipv6 ospfv3" was the only setting, we can clean out the empty
- # ip node afterwards
- if len(config.list_nodes(path[:-1])) == 0:
- config.delete(path[:-1])
-
-def migrate_rip(config, path, interface):
- path = path + ['rip']
- if config.exists(path):
- new_base = ['protocols', 'rip', 'interface']
- config.set(new_base)
- config.set_tag(new_base)
- config.copy(path, new_base + [interface])
- config.delete(path)
-
- # if "ip rip" was the only setting, we can clean out the empty
- # ip node afterwards
- if len(config.list_nodes(path[:-1])) == 0:
- config.delete(path[:-1])
-
-def migrate_ripng(config, path, interface):
- path = path + ['ripng']
- if config.exists(path):
- new_base = ['protocols', 'ripng', 'interface']
- config.set(new_base)
- config.set_tag(new_base)
- config.copy(path, new_base + [interface])
- config.delete(path)
-
- # if "ipv6 ripng" was the only setting, we can clean out the empty
- # ip node afterwards
- if len(config.list_nodes(path[:-1])) == 0:
- config.delete(path[:-1])
-
if __name__ == '__main__':
if (len(argv) < 1):
print("Must specify file name!")
@@ -85,57 +29,29 @@ if __name__ == '__main__':
config = ConfigTree(config_file)
- #
- # Migrate "interface ethernet eth0 ip ospf" to "protocols ospf interface eth0"
- #
- for type in config.list_nodes(['interfaces']):
- for interface in config.list_nodes(['interfaces', type]):
- ip_base = ['interfaces', type, interface, 'ip']
- ipv6_base = ['interfaces', type, interface, 'ipv6']
- migrate_rip(config, ip_base, interface)
- migrate_ripng(config, ipv6_base, interface)
- migrate_ospf(config, ip_base, interface)
- migrate_ospfv3(config, ipv6_base, interface)
-
- vif_path = ['interfaces', type, interface, 'vif']
- if config.exists(vif_path):
- for vif in config.list_nodes(vif_path):
- vif_ip_base = vif_path + [vif, 'ip']
- vif_ipv6_base = vif_path + [vif, 'ipv6']
- ifname = f'{interface}.{vif}'
-
- migrate_rip(config, vif_ip_base, ifname)
- migrate_ripng(config, vif_ipv6_base, ifname)
- migrate_ospf(config, vif_ip_base, ifname)
- migrate_ospfv3(config, vif_ipv6_base, ifname)
-
-
- vif_s_path = ['interfaces', type, interface, 'vif-s']
- if config.exists(vif_s_path):
- for vif_s in config.list_nodes(vif_s_path):
- vif_s_ip_base = vif_s_path + [vif_s, 'ip']
- vif_s_ipv6_base = vif_s_path + [vif_s, 'ipv6']
-
- # vif-c interfaces MUST be migrated before their parent vif-s
- # interface as the migrate_*() functions delete the path!
- vif_c_path = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c']
- if config.exists(vif_c_path):
- for vif_c in config.list_nodes(vif_c_path):
- vif_c_ip_base = vif_c_path + [vif_c, 'ip']
- vif_c_ipv6_base = vif_c_path + [vif_c, 'ipv6']
- ifname = f'{interface}.{vif_s}.{vif_c}'
-
- migrate_rip(config, vif_c_ip_base, ifname)
- migrate_ripng(config, vif_c_ipv6_base, ifname)
- migrate_ospf(config, vif_c_ip_base, ifname)
- migrate_ospfv3(config, vif_c_ipv6_base, ifname)
-
-
- ifname = f'{interface}.{vif_s}'
- migrate_rip(config, vif_s_ip_base, ifname)
- migrate_ripng(config, vif_s_ipv6_base, ifname)
- migrate_ospf(config, vif_s_ip_base, ifname)
- migrate_ospfv3(config, vif_s_ipv6_base, ifname)
+ for type in ['tunnel', 'l2tpv3']:
+ base = ['interfaces', type]
+ if not config.exists(base):
+ # Nothing to do
+ continue
+
+ for interface in config.list_nodes(base):
+ # Migrate "interface tunnel <tunX> encapsulation gre-bridge" to gretap
+ encap_path = base + [interface, 'encapsulation']
+ if type == 'tunnel' and config.exists(encap_path):
+ tmp = config.return_value(encap_path)
+ if tmp == 'gre-bridge':
+ config.set(encap_path, value='gretap')
+
+ # Migrate "interface tunnel|l2tpv3 <interface> local-ip" to source-address
+ # Migrate "interface tunnel|l2tpv3 <interface> remote-ip" to remote
+ local_ip_path = base + [interface, 'local-ip']
+ if config.exists(local_ip_path):
+ config.rename(local_ip_path, 'source-address')
+
+ remote_ip_path = base + [interface, 'remote-ip']
+ if config.exists(remote_ip_path):
+ config.rename(remote_ip_path, 'remote')
try:
with open(file_name, 'w') as f:
diff --git a/src/migration-scripts/interfaces/20-to-21 b/src/migration-scripts/interfaces/20-to-21
index e96663e54..06e07572f 100755
--- a/src/migration-scripts/interfaces/20-to-21
+++ b/src/migration-scripts/interfaces/20-to-21
@@ -18,6 +18,62 @@ from sys import argv
from sys import exit
from vyos.configtree import ConfigTree
+def migrate_ospf(config, path, interface):
+ path = path + ['ospf']
+ if config.exists(path):
+ new_base = ['protocols', 'ospf', 'interface']
+ config.set(new_base)
+ config.set_tag(new_base)
+ config.copy(path, new_base + [interface])
+ config.delete(path)
+
+ # if "ip ospf" was the only setting, we can clean out the empty
+ # ip node afterwards
+ if len(config.list_nodes(path[:-1])) == 0:
+ config.delete(path[:-1])
+
+def migrate_ospfv3(config, path, interface):
+ path = path + ['ospfv3']
+ if config.exists(path):
+ new_base = ['protocols', 'ospfv3', 'interface']
+ config.set(new_base)
+ config.set_tag(new_base)
+ config.copy(path, new_base + [interface])
+ config.delete(path)
+
+ # if "ipv6 ospfv3" was the only setting, we can clean out the empty
+ # ip node afterwards
+ if len(config.list_nodes(path[:-1])) == 0:
+ config.delete(path[:-1])
+
+def migrate_rip(config, path, interface):
+ path = path + ['rip']
+ if config.exists(path):
+ new_base = ['protocols', 'rip', 'interface']
+ config.set(new_base)
+ config.set_tag(new_base)
+ config.copy(path, new_base + [interface])
+ config.delete(path)
+
+ # if "ip rip" was the only setting, we can clean out the empty
+ # ip node afterwards
+ if len(config.list_nodes(path[:-1])) == 0:
+ config.delete(path[:-1])
+
+def migrate_ripng(config, path, interface):
+ path = path + ['ripng']
+ if config.exists(path):
+ new_base = ['protocols', 'ripng', 'interface']
+ config.set(new_base)
+ config.set_tag(new_base)
+ config.copy(path, new_base + [interface])
+ config.delete(path)
+
+ # if "ipv6 ripng" was the only setting, we can clean out the empty
+ # ip node afterwards
+ if len(config.list_nodes(path[:-1])) == 0:
+ config.delete(path[:-1])
+
if __name__ == '__main__':
if (len(argv) < 1):
print("Must specify file name!")
@@ -29,29 +85,57 @@ if __name__ == '__main__':
config = ConfigTree(config_file)
- for type in ['tunnel', 'l2tpv3']:
- base = ['interfaces', type]
- if not config.exists(base):
- # Nothing to do
- continue
-
- for interface in config.list_nodes(base):
- # Migrate "interface tunnel <tunX> encapsulation gre-bridge" to gretap
- encap_path = base + [interface, 'encapsulation']
- if type == 'tunnel' and config.exists(encap_path):
- tmp = config.return_value(encap_path)
- if tmp == 'gre-bridge':
- config.set(encap_path, value='gretap')
-
- # Migrate "interface tunnel|l2tpv3 <interface> local-ip" to source-address
- # Migrate "interface tunnel|l2tpv3 <interface> remote-ip" to remote
- local_ip_path = base + [interface, 'local-ip']
- if config.exists(local_ip_path):
- config.rename(local_ip_path, 'source-address')
-
- remote_ip_path = base + [interface, 'remote-ip']
- if config.exists(remote_ip_path):
- config.rename(remote_ip_path, 'remote')
+ #
+ # Migrate "interface ethernet eth0 ip ospf" to "protocols ospf interface eth0"
+ #
+ for type in config.list_nodes(['interfaces']):
+ for interface in config.list_nodes(['interfaces', type]):
+ ip_base = ['interfaces', type, interface, 'ip']
+ ipv6_base = ['interfaces', type, interface, 'ipv6']
+ migrate_rip(config, ip_base, interface)
+ migrate_ripng(config, ipv6_base, interface)
+ migrate_ospf(config, ip_base, interface)
+ migrate_ospfv3(config, ipv6_base, interface)
+
+ vif_path = ['interfaces', type, interface, 'vif']
+ if config.exists(vif_path):
+ for vif in config.list_nodes(vif_path):
+ vif_ip_base = vif_path + [vif, 'ip']
+ vif_ipv6_base = vif_path + [vif, 'ipv6']
+ ifname = f'{interface}.{vif}'
+
+ migrate_rip(config, vif_ip_base, ifname)
+ migrate_ripng(config, vif_ipv6_base, ifname)
+ migrate_ospf(config, vif_ip_base, ifname)
+ migrate_ospfv3(config, vif_ipv6_base, ifname)
+
+
+ vif_s_path = ['interfaces', type, interface, 'vif-s']
+ if config.exists(vif_s_path):
+ for vif_s in config.list_nodes(vif_s_path):
+ vif_s_ip_base = vif_s_path + [vif_s, 'ip']
+ vif_s_ipv6_base = vif_s_path + [vif_s, 'ipv6']
+
+ # vif-c interfaces MUST be migrated before their parent vif-s
+ # interface as the migrate_*() functions delete the path!
+ vif_c_path = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c']
+ if config.exists(vif_c_path):
+ for vif_c in config.list_nodes(vif_c_path):
+ vif_c_ip_base = vif_c_path + [vif_c, 'ip']
+ vif_c_ipv6_base = vif_c_path + [vif_c, 'ipv6']
+ ifname = f'{interface}.{vif_s}.{vif_c}'
+
+ migrate_rip(config, vif_c_ip_base, ifname)
+ migrate_ripng(config, vif_c_ipv6_base, ifname)
+ migrate_ospf(config, vif_c_ip_base, ifname)
+ migrate_ospfv3(config, vif_c_ipv6_base, ifname)
+
+
+ ifname = f'{interface}.{vif_s}'
+ migrate_rip(config, vif_s_ip_base, ifname)
+ migrate_ripng(config, vif_s_ipv6_base, ifname)
+ migrate_ospf(config, vif_s_ip_base, ifname)
+ migrate_ospfv3(config, vif_s_ipv6_base, ifname)
try:
with open(file_name, 'w') as f:
diff --git a/src/op_mode/ikev2_profile_generator.py b/src/op_mode/ikev2_profile_generator.py
index 4ff37341c..d45525431 100755
--- a/src/op_mode/ikev2_profile_generator.py
+++ b/src/op_mode/ikev2_profile_generator.py
@@ -19,17 +19,99 @@ import argparse
from jinja2 import Template
from sys import exit
from socket import getfqdn
+from cryptography.x509.oid import NameOID
from vyos.config import Config
-from vyos.template import render_to_string
-from cryptography.x509.oid import NameOID
from vyos.pki import load_certificate
+from vyos.template import render_to_string
+from vyos.util import ask_input
+
+# Apple profiles only support one IKE/ESP encryption cipher and hash, whereas
+# VyOS comes with a multitude of different proposals for a connection.
+#
+# We take all available proposals from the VyOS CLI and ask the user which one
+# he would like to get enabled in his profile - thus there is limited possibility
+# to select a proposal that is not supported on the connection profile.
+#
+# IOS supports IKE-SA encryption algorithms:
+# - DES
+# - 3DES
+# - AES-128
+# - AES-256
+# - AES-128-GCM
+# - AES-256-GCM
+# - ChaCha20Poly1305
+#
+vyos2apple_cipher = {
+ '3des' : '3DES',
+ 'aes128' : 'AES-128',
+ 'aes256' : 'AES-256',
+ 'aes128gcm128' : 'AES-128-GCM',
+ 'aes256gcm128' : 'AES-256-GCM',
+ 'chacha20poly1305' : 'ChaCha20Poly1305',
+}
+
+# Windows supports IKE-SA encryption algorithms:
+# - DES3
+# - AES128
+# - AES192
+# - AES256
+# - GCMAES128
+# - GCMAES192
+# - GCMAES256
+#
+vyos2windows_cipher = {
+ '3des' : 'DES3',
+ 'aes128' : 'AES128',
+ 'aes192' : 'AES192',
+ 'aes256' : 'AES256',
+ 'aes128gcm128' : 'GCMAES128',
+ 'aes192gcm128' : 'GCMAES192',
+ 'aes256gcm128' : 'GCMAES256',
+}
+
+# IOS supports IKE-SA integrity algorithms:
+# - SHA1-96
+# - SHA1-160
+# - SHA2-256
+# - SHA2-384
+# - SHA2-512
+#
+vyos2apple_integrity = {
+ 'sha1' : 'SHA1-96',
+ 'sha1_160' : 'SHA1-160',
+ 'sha256' : 'SHA2-256',
+ 'sha384' : 'SHA2-384',
+ 'sha512' : 'SHA2-512',
+}
+
+# Windows supports IKE-SA integrity algorithms:
+# - SHA1-96
+# - SHA1-160
+# - SHA2-256
+# - SHA2-384
+# - SHA2-512
+#
+vyos2windows_integrity = {
+ 'sha1' : 'SHA196',
+ 'sha256' : 'SHA256',
+ 'aes128gmac' : 'GCMAES128',
+ 'aes192gmac' : 'GCMAES192',
+ 'aes256gmac' : 'GCMAES256',
+}
+
+# IOS 14.2 and later do no support dh-group 1,2 and 5. Supported DH groups would
+# be: 14, 15, 16, 17, 18, 19, 20, 21, 31
+ios_supported_dh_groups = ['14', '15', '16', '17', '18', '19', '20', '21', '31']
+# Windows 10 only allows a limited set of DH groups
+windows_supported_dh_groups = ['1', '2', '14', '24']
parser = argparse.ArgumentParser()
-parser.add_argument("--connection", action="store", help="IPsec IKEv2 remote-access connection name from CLI", required=True)
-parser.add_argument("--remote", action="store", help="VPN connection remote-address where the client will connect to", required=True)
-parser.add_argument("--profile", action="store", help="IKEv2 profile name used in the profile list on the device")
-parser.add_argument("--name", action="store", help="VPN connection name as seen in the VPN application later")
+parser.add_argument('--os', const='all', nargs='?', choices=['ios', 'windows'], help='Operating system used for config generation', required=True)
+parser.add_argument("--connection", action="store", help='IPsec IKEv2 remote-access connection name from CLI', required=True)
+parser.add_argument("--remote", action="store", help='VPN connection remote-address where the client will connect to', required=True)
+parser.add_argument("--profile", action="store", help='IKEv2 profile name used in the profile list on the device')
+parser.add_argument("--name", action="store", help='VPN connection name as seen in the VPN application later')
args = parser.parse_args()
ipsec_base = ['vpn', 'ipsec']
@@ -43,7 +125,7 @@ profile_name = 'VyOS IKEv2 Profile'
if args.profile:
profile_name = args.profile
-vpn_name = 'VyOS IKEv2 Profile'
+vpn_name = 'VyOS IKEv2 VPN'
if args.name:
vpn_name = args.name
@@ -73,7 +155,76 @@ data['ca_cn'] = ca_cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].v
data['cert_cn'] = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
data['ca_cert'] = conf.return_value(pki_base + ['ca', ca_name, 'certificate'])
-data['esp_proposal'] = conf.get_config_dict(ipsec_base + ['esp-group', data['esp_group'], 'proposal'], key_mangling=('-', '_'), get_first_key=True)
-data['ike_proposal'] = conf.get_config_dict(ipsec_base + ['ike-group', data['ike_group'], 'proposal'], key_mangling=('-', '_'), get_first_key=True)
+esp_proposals = conf.get_config_dict(ipsec_base + ['esp-group', data['esp_group'], 'proposal'],
+ key_mangling=('-', '_'), get_first_key=True)
+ike_proposal = conf.get_config_dict(ipsec_base + ['ike-group', data['ike_group'], 'proposal'],
+ key_mangling=('-', '_'), get_first_key=True)
+
+
+# This script works only for Apple iOS/iPadOS and Windows. Both operating systems
+# have different limitations thus we load the limitations based on the operating
+# system used.
+
+vyos2client_cipher = vyos2apple_cipher if args.os == 'ios' else vyos2windows_cipher;
+vyos2client_integrity = vyos2apple_integrity if args.os == 'ios' else vyos2windows_integrity;
+supported_dh_groups = ios_supported_dh_groups if args.os == 'ios' else windows_supported_dh_groups;
+
+# Create a dictionary containing client conform IKE settings
+ike = {}
+count = 1
+for _, proposal in ike_proposal.items():
+ if {'dh_group', 'encryption', 'hash'} <= set(proposal):
+ if (proposal['encryption'] in set(vyos2client_cipher) and
+ proposal['hash'] in set(vyos2client_integrity) and
+ proposal['dh_group'] in set(supported_dh_groups)):
+
+ # We 're-code' from the VyOS IPSec proposals to the Apple naming scheme
+ proposal['encryption'] = vyos2client_cipher[ proposal['encryption'] ]
+ proposal['hash'] = vyos2client_integrity[ proposal['hash'] ]
+
+ ike.update( { str(count) : proposal } )
+ count += 1
+
+# Create a dictionary containing Apple conform ESP settings
+esp = {}
+count = 1
+for _, proposal in esp_proposals.items():
+ if {'encryption', 'hash'} <= set(proposal):
+ if proposal['encryption'] in set(vyos2client_cipher) and proposal['hash'] in set(vyos2client_integrity):
+ # We 're-code' from the VyOS IPSec proposals to the Apple naming scheme
+ proposal['encryption'] = vyos2client_cipher[ proposal['encryption'] ]
+ proposal['hash'] = vyos2client_integrity[ proposal['hash'] ]
+
+ esp.update( { str(count) : proposal } )
+ count += 1
+try:
+ if len(ike) > 1:
+ # Propare the input questions for the user
+ tmp = '\n'
+ for number, options in ike.items():
+ tmp += f'({number}) Encryption {options["encryption"]}, Integrity {options["hash"]}, DH group {options["dh_group"]}\n'
+ tmp += '\nSelect one of the above IKE groups: '
+ data['ike_encryption'] = ike[ ask_input(tmp, valid_responses=list(ike)) ]
+ else:
+ data['ike_encryption'] = ike['1']
+
+ if len(esp) > 1:
+ tmp = '\n'
+ for number, options in esp.items():
+ tmp += f'({number}) Encryption {options["encryption"]}, Integrity {options["hash"]}\n'
+ tmp += '\nSelect one of the above ESP groups: '
+ data['esp_encryption'] = esp[ ask_input(tmp, valid_responses=list(esp)) ]
+ else:
+ data['esp_encryption'] = esp['1']
+
+except KeyboardInterrupt:
+ exit("Interrupted")
-print(render_to_string('ipsec/ios_profile.tmpl', data))
+print('\n\n==== <snip> ====')
+if args.os == 'ios':
+ print(render_to_string('ipsec/ios_profile.tmpl', data))
+ print('==== </snip> ====\n')
+ print('Save the XML from above to a new file named "vyos.mobileconfig" and E-Mail it to your phone.')
+elif args.os == 'windows':
+ print(render_to_string('ipsec/windows_profile.tmpl', data))
+ print('==== </snip> ====\n')
diff --git a/src/op_mode/show_dhcp.py b/src/op_mode/show_dhcp.py
index ff1e3cc56..4df275e04 100755
--- a/src/op_mode/show_dhcp.py
+++ b/src/op_mode/show_dhcp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2020 VyOS maintainers and contributors
+# Copyright (C) 2018-2021 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
@@ -27,8 +27,7 @@ from datetime import datetime
from isc_dhcp_leases import Lease, IscDhcpLeases
from vyos.config import Config
-from vyos.util import call
-
+from vyos.util import is_systemd_service_running
lease_file = "/config/dhcpd.leases"
pool_key = "shared-networkname"
@@ -217,7 +216,7 @@ if __name__ == '__main__':
exit(0)
# if dhcp server is down, inactive leases may still be shown as active, so warn the user.
- if call('systemctl -q is-active isc-dhcp-server.service') != 0:
+ if not is_systemd_service_running('isc-dhcp-server.service'):
print("WARNING: DHCP server is configured but not started. Data may be stale.")
if args.leases:
diff --git a/src/op_mode/show_dhcpv6.py b/src/op_mode/show_dhcpv6.py
index f70f04298..1f987ff7b 100755
--- a/src/op_mode/show_dhcpv6.py
+++ b/src/op_mode/show_dhcpv6.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2020 VyOS maintainers and contributors
+# Copyright (C) 2018-2021 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
@@ -27,7 +27,7 @@ from datetime import datetime
from isc_dhcp_leases import Lease, IscDhcpLeases
from vyos.config import Config
-from vyos.util import call
+from vyos.util import is_systemd_service_running
lease_file = "/config/dhcpdv6.leases"
pool_key = "shared-networkname"
@@ -202,7 +202,7 @@ if __name__ == '__main__':
exit(0)
# if dhcp server is down, inactive leases may still be shown as active, so warn the user.
- if call('systemctl -q is-active isc-dhcp-server6.service') != 0:
+ if not is_systemd_service_running('isc-dhcp-server6.service'):
print("WARNING: DHCPv6 server is configured but not started. Data may be stale.")
if args.leases:
diff --git a/src/services/vyos-configd b/src/services/vyos-configd
index 6f770b696..670b6e66a 100755
--- a/src/services/vyos-configd
+++ b/src/services/vyos-configd
@@ -133,8 +133,7 @@ def explicit_print(path, mode, msg):
logger.critical("error explicit_print")
def run_script(script, config, args) -> int:
- if args:
- script.argv = args
+ script.argv = args
config.set_level([])
try:
c = script.get_config(config)
@@ -208,7 +207,7 @@ def process_node_data(config, data) -> int:
return R_ERROR_DAEMON
script_name = None
- args = None
+ args = []
res = re.match(r'^(VYOS_TAGNODE_VALUE=[^/]+)?.*\/([^/]+).py(.*)', data)
if res.group(1):
@@ -221,7 +220,7 @@ def process_node_data(config, data) -> int:
return R_ERROR_DAEMON
if res.group(3):
args = res.group(3).split()
- args.insert(0, f'{script_name}.py')
+ args.insert(0, f'{script_name}.py')
if script_name not in include_set:
return R_PASS
diff --git a/src/systemd/isc-dhcp-server.service b/src/systemd/isc-dhcp-server.service
index 9aa70a7cc..a7d86e69c 100644
--- a/src/systemd/isc-dhcp-server.service
+++ b/src/systemd/isc-dhcp-server.service
@@ -14,10 +14,10 @@ Environment=PID_FILE=/run/dhcp-server/dhcpd.pid CONFIG_FILE=/run/dhcp-server/dhc
PIDFile=/run/dhcp-server/dhcpd.pid
ExecStartPre=/bin/sh -ec '\
touch ${LEASE_FILE}; \
-chown dhcpd:nogroup ${LEASE_FILE}* ; \
+chown dhcpd:vyattacfg ${LEASE_FILE}* ; \
chmod 664 ${LEASE_FILE}* ; \
-/usr/sbin/dhcpd -4 -t -T -q -user dhcpd -group nogroup -pf ${PID_FILE} -cf ${CONFIG_FILE} -lf ${LEASE_FILE} '
-ExecStart=/usr/sbin/dhcpd -4 -q -user dhcpd -group nogroup -pf ${PID_FILE} -cf ${CONFIG_FILE} -lf ${LEASE_FILE}
+/usr/sbin/dhcpd -4 -t -T -q -user dhcpd -group vyattacfg -pf ${PID_FILE} -cf ${CONFIG_FILE} -lf ${LEASE_FILE} '
+ExecStart=/usr/sbin/dhcpd -4 -q -user dhcpd -group vyattacfg -pf ${PID_FILE} -cf ${CONFIG_FILE} -lf ${LEASE_FILE}
Restart=always
[Install]