summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/conf_mode/arp.py114
-rwxr-xr-xsrc/conf_mode/bcast_relay.py4
-rwxr-xr-xsrc/conf_mode/conntrack.py6
-rwxr-xr-xsrc/conf_mode/conntrack_sync.py2
-rwxr-xr-xsrc/conf_mode/container.py (renamed from src/conf_mode/containers.py)73
-rwxr-xr-xsrc/conf_mode/dhcp_relay.py7
-rwxr-xr-xsrc/conf_mode/dhcp_server.py8
-rwxr-xr-xsrc/conf_mode/dhcpv6_relay.py7
-rwxr-xr-xsrc/conf_mode/dhcpv6_server.py37
-rwxr-xr-xsrc/conf_mode/dns_forwarding.py6
-rwxr-xr-xsrc/conf_mode/dynamic_dns.py2
-rwxr-xr-xsrc/conf_mode/firewall.py9
-rwxr-xr-xsrc/conf_mode/flow_accounting_conf.py4
-rwxr-xr-xsrc/conf_mode/high-availability.py2
-rwxr-xr-xsrc/conf_mode/host_name.py5
-rwxr-xr-xsrc/conf_mode/http-api.py2
-rwxr-xr-xsrc/conf_mode/https.py4
-rwxr-xr-xsrc/conf_mode/igmp_proxy.py5
-rwxr-xr-xsrc/conf_mode/interfaces-bonding.py8
-rwxr-xr-xsrc/conf_mode/interfaces-bridge.py8
-rwxr-xr-xsrc/conf_mode/interfaces-dummy.py2
-rwxr-xr-xsrc/conf_mode/interfaces-ethernet.py9
-rwxr-xr-xsrc/conf_mode/interfaces-geneve.py28
-rwxr-xr-xsrc/conf_mode/interfaces-l2tpv3.py6
-rwxr-xr-xsrc/conf_mode/interfaces-loopback.py2
-rwxr-xr-xsrc/conf_mode/interfaces-macsec.py4
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py17
-rwxr-xr-xsrc/conf_mode/interfaces-pppoe.py39
-rwxr-xr-xsrc/conf_mode/interfaces-pseudo-ethernet.py12
-rwxr-xr-xsrc/conf_mode/interfaces-tunnel.py4
-rwxr-xr-xsrc/conf_mode/interfaces-vti.py2
-rwxr-xr-xsrc/conf_mode/interfaces-vxlan.py15
-rwxr-xr-xsrc/conf_mode/interfaces-wireguard.py10
-rwxr-xr-xsrc/conf_mode/interfaces-wireless.py12
-rwxr-xr-xsrc/conf_mode/interfaces-wwan.py28
-rwxr-xr-xsrc/conf_mode/lldp.py11
-rwxr-xr-xsrc/conf_mode/nat.py13
-rwxr-xr-xsrc/conf_mode/nat66.py11
-rwxr-xr-xsrc/conf_mode/ntp.py4
-rwxr-xr-xsrc/conf_mode/policy-route.py5
-rwxr-xr-xsrc/conf_mode/policy.py2
-rwxr-xr-xsrc/conf_mode/protocols_bfd.py2
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py33
-rwxr-xr-xsrc/conf_mode/protocols_igmp.py2
-rwxr-xr-xsrc/conf_mode/protocols_isis.py6
-rwxr-xr-xsrc/conf_mode/protocols_mpls.py2
-rwxr-xr-xsrc/conf_mode/protocols_nhrp.py4
-rwxr-xr-xsrc/conf_mode/protocols_ospf.py8
-rwxr-xr-xsrc/conf_mode/protocols_ospfv3.py2
-rwxr-xr-xsrc/conf_mode/protocols_pim.py2
-rwxr-xr-xsrc/conf_mode/protocols_rip.py2
-rwxr-xr-xsrc/conf_mode/protocols_ripng.py2
-rwxr-xr-xsrc/conf_mode/protocols_rpki.py2
-rwxr-xr-xsrc/conf_mode/protocols_static.py7
-rwxr-xr-xsrc/conf_mode/protocols_static_multicast.py2
-rwxr-xr-xsrc/conf_mode/salt-minion.py75
-rwxr-xr-xsrc/conf_mode/service_console-server.py4
-rwxr-xr-xsrc/conf_mode/service_ids_fastnetmon.py4
-rwxr-xr-xsrc/conf_mode/service_ipoe-server.py4
-rwxr-xr-xsrc/conf_mode/service_mdns-repeater.py4
-rwxr-xr-xsrc/conf_mode/service_monitoring_telegraf.py39
-rwxr-xr-xsrc/conf_mode/service_pppoe-server.py4
-rwxr-xr-xsrc/conf_mode/service_router-advert.py2
-rwxr-xr-xsrc/conf_mode/service_upnp.py2
-rwxr-xr-xsrc/conf_mode/service_webproxy.py6
-rwxr-xr-xsrc/conf_mode/snmp.py23
-rwxr-xr-xsrc/conf_mode/ssh.py4
-rwxr-xr-xsrc/conf_mode/system-login.py4
-rwxr-xr-xsrc/conf_mode/system-logs.py4
-rwxr-xr-xsrc/conf_mode/system-option.py4
-rwxr-xr-xsrc/conf_mode/system-proxy.py90
-rwxr-xr-xsrc/conf_mode/system-syslog.py4
-rwxr-xr-xsrc/conf_mode/system_console.py2
-rwxr-xr-xsrc/conf_mode/system_lcd.py6
-rwxr-xr-xsrc/conf_mode/system_sysctl.py2
-rwxr-xr-xsrc/conf_mode/tftp_server.py7
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py20
-rwxr-xr-xsrc/conf_mode/vpn_l2tp.py4
-rwxr-xr-xsrc/conf_mode/vpn_openconnect.py88
-rwxr-xr-xsrc/conf_mode/vpn_pptp.py4
-rwxr-xr-xsrc/conf_mode/vpn_sstp.py4
-rwxr-xr-xsrc/conf_mode/vrf.py18
-rwxr-xr-xsrc/conf_mode/vrf_vni.py2
-rwxr-xr-xsrc/conf_mode/zone_policy.py2
-rwxr-xr-xsrc/etc/ppp/ip-up.d/99-vyos-pppoe-callback20
-rwxr-xr-xsrc/migration-scripts/interfaces/25-to-2654
-rwxr-xr-xsrc/migration-scripts/openconnect/1-to-254
-rwxr-xr-xsrc/migration-scripts/quagga/9-to-1062
-rwxr-xr-xsrc/migration-scripts/system/23-to-2485
-rwxr-xr-xsrc/op_mode/conntrack_sync.py2
-rwxr-xr-xsrc/op_mode/containers_op.py78
-rwxr-xr-xsrc/op_mode/generate_openconnect_otp_key.py65
-rwxr-xr-xsrc/op_mode/generate_ovpn_client_file.py4
-rwxr-xr-xsrc/op_mode/ikev2_profile_generator.py4
-rwxr-xr-xsrc/op_mode/restart_frr.py3
-rwxr-xr-xsrc/op_mode/show_dhcp.py3
-rwxr-xr-xsrc/op_mode/show_dhcpv6.py3
-rwxr-xr-xsrc/op_mode/show_openvpn.py23
-rwxr-xr-xsrc/op_mode/traceroute.py207
-rwxr-xr-xsrc/op_mode/vpn_ipsec.py17
-rwxr-xr-xsrc/services/vyos-hostsd8
-rwxr-xr-xsrc/validators/as-number-list29
-rwxr-xr-xsrc/validators/port-multi27
103 files changed, 1203 insertions, 606 deletions
diff --git a/src/conf_mode/arp.py b/src/conf_mode/arp.py
index aac07bd80..1cd8f5451 100755
--- a/src/conf_mode/arp.py
+++ b/src/conf_mode/arp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018 VyOS maintainers and contributors
+# Copyright (C) 2018-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -13,92 +13,62 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-#
-import sys
-import os
-import re
-import syslog as sl
+from sys import exit
from vyos.config import Config
+from vyos.configdict import node_changed
from vyos.util import call
from vyos import ConfigError
-
from vyos import airbag
airbag.enable()
-arp_cmd = '/usr/sbin/arp'
-
-def get_config():
- c = Config()
- if not c.exists('protocols static arp'):
- return None
-
- c.set_level('protocols static')
- config_data = {}
-
- for ip_addr in c.list_nodes('arp'):
- config_data.update(
- {
- ip_addr : c.return_value('arp ' + ip_addr + ' hwaddr')
- }
- )
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
- return config_data
+ base = ['protocols', 'static', 'arp']
+ arp = conf.get_config_dict(base, get_first_key=True)
-def generate(c):
- c_eff = Config()
- c_eff.set_level('protocols static')
- c_eff_cnf = {}
- for ip_addr in c_eff.list_effective_nodes('arp'):
- c_eff_cnf.update(
- {
- ip_addr : c_eff.return_effective_value('arp ' + ip_addr + ' hwaddr')
- }
- )
+ if 'interface' in arp:
+ for interface in arp['interface']:
+ tmp = node_changed(conf, base + ['interface', interface, 'address'], recursive=True)
+ if tmp: arp['interface'][interface].update({'address_old' : tmp})
- config_data = {
- 'remove' : [],
- 'update' : {}
- }
- ### removal
- if c == None:
- for ip_addr in c_eff_cnf:
- config_data['remove'].append(ip_addr)
- else:
- for ip_addr in c_eff_cnf:
- if not ip_addr in c or c[ip_addr] == None:
- config_data['remove'].append(ip_addr)
+ return arp
- ### add/update
- if c != None:
- for ip_addr in c:
- if not ip_addr in c_eff_cnf:
- config_data['update'][ip_addr] = c[ip_addr]
- if ip_addr in c_eff_cnf:
- if c[ip_addr] != c_eff_cnf[ip_addr] and c[ip_addr] != None:
- config_data['update'][ip_addr] = c[ip_addr]
+def verify(arp):
+ pass
- return config_data
+def generate(arp):
+ pass
-def apply(c):
- for ip_addr in c['remove']:
- sl.syslog(sl.LOG_NOTICE, "arp -d " + ip_addr)
- call(f'{arp_cmd} -d {ip_addr} >/dev/null 2>&1')
+def apply(arp):
+ if not arp:
+ return None
- for ip_addr in c['update']:
- sl.syslog(sl.LOG_NOTICE, "arp -s " + ip_addr + " " + c['update'][ip_addr])
- updated = c['update'][ip_addr]
- call(f'{arp_cmd} -s {ip_addr} {updated}')
+ if 'interface' in arp:
+ for interface, interface_config in arp['interface'].items():
+ # Delete old static ARP assignments first
+ if 'address_old' in interface_config:
+ for address in interface_config['address_old']:
+ call(f'ip neigh del {address} dev {interface}')
+ # Add new static ARP entries to interface
+ if 'address' not in interface_config:
+ continue
+ for address, address_config in interface_config['address'].items():
+ mac = address_config['mac']
+ call(f'ip neigh add {address} lladdr {mac} dev {interface}')
if __name__ == '__main__':
- try:
- c = get_config()
- ## syntax verification is done via cli
- config = generate(c)
- apply(config)
- except ConfigError as e:
- print(e)
- sys.exit(1)
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/bcast_relay.py b/src/conf_mode/bcast_relay.py
index d93a2a8f4..39a2971ce 100755
--- a/src/conf_mode/bcast_relay.py
+++ b/src/conf_mode/bcast_relay.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2017-2020 VyOS maintainers and contributors
+# Copyright (C) 2017-2022 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
@@ -78,7 +78,7 @@ def generate(relay):
continue
config['instance'] = instance
- render(config_file_base + instance, 'bcast-relay/udp-broadcast-relay.tmpl',
+ render(config_file_base + instance, 'bcast-relay/udp-broadcast-relay.j2',
config)
return None
diff --git a/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py
index aabf2bdf5..82289526f 100755
--- a/src/conf_mode/conntrack.py
+++ b/src/conf_mode/conntrack.py
@@ -101,9 +101,9 @@ def verify(conntrack):
return None
def generate(conntrack):
- render(conntrack_config, 'conntrack/vyos_nf_conntrack.conf.tmpl', conntrack)
- render(sysctl_file, 'conntrack/sysctl.conf.tmpl', conntrack)
- render(nftables_ct_file, 'conntrack/nftables-ct.tmpl', conntrack)
+ render(conntrack_config, 'conntrack/vyos_nf_conntrack.conf.j2', conntrack)
+ render(sysctl_file, 'conntrack/sysctl.conf.j2', conntrack)
+ render(nftables_ct_file, 'conntrack/nftables-ct.j2', conntrack)
# dry-run newly generated configuration
tmp = run(f'nft -c -f {nftables_ct_file}')
diff --git a/src/conf_mode/conntrack_sync.py b/src/conf_mode/conntrack_sync.py
index 34d1f7398..311e01529 100755
--- a/src/conf_mode/conntrack_sync.py
+++ b/src/conf_mode/conntrack_sync.py
@@ -111,7 +111,7 @@ def generate(conntrack):
os.unlink(config_file)
return None
- render(config_file, 'conntrackd/conntrackd.conf.tmpl', conntrack)
+ render(config_file, 'conntrackd/conntrackd.conf.j2', conntrack)
return None
diff --git a/src/conf_mode/containers.py b/src/conf_mode/container.py
index 516671844..7e1dc5911 100755
--- a/src/conf_mode/containers.py
+++ b/src/conf_mode/container.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-2022 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
@@ -28,7 +28,6 @@ from vyos.configdict import node_changed
from vyos.util import call
from vyos.util import cmd
from vyos.util import run
-from vyos.util import read_file
from vyos.util import write_file
from vyos.template import inc_ip
from vyos.template import is_ipv4
@@ -42,6 +41,20 @@ airbag.enable()
config_containers_registry = '/etc/containers/registries.conf'
config_containers_storage = '/etc/containers/storage.conf'
+def _run_rerun(container_cmd):
+ counter = 0
+ while True:
+ if counter >= 10:
+ break
+ try:
+ _cmd(container_cmd)
+ break
+ except:
+ counter = counter +1
+ sleep(0.5)
+
+ return None
+
def _cmd(command):
if os.path.exists('/tmp/vyos.container.debug'):
print(command)
@@ -77,10 +90,10 @@ def get_config(config=None):
container['name'][name] = dict_merge(default_values, container['name'][name])
# Delete container network, delete containers
- tmp = node_changed(conf, ['container', 'network'])
+ tmp = node_changed(conf, base + ['container', 'network'])
if tmp: container.update({'network_remove' : tmp})
- tmp = node_changed(conf, ['container', 'name'])
+ tmp = node_changed(conf, base + ['container', 'name'])
if tmp: container.update({'container_remove' : tmp})
return container
@@ -93,6 +106,20 @@ def verify(container):
# Add new container
if 'name' in container:
for name, container_config in container['name'].items():
+ # Container image is a mandatory option
+ if 'image' not in container_config:
+ raise ConfigError(f'Container image for "{name}" is mandatory!')
+
+ # verify container image exists locally
+ image = container_config['image']
+
+ # Check if requested container image exists locally. If it does not
+ # exist locally - inform the user.
+ if run(f'podman image exists {image}') != 0:
+ raise ConfigError(f'Image "{image}" used in contianer "{name}" does not exist '\
+ f'locally.\nPlease use "add container image {image}" to add it '\
+ 'to the system!')
+
if 'network' in container_config:
if len(container_config['network']) > 1:
raise ConfigError(f'Only one network can be specified for container "{name}"!')
@@ -151,10 +178,6 @@ def verify(container):
if not os.path.exists(source):
raise ConfigError(f'Volume "{volume}" source path "{source}" does not exist!')
- # Container image is a mandatory option
- if 'image' not in container_config:
- raise ConfigError(f'Container image for "{name}" is mandatory!')
-
# If 'allow-host-networks' or 'network' not set.
if 'allow_host_networks' not in container_config and 'network' not in container_config:
raise ConfigError(f'Must either set "network" or "allow-host-networks" for container "{name}"!')
@@ -194,6 +217,10 @@ def verify(container):
def generate(container):
# bail out early - looks like removal from running config
if not container:
+ if os.path.exists(config_containers_registry):
+ os.unlink(config_containers_registry)
+ if os.path.exists(config_containers_storage):
+ os.unlink(config_containers_storage)
return None
if 'network' in container:
@@ -227,8 +254,8 @@ def generate(container):
write_file(f'/etc/cni/net.d/{network}.conflist', json_write(tmp, indent=2))
- render(config_containers_registry, 'containers/registry.tmpl', container)
- render(config_containers_storage, 'containers/storage.tmpl', container)
+ render(config_containers_registry, 'container/registries.conf.j2', container)
+ render(config_containers_storage, 'container/storage.conf.j2', container)
return None
@@ -263,13 +290,6 @@ def apply(container):
memory = container_config['memory']
restart = container_config['restart']
- # Check if requested container image exists locally. If it does not, we
- # pull it. print() is the best way to have a good response from the
- # polling process to the user to display progress. If the image exists
- # locally, a user can update it running `update container image <name>`
- tmp = run(f'podman image exists {image}')
- if tmp != 0: print(os.system(f'podman pull {image}'))
-
# Add capability options. Should be in uppercase
cap_add = ''
if 'cap_add' in container_config:
@@ -318,7 +338,7 @@ def apply(container):
f'--memory {memory}m --memory-swap 0 --restart {restart} ' \
f'--name {name} {device} {port} {volume} {env_opt}'
if 'allow_host_networks' in container_config:
- run(f'{container_base_cmd} --net host {image}')
+ _run_rerun(f'{container_base_cmd} --net host {image}')
else:
for network in container_config['network']:
ipparam = ''
@@ -326,25 +346,10 @@ def apply(container):
address = container_config['network'][network]['address']
ipparam = f'--ip {address}'
- run(f'{container_base_cmd} --net {network} {ipparam} {image}')
+ _run_rerun(f'{container_base_cmd} --net {network} {ipparam} {image}')
return None
-def run(container_cmd):
- counter = 0
- while True:
- if counter >= 10:
- break
- try:
- _cmd(container_cmd)
- break
- except:
- counter = counter +1
- sleep(0.5)
-
- return None
-
-
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/dhcp_relay.py b/src/conf_mode/dhcp_relay.py
index 6352e0b4a..4de2ca2f3 100755
--- a/src/conf_mode/dhcp_relay.py
+++ b/src/conf_mode/dhcp_relay.py
@@ -66,18 +66,19 @@ def generate(relay):
if not relay:
return None
- render(config_file, 'dhcp-relay/dhcrelay.conf.tmpl', relay)
+ render(config_file, 'dhcp-relay/dhcrelay.conf.j2', relay)
return None
def apply(relay):
# bail out early - looks like removal from running config
+ service_name = 'isc-dhcp-relay.service'
if not relay:
- call('systemctl stop isc-dhcp-relay.service')
+ call(f'systemctl stop {service_name}')
if os.path.exists(config_file):
os.unlink(config_file)
return None
- call('systemctl restart isc-dhcp-relay.service')
+ call(f'systemctl restart {service_name}')
return None
diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py
index a8cef5ebf..52b682d6d 100755
--- a/src/conf_mode/dhcp_server.py
+++ b/src/conf_mode/dhcp_server.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2021 VyOS maintainers and contributors
+# Copyright (C) 2018-2022 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
@@ -109,7 +109,7 @@ def get_config(config=None):
if not conf.exists(base):
return None
- dhcp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ dhcp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True)
# T2665: defaults include lease time per TAG node which need to be added to
# individual subnet definitions
default_values = defaults(base + ['shared-network-name', 'subnet'])
@@ -286,7 +286,7 @@ def generate(dhcp):
# Please see: https://phabricator.vyos.net/T1129 for quoting of the raw
# parameters we can pass to ISC DHCPd
tmp_file = '/tmp/dhcpd.conf'
- render(tmp_file, 'dhcp-server/dhcpd.conf.tmpl', dhcp,
+ render(tmp_file, 'dhcp-server/dhcpd.conf.j2', dhcp,
formater=lambda _: _.replace("&quot;", '"'))
# XXX: as we have the ability for a user to pass in "raw" options via VyOS
# CLI (see T3544) we now ask ISC dhcpd to test the newly rendered
@@ -299,7 +299,7 @@ def generate(dhcp):
# Now that we know that the newly rendered configuration is "good" we can
# render the "real" configuration
- render(config_file, 'dhcp-server/dhcpd.conf.tmpl', dhcp,
+ render(config_file, 'dhcp-server/dhcpd.conf.j2', dhcp,
formater=lambda _: _.replace("&quot;", '"'))
return None
diff --git a/src/conf_mode/dhcpv6_relay.py b/src/conf_mode/dhcpv6_relay.py
index aea2c3b73..c1bd51f62 100755
--- a/src/conf_mode/dhcpv6_relay.py
+++ b/src/conf_mode/dhcpv6_relay.py
@@ -82,19 +82,20 @@ def generate(relay):
if not relay:
return None
- render(config_file, 'dhcp-relay/dhcrelay6.conf.tmpl', relay)
+ render(config_file, 'dhcp-relay/dhcrelay6.conf.j2', relay)
return None
def apply(relay):
# bail out early - looks like removal from running config
+ service_name = 'isc-dhcp-relay6.service'
if not relay:
# DHCPv6 relay support is removed in the commit
- call('systemctl stop isc-dhcp-relay6.service')
+ call(f'systemctl stop {service_name}')
if os.path.exists(config_file):
os.unlink(config_file)
return None
- call('systemctl restart isc-dhcp-relay6.service')
+ call(f'systemctl restart {service_name}')
return None
diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py
index e6a2e4486..078ff327c 100755
--- a/src/conf_mode/dhcpv6_server.py
+++ b/src/conf_mode/dhcpv6_server.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2020 VyOS maintainers and contributors
+# Copyright (C) 2018-2022 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
@@ -41,7 +41,9 @@ def get_config(config=None):
if not conf.exists(base):
return None
- dhcpv6 = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ dhcpv6 = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
return dhcpv6
def verify(dhcpv6):
@@ -51,7 +53,7 @@ def verify(dhcpv6):
# If DHCP is enabled we need one share-network
if 'shared_network_name' not in dhcpv6:
- raise ConfigError('No DHCPv6 shared networks configured. At least\n' \
+ raise ConfigError('No DHCPv6 shared networks configured. At least '\
'one DHCPv6 shared network must be configured.')
# Inspect shared-network/subnet
@@ -60,8 +62,9 @@ def verify(dhcpv6):
for network, network_config in dhcpv6['shared_network_name'].items():
# A shared-network requires a subnet definition
if 'subnet' not in network_config:
- raise ConfigError(f'No DHCPv6 lease subnets configured for "{network}". At least one\n' \
- 'lease subnet must be configured for each shared network!')
+ raise ConfigError(f'No DHCPv6 lease subnets configured for "{network}". '\
+ 'At least one lease subnet must be configured for '\
+ 'each shared network!')
for subnet, subnet_config in network_config['subnet'].items():
if 'address_range' in subnet_config:
@@ -83,20 +86,20 @@ def verify(dhcpv6):
# Stop address must be greater or equal to start address
if not ip_address(stop) >= ip_address(start):
- raise ConfigError(f'address-range stop address "{stop}" must be greater or equal\n' \
+ raise ConfigError(f'address-range stop address "{stop}" must be greater then or equal ' \
f'to the range start address "{start}"!')
# DHCPv6 range start address must be unique - two ranges can't
# start with the same address - makes no sense
if start in range6_start:
- raise ConfigError(f'Conflicting DHCPv6 lease range:\n' \
+ raise ConfigError(f'Conflicting DHCPv6 lease range: '\
f'Pool start address "{start}" defined multipe times!')
range6_start.append(start)
# DHCPv6 range stop address must be unique - two ranges can't
# end with the same address - makes no sense
if stop in range6_stop:
- raise ConfigError(f'Conflicting DHCPv6 lease range:\n' \
+ raise ConfigError(f'Conflicting DHCPv6 lease range: '\
f'Pool stop address "{stop}" defined multipe times!')
range6_stop.append(stop)
@@ -112,7 +115,7 @@ def verify(dhcpv6):
for prefix, prefix_config in subnet_config['prefix_delegation']['start'].items():
if 'stop' not in prefix_config:
- raise ConfigError(f'Stop address of delegated IPv6 prefix range "{prefix}"\n'
+ raise ConfigError(f'Stop address of delegated IPv6 prefix range "{prefix}" '\
f'must be configured')
if 'prefix_length' not in prefix_config:
@@ -126,6 +129,10 @@ def verify(dhcpv6):
if ip_address(mapping_config['ipv6_address']) not in ip_network(subnet):
raise ConfigError(f'static-mapping address for mapping "{mapping}" is not in subnet "{subnet}"!')
+ if 'vendor_option' in subnet_config:
+ if len(dict_search('vendor_option.cisco.tftp_server', subnet_config)) > 2:
+ raise ConfigError(f'No more then two Cisco tftp-servers should be defined for subnet "{subnet}"!')
+
# Subnets must be unique
if subnet in subnets:
raise ConfigError(f'DHCPv6 subnets must be unique! Subnet {subnet} defined multiple times!')
@@ -149,8 +156,8 @@ def verify(dhcpv6):
raise ConfigError('DHCPv6 conflicting subnet ranges: {0} overlaps {1}'.format(net, net2))
if not listen_ok:
- raise ConfigError('None of the DHCPv6 subnets are connected to a subnet6 on\n' \
- 'this machine. At least one subnet6 must be connected such that\n' \
+ raise ConfigError('None of the DHCPv6 subnets are connected to a subnet6 on '\
+ 'this machine. At least one subnet6 must be connected such that '\
'DHCPv6 listens on an interface!')
@@ -161,20 +168,20 @@ def generate(dhcpv6):
if not dhcpv6 or 'disable' in dhcpv6:
return None
- render(config_file, 'dhcp-server/dhcpdv6.conf.tmpl', dhcpv6)
+ render(config_file, 'dhcp-server/dhcpdv6.conf.j2', dhcpv6)
return None
def apply(dhcpv6):
# bail out early - looks like removal from running config
+ service_name = 'isc-dhcp-server6.service'
if not dhcpv6 or 'disable' in dhcpv6:
# DHCP server is removed in the commit
- call('systemctl stop isc-dhcp-server6.service')
+ call(f'systemctl stop {service_name}')
if os.path.exists(config_file):
os.unlink(config_file)
-
return None
- call('systemctl restart isc-dhcp-server6.service')
+ call(f'systemctl restart {service_name}')
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py
index fa9b21f20..f1c2d1f43 100755
--- a/src/conf_mode/dns_forwarding.py
+++ b/src/conf_mode/dns_forwarding.py
@@ -279,10 +279,10 @@ def generate(dns):
if not dns:
return None
- render(pdns_rec_config_file, 'dns-forwarding/recursor.conf.tmpl',
+ render(pdns_rec_config_file, 'dns-forwarding/recursor.conf.j2',
dns, user=pdns_rec_user, group=pdns_rec_group)
- render(pdns_rec_lua_conf_file, 'dns-forwarding/recursor.conf.lua.tmpl',
+ render(pdns_rec_lua_conf_file, 'dns-forwarding/recursor.conf.lua.j2',
dns, user=pdns_rec_user, group=pdns_rec_group)
for zone_filename in glob(f'{pdns_rec_run_dir}/zone.*.conf'):
@@ -290,7 +290,7 @@ def generate(dns):
if 'authoritative_zones' in dns:
for zone in dns['authoritative_zones']:
- render(zone['file'], 'dns-forwarding/recursor.zone.conf.tmpl',
+ render(zone['file'], 'dns-forwarding/recursor.zone.conf.j2',
zone, user=pdns_rec_user, group=pdns_rec_group)
diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py
index a31e5ed75..06a2f7e15 100755
--- a/src/conf_mode/dynamic_dns.py
+++ b/src/conf_mode/dynamic_dns.py
@@ -131,7 +131,7 @@ def generate(dyndns):
if not dyndns:
return None
- render(config_file, 'dynamic-dns/ddclient.conf.tmpl', dyndns)
+ render(config_file, 'dynamic-dns/ddclient.conf.j2', dyndns)
return None
def apply(dyndns):
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index f33198a49..6924bf555 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -21,6 +21,7 @@ from glob import glob
from json import loads
from sys import exit
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
@@ -225,7 +226,7 @@ def verify_rule(firewall, rule_conf, ipv6):
raise ConfigError(f'Invalid {error_group} "{group_name}" on firewall rule')
if not group_obj:
- print(f'WARNING: {error_group} "{group_name}" has no members')
+ Warning(f'{error_group} "{group_name}" has no members!')
if 'port' in side_conf or dict_search_args(side_conf, 'group', 'port_group'):
if 'protocol' not in rule_conf:
@@ -326,8 +327,8 @@ def generate(firewall):
else:
firewall['cleanup_commands'] = cleanup_commands(firewall)
- render(nftables_conf, 'firewall/nftables.tmpl', firewall)
- render(nftables_defines_conf, 'firewall/nftables-defines.tmpl', firewall)
+ render(nftables_conf, 'firewall/nftables.j2', firewall)
+ render(nftables_defines_conf, 'firewall/nftables-defines.j2', firewall)
return None
def apply_sysfs(firewall):
@@ -395,7 +396,7 @@ def resync_policy_route():
# Update policy route as firewall groups were updated
tmp = run(policy_route_conf_script)
if tmp > 0:
- print('Warning: Failed to re-apply policy route configuration')
+ Warning('Failed to re-apply policy route configuration!')
def apply(firewall):
if 'first_install' in firewall:
diff --git a/src/conf_mode/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py
index 25bf54790..7f7a98b04 100755
--- a/src/conf_mode/flow_accounting_conf.py
+++ b/src/conf_mode/flow_accounting_conf.py
@@ -239,8 +239,8 @@ def generate(flow_config):
if not flow_config:
return None
- render(uacctd_conf_path, 'pmacct/uacctd.conf.tmpl', flow_config)
- render(systemd_override, 'pmacct/override.conf.tmpl', flow_config)
+ render(uacctd_conf_path, 'pmacct/uacctd.conf.j2', flow_config)
+ render(systemd_override, 'pmacct/override.conf.j2', flow_config)
# Reload systemd manager configuration
call('systemctl daemon-reload')
diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py
index 7d51bb393..f939f9469 100755
--- a/src/conf_mode/high-availability.py
+++ b/src/conf_mode/high-availability.py
@@ -152,7 +152,7 @@ def generate(ha):
if not ha:
return None
- render(VRRP.location['config'], 'high-availability/keepalived.conf.tmpl', ha)
+ render(VRRP.location['config'], 'high-availability/keepalived.conf.j2', ha)
return None
def apply(ha):
diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py
index 87bad0dc6..93f244f42 100755
--- a/src/conf_mode/host_name.py
+++ b/src/conf_mode/host_name.py
@@ -21,13 +21,14 @@ import copy
import vyos.util
import vyos.hostsd_client
-from vyos import ConfigError
+from vyos.base import Warning
from vyos.config import Config
from vyos.ifconfig import Section
from vyos.template import is_ip
from vyos.util import cmd
from vyos.util import call
from vyos.util import process_named_running
+from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -113,7 +114,7 @@ def verify(hosts):
for interface, interface_config in hosts['nameservers_dhcp_interfaces'].items():
# Warnin user if interface does not have DHCP or DHCPv6 configured
if not set(interface_config).intersection(['dhcp', 'dhcpv6']):
- print(f'WARNING: "{interface}" is not a DHCP interface but uses DHCP name-server option!')
+ Warning(f'"{interface}" is not a DHCP interface but uses DHCP name-server option!')
return None
diff --git a/src/conf_mode/http-api.py b/src/conf_mode/http-api.py
index 00f3d4f7f..4a7906c17 100755
--- a/src/conf_mode/http-api.py
+++ b/src/conf_mode/http-api.py
@@ -117,7 +117,7 @@ def generate(http_api):
with open(api_conf_file, 'w') as f:
json.dump(http_api, f, indent=2)
- render(systemd_service, 'https/vyos-http-api.service.tmpl', http_api)
+ render(systemd_service, 'https/vyos-http-api.service.j2', http_api)
return None
def apply(http_api):
diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py
index 37fa36797..3057357fc 100755
--- a/src/conf_mode/https.py
+++ b/src/conf_mode/https.py
@@ -214,8 +214,8 @@ def generate(https):
'certbot': certbot
}
- render(config_file, 'https/nginx.default.tmpl', data)
- render(systemd_override, 'https/override.conf.tmpl', https)
+ render(config_file, 'https/nginx.default.j2', data)
+ render(systemd_override, 'https/override.conf.j2', https)
return None
def apply(https):
diff --git a/src/conf_mode/igmp_proxy.py b/src/conf_mode/igmp_proxy.py
index fb030c9f3..de6a51c64 100755
--- a/src/conf_mode/igmp_proxy.py
+++ b/src/conf_mode/igmp_proxy.py
@@ -19,6 +19,7 @@ import os
from sys import exit
from netifaces import interfaces
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.template import render
@@ -92,10 +93,10 @@ def generate(igmp_proxy):
# bail out early - service is disabled, but inform user
if 'disable' in igmp_proxy:
- print('WARNING: IGMP Proxy will be deactivated because it is disabled')
+ Warning('IGMP Proxy will be deactivated because it is disabled')
return None
- render(config_file, 'igmp-proxy/igmpproxy.conf.tmpl', igmp_proxy)
+ render(config_file, 'igmp-proxy/igmpproxy.conf.j2', igmp_proxy)
return None
diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py
index ad5a0f499..4167594e3 100755
--- a/src/conf_mode/interfaces-bonding.py
+++ b/src/conf_mode/interfaces-bonding.py
@@ -68,7 +68,7 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'bonding']
- bond = get_interface_dict(conf, base)
+ ifname, bond = get_interface_dict(conf, base)
# To make our own life easier transfor the list of member interfaces
# into a dictionary - we will use this to add additional information
@@ -81,14 +81,14 @@ def get_config(config=None):
if 'mode' in bond:
bond['mode'] = get_bond_mode(bond['mode'])
- tmp = leaf_node_changed(conf, ['mode'])
+ tmp = leaf_node_changed(conf, base + [ifname, 'mode'])
if tmp: bond.update({'shutdown_required': {}})
- tmp = leaf_node_changed(conf, ['lacp-rate'])
+ tmp = leaf_node_changed(conf, base + [ifname, 'lacp-rate'])
if tmp: bond.update({'shutdown_required': {}})
# determine which members have been removed
- interfaces_removed = leaf_node_changed(conf, ['member', 'interface'])
+ interfaces_removed = leaf_node_changed(conf, base + [ifname, 'member', 'interface'])
if interfaces_removed:
bond.update({'shutdown_required': {}})
if 'member' not in bond:
diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py
index b1f7e6d7c..38ae727c1 100755
--- a/src/conf_mode/interfaces-bridge.py
+++ b/src/conf_mode/interfaces-bridge.py
@@ -50,15 +50,15 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'bridge']
- bridge = get_interface_dict(conf, base)
+ ifname, bridge = get_interface_dict(conf, base)
# determine which members have been removed
- tmp = node_changed(conf, ['member', 'interface'], key_mangling=('-', '_'))
+ tmp = node_changed(conf, base + [ifname, 'member', 'interface'], key_mangling=('-', '_'))
if tmp:
if 'member' in bridge:
- bridge['member'].update({'interface_remove': tmp })
+ bridge['member'].update({'interface_remove' : tmp })
else:
- bridge.update({'member': {'interface_remove': tmp }})
+ bridge.update({'member' : {'interface_remove' : tmp }})
if dict_search('member.interface', bridge):
# XXX: T2665: we need a copy of the dict keys for iteration, else we will get:
diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py
index 4a1eb7b93..e771581e1 100755
--- a/src/conf_mode/interfaces-dummy.py
+++ b/src/conf_mode/interfaces-dummy.py
@@ -37,7 +37,7 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'dummy']
- dummy = get_interface_dict(conf, base)
+ _, dummy = get_interface_dict(conf, base)
return dummy
def verify(dummy):
diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py
index 6aea7a80e..fec4456fb 100755
--- a/src/conf_mode/interfaces-ethernet.py
+++ b/src/conf_mode/interfaces-ethernet.py
@@ -19,6 +19,7 @@ import os
from glob import glob
from sys import exit
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import get_interface_dict
from vyos.configverify import verify_address
@@ -64,7 +65,7 @@ def get_config(config=None):
get_first_key=True, no_tag_node_value_mangle=True)
base = ['interfaces', 'ethernet']
- ethernet = get_interface_dict(conf, base)
+ _, ethernet = get_interface_dict(conf, base)
if 'deleted' not in ethernet:
if pki: ethernet['pki'] = pki
@@ -142,8 +143,8 @@ def verify(ethernet):
raise ConfigError('XDP requires additional TX queues, too few available!')
if {'is_bond_member', 'mac'} <= set(ethernet):
- print(f'WARNING: changing mac address "{mac}" will be ignored as "{ifname}" '
- f'is a member of bond "{is_bond_member}"'.format(**ethernet))
+ Warning(f'changing mac address "{mac}" will be ignored as "{ifname}" ' \
+ f'is a member of bond "{is_bond_member}"'.format(**ethernet))
# use common function to verify VLAN configuration
verify_vlan_config(ethernet)
@@ -152,7 +153,7 @@ def verify(ethernet):
def generate(ethernet):
if 'eapol' in ethernet:
render(wpa_suppl_conf.format(**ethernet),
- 'ethernet/wpa_supplicant.conf.tmpl', ethernet)
+ 'ethernet/wpa_supplicant.conf.j2', ethernet)
ifname = ethernet['ifname']
cert_file_path = os.path.join(cfg_dir, f'{ifname}_cert.pem')
diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py
index 3a668226b..b9cf2fa3c 100755
--- a/src/conf_mode/interfaces-geneve.py
+++ b/src/conf_mode/interfaces-geneve.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019-2020 VyOS maintainers and contributors
+# Copyright (C) 2019-2022 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,6 +21,8 @@ from netifaces import interfaces
from vyos.config import Config
from vyos.configdict import get_interface_dict
+from vyos.configdict import leaf_node_changed
+from vyos.configdict import is_node_changed
from vyos.configverify import verify_address
from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_bridge_delete
@@ -41,7 +43,18 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'geneve']
- geneve = get_interface_dict(conf, base)
+ ifname, geneve = get_interface_dict(conf, base)
+
+ # GENEVE interfaces are picky and require recreation if certain parameters
+ # change. But a GENEVE interface should - of course - not be re-created if
+ # it's description or IP address is adjusted. Feels somehow logic doesn't it?
+ for cli_option in ['remote', 'vni']:
+ if leaf_node_changed(conf, base + [ifname, cli_option]):
+ geneve.update({'rebuild_required': {}})
+
+ if is_node_changed(conf, base + [ifname, 'parameters']):
+ geneve.update({'rebuild_required': {}})
+
return geneve
def verify(geneve):
@@ -67,11 +80,12 @@ def generate(geneve):
def apply(geneve):
# Check if GENEVE interface already exists
- if geneve['ifname'] in interfaces():
- g = GeneveIf(geneve['ifname'])
- # GENEVE is super picky and the tunnel always needs to be recreated,
- # thus we can simply always delete it first.
- g.remove()
+ if 'rebuild_required' in geneve or 'delete' in geneve:
+ if geneve['ifname'] in interfaces():
+ g = GeneveIf(geneve['ifname'])
+ # GENEVE is super picky and the tunnel always needs to be recreated,
+ # thus we can simply always delete it first.
+ g.remove()
if 'deleted' not in geneve:
# Finally create the new interface
diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py
index 22256bf4f..6a486f969 100755
--- a/src/conf_mode/interfaces-l2tpv3.py
+++ b/src/conf_mode/interfaces-l2tpv3.py
@@ -45,15 +45,15 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'l2tpv3']
- l2tpv3 = get_interface_dict(conf, base)
+ ifname, l2tpv3 = get_interface_dict(conf, base)
# To delete an l2tpv3 interface we need the current tunnel and session-id
if 'deleted' in l2tpv3:
- tmp = leaf_node_changed(conf, ['tunnel-id'])
+ tmp = leaf_node_changed(conf, base + [ifname, 'tunnel-id'])
# leaf_node_changed() returns a list
l2tpv3.update({'tunnel_id': tmp[0]})
- tmp = leaf_node_changed(conf, ['session-id'])
+ tmp = leaf_node_changed(conf, base + [ifname, 'session-id'])
l2tpv3.update({'session_id': tmp[0]})
return l2tpv3
diff --git a/src/conf_mode/interfaces-loopback.py b/src/conf_mode/interfaces-loopback.py
index e4bc15bb5..08d34477a 100755
--- a/src/conf_mode/interfaces-loopback.py
+++ b/src/conf_mode/interfaces-loopback.py
@@ -36,7 +36,7 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'loopback']
- loopback = get_interface_dict(conf, base)
+ _, loopback = get_interface_dict(conf, base)
return loopback
def verify(loopback):
diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py
index 96fc1c41c..279dd119b 100755
--- a/src/conf_mode/interfaces-macsec.py
+++ b/src/conf_mode/interfaces-macsec.py
@@ -48,7 +48,7 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'macsec']
- macsec = get_interface_dict(conf, base)
+ ifname, macsec = get_interface_dict(conf, base)
# Check if interface has been removed
if 'deleted' in macsec:
@@ -98,7 +98,7 @@ def verify(macsec):
def generate(macsec):
render(wpa_suppl_conf.format(**macsec),
- 'macsec/wpa_supplicant.conf.tmpl', macsec)
+ 'macsec/wpa_supplicant.conf.j2', macsec)
return None
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index 83d1c6d9b..4750ca3e8 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -32,7 +32,7 @@ from shutil import rmtree
from vyos.config import Config
from vyos.configdict import get_interface_dict
-from vyos.configdict import leaf_node_changed
+from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_mirror_redirect
@@ -85,13 +85,12 @@ def get_config(config=None):
tmp_pki = conf.get_config_dict(['pki'], key_mangling=('-', '_'),
get_first_key=True, no_tag_node_value_mangle=True)
- openvpn = get_interface_dict(conf, base)
+ ifname, openvpn = get_interface_dict(conf, base)
if 'deleted' not in openvpn:
openvpn['pki'] = tmp_pki
-
- tmp = leaf_node_changed(conf, ['openvpn-option'])
- if tmp: openvpn['restart_required'] = ''
+ if is_node_changed(conf, base + [ifname, 'openvpn-option']):
+ openvpn.update({'restart_required': {}})
# We have to get the dict using 'get_config_dict' instead of 'get_interface_dict'
# as 'get_interface_dict' merges the defaults in, so we can not check for defaults in there.
@@ -608,7 +607,7 @@ def generate(openvpn):
# Generate User/Password authentication file
if 'authentication' in openvpn:
- render(openvpn['auth_user_pass_file'], 'openvpn/auth.pw.tmpl', openvpn,
+ render(openvpn['auth_user_pass_file'], 'openvpn/auth.pw.j2', openvpn,
user=user, group=group, permission=0o600)
else:
# delete old auth file if present
@@ -624,16 +623,16 @@ def generate(openvpn):
# Our client need's to know its subnet mask ...
client_config['server_subnet'] = dict_search('server.subnet', openvpn)
- render(client_file, 'openvpn/client.conf.tmpl', client_config,
+ render(client_file, 'openvpn/client.conf.j2', client_config,
user=user, group=group)
# we need to support quoting of raw parameters from OpenVPN CLI
# see https://phabricator.vyos.net/T1632
- render(cfg_file.format(**openvpn), 'openvpn/server.conf.tmpl', openvpn,
+ render(cfg_file.format(**openvpn), 'openvpn/server.conf.j2', openvpn,
formater=lambda _: _.replace("&quot;", '"'), user=user, group=group)
# Render 20-override.conf for OpenVPN service
- render(service_file.format(**openvpn), 'openvpn/service-override.conf.tmpl', openvpn,
+ render(service_file.format(**openvpn), 'openvpn/service-override.conf.j2', openvpn,
formater=lambda _: _.replace("&quot;", '"'), user=user, group=group)
# Reload systemd services config to apply an override
call(f'systemctl daemon-reload')
diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py
index bfb1fadd5..e2fdc7a42 100755
--- a/src/conf_mode/interfaces-pppoe.py
+++ b/src/conf_mode/interfaces-pppoe.py
@@ -22,7 +22,9 @@ from netifaces import interfaces
from vyos.config import Config
from vyos.configdict import get_interface_dict
+from vyos.configdict import is_node_changed
from vyos.configdict import leaf_node_changed
+from vyos.configdict import get_pppoe_interfaces
from vyos.configverify import verify_authentication
from vyos.configverify import verify_source_interface
from vyos.configverify import verify_interface_exists
@@ -47,33 +49,17 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'pppoe']
- pppoe = get_interface_dict(conf, base)
+ ifname, pppoe = get_interface_dict(conf, base)
# We should only terminate the PPPoE session if critical parameters change.
# All parameters that can be changed on-the-fly (like interface description)
# should not lead to a reconnect!
- tmp = leaf_node_changed(conf, ['access-concentrator'])
- if tmp: pppoe.update({'shutdown_required': {}})
-
- tmp = leaf_node_changed(conf, ['connect-on-demand'])
- if tmp: pppoe.update({'shutdown_required': {}})
-
- tmp = leaf_node_changed(conf, ['service-name'])
- if tmp: pppoe.update({'shutdown_required': {}})
-
- tmp = leaf_node_changed(conf, ['source-interface'])
- if tmp: pppoe.update({'shutdown_required': {}})
-
- tmp = leaf_node_changed(conf, ['vrf'])
- # leaf_node_changed() returns a list, as VRF is a non-multi node, there
- # will be only one list element
- if tmp: pppoe.update({'vrf_old': tmp[0]})
-
- tmp = leaf_node_changed(conf, ['authentication', 'user'])
- if tmp: pppoe.update({'shutdown_required': {}})
-
- tmp = leaf_node_changed(conf, ['authentication', 'password'])
- if tmp: pppoe.update({'shutdown_required': {}})
+ for options in ['access-concentrator', 'connect-on-demand', 'service-name',
+ 'source-interface', 'vrf', 'no-default-route', 'authentication']:
+ if is_node_changed(conf, base + [ifname, options]):
+ pppoe.update({'shutdown_required': {}})
+ # bail out early - no need to further process other nodes
+ break
return pppoe
@@ -106,7 +92,7 @@ def generate(pppoe):
return None
# Create PPP configuration files
- render(config_pppoe, 'pppoe/peer.tmpl', pppoe, permission=0o640)
+ render(config_pppoe, 'pppoe/peer.j2', pppoe, permission=0o640)
return None
@@ -120,7 +106,7 @@ def apply(pppoe):
return None
# reconnect should only be necessary when certain config options change,
- # like ACS name, authentication, no-peer-dns, source-interface
+ # like ACS name, authentication ... (see get_config() for details)
if ((not is_systemd_service_running(f'ppp@{ifname}.service')) or
'shutdown_required' in pppoe):
@@ -130,6 +116,9 @@ def apply(pppoe):
p.remove()
call(f'systemctl restart ppp@{ifname}.service')
+ # When interface comes "live" a hook is called:
+ # /etc/ppp/ip-up.d/99-vyos-pppoe-callback
+ # which triggers PPPoEIf.update()
else:
if os.path.isdir(f'/sys/class/net/{ifname}'):
p = PPPoEIf(ifname)
diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py
index f2c85554f..1cd3fe276 100755
--- a/src/conf_mode/interfaces-pseudo-ethernet.py
+++ b/src/conf_mode/interfaces-pseudo-ethernet.py
@@ -18,7 +18,7 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import get_interface_dict
-from vyos.configdict import leaf_node_changed
+from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
from vyos.configverify import verify_address
from vyos.configverify import verify_bridge_delete
@@ -42,14 +42,14 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'pseudo-ethernet']
- peth = get_interface_dict(conf, base)
+ ifname, peth = get_interface_dict(conf, base)
- mode = leaf_node_changed(conf, ['mode'])
- if mode: peth.update({'mode_old' : mode})
+ mode = is_node_changed(conf, ['mode'])
+ if mode: peth.update({'shutdown_required' : {}})
if 'source_interface' in peth:
- peth['parent'] = get_interface_dict(conf, ['interfaces', 'ethernet'],
- peth['source_interface'])
+ _, peth['parent'] = get_interface_dict(conf, ['interfaces', 'ethernet'],
+ peth['source_interface'])
return peth
def verify(peth):
diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py
index f4668d976..eff7f373c 100755
--- a/src/conf_mode/interfaces-tunnel.py
+++ b/src/conf_mode/interfaces-tunnel.py
@@ -48,10 +48,10 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'tunnel']
- tunnel = get_interface_dict(conf, base)
+ ifname, tunnel = get_interface_dict(conf, base)
if 'deleted' not in tunnel:
- tmp = leaf_node_changed(conf, ['encapsulation'])
+ tmp = leaf_node_changed(conf, base + [ifname, 'encapsulation'])
if tmp: tunnel.update({'encapsulation_changed': {}})
# We also need to inspect other configured tunnels as there are Kernel
diff --git a/src/conf_mode/interfaces-vti.py b/src/conf_mode/interfaces-vti.py
index f06fdff1b..f4b0436af 100755
--- a/src/conf_mode/interfaces-vti.py
+++ b/src/conf_mode/interfaces-vti.py
@@ -36,7 +36,7 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'vti']
- vti = get_interface_dict(conf, base)
+ _, vti = get_interface_dict(conf, base)
return vti
def verify(vti):
diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py
index 0a9b51cac..f44d754ba 100755
--- a/src/conf_mode/interfaces-vxlan.py
+++ b/src/conf_mode/interfaces-vxlan.py
@@ -19,9 +19,11 @@ import os
from sys import exit
from netifaces import interfaces
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import get_interface_dict
from vyos.configdict import leaf_node_changed
+from vyos.configdict import is_node_changed
from vyos.configverify import verify_address
from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_mtu_ipv6
@@ -44,18 +46,19 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'vxlan']
- vxlan = get_interface_dict(conf, base)
+ ifname, vxlan = get_interface_dict(conf, base)
# VXLAN interfaces are picky and require recreation if certain parameters
# change. But a VXLAN interface should - of course - not be re-created if
# it's description or IP address is adjusted. Feels somehow logic doesn't it?
for cli_option in ['external', 'gpe', 'group', 'port', 'remote',
- 'source-address', 'source-interface', 'vni',
- 'parameters ip dont-fragment', 'parameters ip tos',
- 'parameters ip ttl']:
- if leaf_node_changed(conf, cli_option.split()):
+ 'source-address', 'source-interface', 'vni']:
+ if leaf_node_changed(conf, base + [ifname, cli_option]):
vxlan.update({'rebuild_required': {}})
+ if is_node_changed(conf, base + [ifname, 'parameters']):
+ vxlan.update({'rebuild_required': {}})
+
# We need to verify that no other VXLAN tunnel is configured when external
# mode is in use - Linux Kernel limitation
conf.set_level(base)
@@ -78,7 +81,7 @@ def verify(vxlan):
return None
if int(vxlan['mtu']) < 1500:
- print('WARNING: RFC7348 recommends VXLAN tunnels preserve a 1500 byte MTU')
+ Warning('RFC7348 recommends VXLAN tunnels preserve a 1500 byte MTU')
if 'group' in vxlan:
if 'source_interface' not in vxlan:
diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py
index b404375d6..180ffa507 100755
--- a/src/conf_mode/interfaces-wireguard.py
+++ b/src/conf_mode/interfaces-wireguard.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2020 VyOS maintainers and contributors
+# Copyright (C) 2018-2022 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
@@ -46,17 +46,17 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'wireguard']
- wireguard = get_interface_dict(conf, base)
+ ifname, wireguard = get_interface_dict(conf, base)
# Check if a port was changed
- wireguard['port_changed'] = leaf_node_changed(conf, ['port'])
+ wireguard['port_changed'] = leaf_node_changed(conf, base + [ifname, 'port'])
# Determine which Wireguard peer has been removed.
# Peers can only be removed with their public key!
dict = {}
- tmp = node_changed(conf, ['peer'], key_mangling=('-', '_'))
+ tmp = node_changed(conf, base + [ifname, 'peer'], key_mangling=('-', '_'))
for peer in (tmp or []):
- public_key = leaf_node_changed(conf, ['peer', peer, 'public_key'])
+ public_key = leaf_node_changed(conf, base + [ifname, 'peer', peer, 'public_key'])
if public_key:
dict = dict_merge({'peer_remove' : {peer : {'public_key' : public_key[0]}}}, dict)
wireguard.update(dict)
diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py
index 500952df1..d34297063 100755
--- a/src/conf_mode/interfaces-wireless.py
+++ b/src/conf_mode/interfaces-wireless.py
@@ -76,15 +76,19 @@ def get_config(config=None):
conf = Config()
base = ['interfaces', 'wireless']
- wifi = get_interface_dict(conf, base)
+ ifname, wifi = get_interface_dict(conf, base)
# Cleanup "delete" default values when required user selectable values are
# not defined at all
- tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True)
+ tmp = conf.get_config_dict(base + [ifname], key_mangling=('-', '_'),
+ get_first_key=True)
if not (dict_search('security.wpa.passphrase', tmp) or
dict_search('security.wpa.radius', tmp)):
if 'deleted' not in wifi:
del wifi['security']['wpa']
+ # if 'security' key is empty, drop it too
+ if len(wifi['security']) == 0:
+ del wifi['security']
# defaults include RADIUS server specifics per TAG node which need to be
# added to individual RADIUS servers instead - so we can simply delete them
@@ -244,11 +248,11 @@ def generate(wifi):
# render appropriate new config files depending on access-point or station mode
if wifi['type'] == 'access-point':
- render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.tmpl',
+ render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.j2',
wifi)
elif wifi['type'] == 'station':
- render(wpa_suppl_conf.format(**wifi), 'wifi/wpa_supplicant.conf.tmpl',
+ render(wpa_suppl_conf.format(**wifi), 'wifi/wpa_supplicant.conf.j2',
wifi)
return None
diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py
index 9a33039a3..e275ace84 100755
--- a/src/conf_mode/interfaces-wwan.py
+++ b/src/conf_mode/interfaces-wwan.py
@@ -21,7 +21,7 @@ from time import sleep
from vyos.config import Config
from vyos.configdict import get_interface_dict
-from vyos.configdict import leaf_node_changed
+from vyos.configdict import is_node_changed
from vyos.configverify import verify_authentication
from vyos.configverify import verify_interface_exists
from vyos.configverify import verify_mirror_redirect
@@ -50,42 +50,36 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'wwan']
- wwan = get_interface_dict(conf, base)
+ ifname, wwan = get_interface_dict(conf, base)
# We should only terminate the WWAN session if critical parameters change.
# All parameters that can be changed on-the-fly (like interface description)
# should not lead to a reconnect!
- tmp = leaf_node_changed(conf, ['address'])
+ tmp = is_node_changed(conf, base + [ifname, 'address'])
if tmp: wwan.update({'shutdown_required': {}})
- tmp = leaf_node_changed(conf, ['apn'])
+ tmp = is_node_changed(conf, base + [ifname, 'apn'])
if tmp: wwan.update({'shutdown_required': {}})
- tmp = leaf_node_changed(conf, ['disable'])
+ tmp = is_node_changed(conf, base + [ifname, 'disable'])
if tmp: wwan.update({'shutdown_required': {}})
- tmp = leaf_node_changed(conf, ['vrf'])
- # leaf_node_changed() returns a list, as VRF is a non-multi node, there
- # will be only one list element
- if tmp: wwan.update({'vrf_old': tmp[0]})
-
- tmp = leaf_node_changed(conf, ['authentication', 'user'])
+ tmp = is_node_changed(conf, base + [ifname, 'vrf'])
if tmp: wwan.update({'shutdown_required': {}})
- tmp = leaf_node_changed(conf, ['authentication', 'password'])
+ tmp = is_node_changed(conf, base + [ifname, 'authentication'])
if tmp: wwan.update({'shutdown_required': {}})
- tmp = leaf_node_changed(conf, ['ipv6', 'address', 'autoconf'])
+ tmp = is_node_changed(conf, base + [ifname, 'ipv6', 'address', 'autoconf'])
if tmp: wwan.update({'shutdown_required': {}})
# We need to know the amount of other WWAN interfaces as ModemManager needs
# to be started or stopped.
conf.set_level(base)
- wwan['other_interfaces'] = conf.get_config_dict([], key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)
+ _, wwan['other_interfaces'] = conf.get_config_dict([], key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
- ifname = wwan['ifname']
# This if-clause is just to be sure - it will always evaluate to true
if ifname in wwan['other_interfaces']:
del wwan['other_interfaces'][ifname]
diff --git a/src/conf_mode/lldp.py b/src/conf_mode/lldp.py
index db8328259..c703c1fe0 100755
--- a/src/conf_mode/lldp.py
+++ b/src/conf_mode/lldp.py
@@ -18,6 +18,7 @@ import os
from sys import exit
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.validate import is_addr_assigned
@@ -84,11 +85,11 @@ def verify(lldp):
if 'management_address' in lldp:
for address in lldp['management_address']:
- message = f'WARNING: LLDP management address "{address}" is invalid'
+ message = f'LLDP management address "{address}" is invalid'
if is_loopback_addr(address):
- print(f'{message} - loopback address')
+ Warning(f'{message} - loopback address')
elif not is_addr_assigned(address):
- print(f'{message} - not assigned to any interface')
+ Warning(f'{message} - not assigned to any interface')
if 'interface' in lldp:
for interface, interface_config in lldp['interface'].items():
@@ -110,8 +111,8 @@ def generate(lldp):
if lldp is None:
return
- render(config_file, 'lldp/lldpd.tmpl', lldp)
- render(vyos_config_file, 'lldp/vyos.conf.tmpl', lldp)
+ render(config_file, 'lldp/lldpd.j2', lldp)
+ render(vyos_config_file, 'lldp/vyos.conf.j2', lldp)
def apply(lldp):
systemd_service = 'lldpd.service'
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index 9f319fc8a..85819a77e 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -23,6 +23,7 @@ from platform import release as kernel_version
from sys import exit
from netifaces import interfaces
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.template import render
@@ -142,14 +143,14 @@ def verify(nat):
raise ConfigError(f'{err_msg} outbound-interface not specified')
if config['outbound_interface'] not in 'any' and config['outbound_interface'] not in interfaces():
- print(f'WARNING: rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')
+ Warning(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')
addr = dict_search('translation.address', config)
if addr != None:
if addr != 'masquerade' and not is_ip_network(addr):
for ip in addr.split('-'):
if not is_addr_assigned(ip):
- print(f'WARNING: IP address {ip} does not exist on the system!')
+ Warning(f'IP address {ip} does not exist on the system!')
elif 'exclude' not in config:
raise ConfigError(f'{err_msg}\n' \
'translation address not specified')
@@ -167,7 +168,7 @@ def verify(nat):
'inbound-interface not specified')
else:
if config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces():
- print(f'WARNING: rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')
+ Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')
if dict_search('translation.address', config) == None and 'exclude' not in config:
@@ -180,13 +181,13 @@ def verify(nat):
return None
def generate(nat):
- render(nftables_nat_config, 'firewall/nftables-nat.tmpl', nat)
+ render(nftables_nat_config, 'firewall/nftables-nat.j2', nat)
# dry-run newly generated configuration
tmp = run(f'nft -c -f {nftables_nat_config}')
if tmp > 0:
- if os.path.exists(nftables_ct_file):
- os.unlink(nftables_ct_file)
+ if os.path.exists(nftables_nat_config):
+ os.unlink(nftables_nat_config)
raise ConfigError('Configuration file errors encountered!')
return None
diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py
index 8bf2e8073..0972151a0 100755
--- a/src/conf_mode/nat66.py
+++ b/src/conf_mode/nat66.py
@@ -21,6 +21,7 @@ import os
from sys import exit
from netifaces import interfaces
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.template import render
@@ -117,12 +118,12 @@ def verify(nat):
raise ConfigError(f'{err_msg} outbound-interface not specified')
if config['outbound_interface'] not in interfaces():
- raise ConfigError(f'WARNING: rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')
+ raise ConfigError(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')
addr = dict_search('translation.address', config)
if addr != None:
if addr != 'masquerade' and not is_ipv6(addr):
- raise ConfigError(f'Warning: IPv6 address {addr} is not a valid address')
+ raise ConfigError(f'IPv6 address {addr} is not a valid address')
else:
raise ConfigError(f'{err_msg} translation address not specified')
@@ -140,13 +141,13 @@ def verify(nat):
'inbound-interface not specified')
else:
if config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces():
- print(f'WARNING: rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')
+ Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')
return None
def generate(nat):
- render(nftables_nat66_config, 'firewall/nftables-nat66.tmpl', nat, permission=0o755)
- render(ndppd_config, 'ndppd/ndppd.conf.tmpl', nat, permission=0o755)
+ render(nftables_nat66_config, 'firewall/nftables-nat66.j2', nat, permission=0o755)
+ render(ndppd_config, 'ndppd/ndppd.conf.j2', nat, permission=0o755)
return None
def apply(nat):
diff --git a/src/conf_mode/ntp.py b/src/conf_mode/ntp.py
index 52070aabc..0d6ec9ace 100755
--- a/src/conf_mode/ntp.py
+++ b/src/conf_mode/ntp.py
@@ -56,8 +56,8 @@ def generate(ntp):
if not ntp:
return None
- render(config_file, 'ntp/ntpd.conf.tmpl', ntp)
- render(systemd_override, 'ntp/override.conf.tmpl', ntp)
+ render(config_file, 'ntp/ntpd.conf.j2', ntp)
+ render(systemd_override, 'ntp/override.conf.j2', ntp)
return None
diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py
index 3d1d7d8c5..5de341beb 100755
--- a/src/conf_mode/policy-route.py
+++ b/src/conf_mode/policy-route.py
@@ -20,6 +20,7 @@ import re
from json import loads
from sys import exit
+from vyos.base import Warning
from vyos.config import Config
from vyos.template import render
from vyos.util import cmd
@@ -135,7 +136,7 @@ def verify_rule(policy, name, rule_conf, ipv6):
raise ConfigError(f'Invalid {error_group} "{group_name}" on policy route rule')
if not group_obj:
- print(f'WARNING: {error_group} "{group_name}" has no members')
+ Warning(f'{error_group} "{group_name}" has no members')
if 'port' in side_conf or dict_search_args(side_conf, 'group', 'port_group'):
if 'protocol' not in rule_conf:
@@ -203,7 +204,7 @@ def generate(policy):
else:
policy['cleanup_commands'] = cleanup_commands(policy)
- render(nftables_conf, 'firewall/nftables-policy.tmpl', policy)
+ render(nftables_conf, 'firewall/nftables-policy.j2', policy)
return None
def apply_table_marks(policy):
diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py
index 9d8fcfa36..ef6008140 100755
--- a/src/conf_mode/policy.py
+++ b/src/conf_mode/policy.py
@@ -177,7 +177,7 @@ def verify(policy):
def generate(policy):
if not policy:
return None
- policy['new_frr_config'] = render_to_string('frr/policy.frr.tmpl', policy)
+ policy['new_frr_config'] = render_to_string('frr/policy.frr.j2', policy)
return None
def apply(policy):
diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py
index 4ebc0989c..0436abaf9 100755
--- a/src/conf_mode/protocols_bfd.py
+++ b/src/conf_mode/protocols_bfd.py
@@ -98,7 +98,7 @@ def verify(bfd):
def generate(bfd):
if not bfd:
return None
- bfd['new_frr_config'] = render_to_string('frr/bfdd.frr.tmpl', bfd)
+ bfd['new_frr_config'] = render_to_string('frr/bfdd.frr.j2', bfd)
def apply(bfd):
bfd_daemon = 'bfdd'
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index dace53d37..cd46cbcb4 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -138,13 +138,20 @@ def verify(bgp):
if asn == bgp['local_as']:
raise ConfigError('Cannot have local-as same as BGP AS number')
+ # Neighbor AS specified for local-as and remote-as can not be the same
+ if dict_search('remote_as', peer_config) == asn:
+ raise ConfigError(f'Neighbor "{peer}" has local-as specified which is '\
+ 'the same as remote-as, this is not allowed!')
+
# ttl-security and ebgp-multihop can't be used in the same configration
if 'ebgp_multihop' in peer_config and 'ttl_security' in peer_config:
raise ConfigError('You can not set both ebgp-multihop and ttl-security hops')
- # Check if neighbor has both override capability and strict capability match configured at the same time.
+ # Check if neighbor has both override capability and strict capability match
+ # configured at the same time.
if 'override_capability' in peer_config and 'strict_capability_match' in peer_config:
- raise ConfigError(f'Neighbor "{peer}" cannot have both override-capability and strict-capability-match configured at the same time!')
+ raise ConfigError(f'Neighbor "{peer}" cannot have both override-capability and '\
+ 'strict-capability-match configured at the same time!')
# Check spaces in the password
if 'password' in peer_config and ' ' in peer_config['password']:
@@ -157,6 +164,22 @@ def verify(bgp):
if not verify_remote_as(peer_config, bgp):
raise ConfigError(f'Neighbor "{peer}" remote-as must be set!')
+ # Peer-group member cannot override remote-as of peer-group
+ if 'peer_group' in peer_config:
+ peer_group = peer_config['peer_group']
+ if 'remote_as' in peer_config and 'remote_as' in bgp['peer_group'][peer_group]:
+ raise ConfigError(f'Peer-group member "{peer}" cannot override remote-as of peer-group "{peer_group}"!')
+ if 'interface' in peer_config:
+ if 'peer_group' in peer_config['interface']:
+ peer_group = peer_config['interface']['peer_group']
+ if 'remote_as' in peer_config['interface'] and 'remote_as' in bgp['peer_group'][peer_group]:
+ raise ConfigError(f'Peer-group member "{peer}" cannot override remote-as of peer-group "{peer_group}"!')
+ if 'v6only' in peer_config['interface']:
+ if 'peer_group' in peer_config['interface']['v6only']:
+ peer_group = peer_config['interface']['v6only']['peer_group']
+ if 'remote_as' in peer_config['interface']['v6only'] and 'remote_as' in bgp['peer_group'][peer_group]:
+ raise ConfigError(f'Peer-group member "{peer}" cannot override remote-as of peer-group "{peer_group}"!')
+
# Only checks for ipv4 and ipv6 neighbors
# Check if neighbor address is assigned as system interface address
vrf = None
@@ -297,9 +320,9 @@ def generate(bgp):
if not bgp or 'deleted' in bgp:
return None
- bgp['protocol'] = 'bgp' # required for frr/vrf.route-map.frr.tmpl
- bgp['frr_zebra_config'] = render_to_string('frr/vrf.route-map.frr.tmpl', bgp)
- bgp['frr_bgpd_config'] = render_to_string('frr/bgpd.frr.tmpl', bgp)
+ bgp['protocol'] = 'bgp' # required for frr/vrf.route-map.frr.j2
+ bgp['frr_zebra_config'] = render_to_string('frr/vrf.route-map.frr.j2', bgp)
+ bgp['frr_bgpd_config'] = render_to_string('frr/bgpd.frr.j2', bgp)
return None
diff --git a/src/conf_mode/protocols_igmp.py b/src/conf_mode/protocols_igmp.py
index 28d560d03..65cc2beba 100755
--- a/src/conf_mode/protocols_igmp.py
+++ b/src/conf_mode/protocols_igmp.py
@@ -108,7 +108,7 @@ def generate(igmp):
if igmp is None:
return None
- render(config_file, 'frr/igmp.frr.tmpl', igmp)
+ render(config_file, 'frr/igmp.frr.j2', igmp)
return None
def apply(igmp):
diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py
index f2501e38a..5dafd26d0 100755
--- a/src/conf_mode/protocols_isis.py
+++ b/src/conf_mode/protocols_isis.py
@@ -210,9 +210,9 @@ def generate(isis):
if not isis or 'deleted' in isis:
return None
- isis['protocol'] = 'isis' # required for frr/vrf.route-map.frr.tmpl
- isis['frr_zebra_config'] = render_to_string('frr/vrf.route-map.frr.tmpl', isis)
- isis['frr_isisd_config'] = render_to_string('frr/isisd.frr.tmpl', isis)
+ isis['protocol'] = 'isis' # required for frr/vrf.route-map.frr.j2
+ isis['frr_zebra_config'] = render_to_string('frr/vrf.route-map.frr.j2', isis)
+ isis['frr_isisd_config'] = render_to_string('frr/isisd.frr.j2', isis)
return None
def apply(isis):
diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py
index 933e23065..5da8e7b06 100755
--- a/src/conf_mode/protocols_mpls.py
+++ b/src/conf_mode/protocols_mpls.py
@@ -68,7 +68,7 @@ def generate(mpls):
if not mpls or 'deleted' in mpls:
return None
- mpls['frr_ldpd_config'] = render_to_string('frr/ldpd.frr.tmpl', mpls)
+ mpls['frr_ldpd_config'] = render_to_string('frr/ldpd.frr.j2', mpls)
return None
def apply(mpls):
diff --git a/src/conf_mode/protocols_nhrp.py b/src/conf_mode/protocols_nhrp.py
index 7eeb5cd30..92b335085 100755
--- a/src/conf_mode/protocols_nhrp.py
+++ b/src/conf_mode/protocols_nhrp.py
@@ -84,7 +84,7 @@ def verify(nhrp):
return None
def generate(nhrp):
- render(opennhrp_conf, 'nhrp/opennhrp.conf.tmpl', nhrp)
+ render(opennhrp_conf, 'nhrp/opennhrp.conf.j2', nhrp)
return None
def apply(nhrp):
@@ -104,7 +104,7 @@ def apply(nhrp):
if rule_handle:
remove_nftables_rule('ip filter', 'VYOS_FW_OUTPUT', rule_handle)
- action = 'restart' if nhrp and 'tunnel' in nhrp else 'stop'
+ action = 'reload-or-restart' if nhrp and 'tunnel' in nhrp else 'stop'
run(f'systemctl {action} opennhrp')
return None
diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py
index 26d491838..5b4874ba2 100755
--- a/src/conf_mode/protocols_ospf.py
+++ b/src/conf_mode/protocols_ospf.py
@@ -160,7 +160,7 @@ def verify(ospf):
route_map_name = dict_search('default_information.originate.route_map', ospf)
if route_map_name: verify_route_map(route_map_name, ospf)
- # Validate if configured Access-list exists
+ # Validate if configured Access-list exists
if 'area' in ospf:
for area, area_config in ospf['area'].items():
if 'import_list' in area_config:
@@ -204,9 +204,9 @@ def generate(ospf):
if not ospf or 'deleted' in ospf:
return None
- ospf['protocol'] = 'ospf' # required for frr/vrf.route-map.frr.tmpl
- ospf['frr_zebra_config'] = render_to_string('frr/vrf.route-map.frr.tmpl', ospf)
- ospf['frr_ospfd_config'] = render_to_string('frr/ospfd.frr.tmpl', ospf)
+ ospf['protocol'] = 'ospf' # required for frr/vrf.route-map.frr.j2
+ ospf['frr_zebra_config'] = render_to_string('frr/vrf.route-map.frr.j2', ospf)
+ ospf['frr_ospfd_config'] = render_to_string('frr/ospfd.frr.j2', ospf)
return None
def apply(ospf):
diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py
index f8e733ba5..ee4eaf59d 100755
--- a/src/conf_mode/protocols_ospfv3.py
+++ b/src/conf_mode/protocols_ospfv3.py
@@ -142,7 +142,7 @@ def generate(ospfv3):
if not ospfv3 or 'deleted' in ospfv3:
return None
- ospfv3['new_frr_config'] = render_to_string('frr/ospf6d.frr.tmpl', ospfv3)
+ ospfv3['new_frr_config'] = render_to_string('frr/ospf6d.frr.j2', ospfv3)
return None
def apply(ospfv3):
diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py
index df2e6f941..78df9b6f8 100755
--- a/src/conf_mode/protocols_pim.py
+++ b/src/conf_mode/protocols_pim.py
@@ -135,7 +135,7 @@ def generate(pim):
if pim is None:
return None
- render(config_file, 'frr/pimd.frr.tmpl', pim)
+ render(config_file, 'frr/pimd.frr.j2', pim)
return None
def apply(pim):
diff --git a/src/conf_mode/protocols_rip.py b/src/conf_mode/protocols_rip.py
index 300f56489..a76c1ce76 100755
--- a/src/conf_mode/protocols_rip.py
+++ b/src/conf_mode/protocols_rip.py
@@ -102,7 +102,7 @@ def generate(rip):
if not rip or 'deleted' in rip:
return None
- rip['new_frr_config'] = render_to_string('frr/ripd.frr.tmpl', rip)
+ rip['new_frr_config'] = render_to_string('frr/ripd.frr.j2', rip)
return None
def apply(rip):
diff --git a/src/conf_mode/protocols_ripng.py b/src/conf_mode/protocols_ripng.py
index d9b8c0b30..21ff710b3 100755
--- a/src/conf_mode/protocols_ripng.py
+++ b/src/conf_mode/protocols_ripng.py
@@ -93,7 +93,7 @@ def generate(ripng):
ripng['new_frr_config'] = ''
return None
- ripng['new_frr_config'] = render_to_string('frr/ripngd.frr.tmpl', ripng)
+ ripng['new_frr_config'] = render_to_string('frr/ripngd.frr.j2', ripng)
return None
def apply(ripng):
diff --git a/src/conf_mode/protocols_rpki.py b/src/conf_mode/protocols_rpki.py
index 51ad0d315..62ea9c878 100755
--- a/src/conf_mode/protocols_rpki.py
+++ b/src/conf_mode/protocols_rpki.py
@@ -81,7 +81,7 @@ def verify(rpki):
def generate(rpki):
if not rpki:
return
- rpki['new_frr_config'] = render_to_string('frr/rpki.frr.tmpl', rpki)
+ rpki['new_frr_config'] = render_to_string('frr/rpki.frr.j2', rpki)
return None
def apply(rpki):
diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py
index f0ec48de4..58e202928 100755
--- a/src/conf_mode/protocols_static.py
+++ b/src/conf_mode/protocols_static.py
@@ -22,6 +22,7 @@ from sys import argv
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configdict import get_dhcp_interfaces
+from vyos.configdict import get_pppoe_interfaces
from vyos.configverify import verify_common_route_maps
from vyos.configverify import verify_vrf
from vyos.template import render_to_string
@@ -59,7 +60,9 @@ def get_config(config=None):
# T3680 - get a list of all interfaces currently configured to use DHCP
tmp = get_dhcp_interfaces(conf, vrf)
- if tmp: static['dhcp'] = tmp
+ if tmp: static.update({'dhcp' : tmp})
+ tmp = get_pppoe_interfaces(conf, vrf)
+ if tmp: static.update({'pppoe' : tmp})
return static
@@ -91,7 +94,7 @@ def verify(static):
def generate(static):
if not static:
return None
- static['new_frr_config'] = render_to_string('frr/staticd.frr.tmpl', static)
+ static['new_frr_config'] = render_to_string('frr/staticd.frr.j2', static)
return None
def apply(static):
diff --git a/src/conf_mode/protocols_static_multicast.py b/src/conf_mode/protocols_static_multicast.py
index 99157835a..6afdf31f3 100755
--- a/src/conf_mode/protocols_static_multicast.py
+++ b/src/conf_mode/protocols_static_multicast.py
@@ -96,7 +96,7 @@ def generate(mroute):
if mroute is None:
return None
- render(config_file, 'frr/static_mcast.frr.tmpl', mroute)
+ render(config_file, 'frr/static_mcast.frr.j2', mroute)
return None
def apply(mroute):
diff --git a/src/conf_mode/salt-minion.py b/src/conf_mode/salt-minion.py
index 841bf6a39..00b889a11 100755
--- a/src/conf_mode/salt-minion.py
+++ b/src/conf_mode/salt-minion.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2020 VyOS maintainers and contributors
+# Copyright (C) 2018-2022 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
@@ -16,14 +16,18 @@
import os
-from copy import deepcopy
from socket import gethostname
from sys import exit
from urllib3 import PoolManager
+from vyos.base import Warning
from vyos.config import Config
+from vyos.configdict import dict_merge
+from vyos.configverify import verify_interface_exists
from vyos.template import render
-from vyos.util import call, chown
+from vyos.util import call
+from vyos.util import chown
+from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
@@ -32,20 +36,10 @@ airbag.enable()
config_file = r'/etc/salt/minion'
master_keyfile = r'/opt/vyatta/etc/config/salt/pki/minion/master_sign.pub'
-default_config_data = {
- 'hash': 'sha256',
- 'log_level': 'warning',
- 'master' : 'salt',
- 'user': 'minion',
- 'group': 'vyattacfg',
- 'salt_id': gethostname(),
- 'mine_interval': '60',
- 'verify_master_pubkey_sign': 'false',
- 'master_key': ''
-}
+user='minion'
+group='vyattacfg'
def get_config(config=None):
- salt = deepcopy(default_config_data)
if config:
conf = config
else:
@@ -54,44 +48,44 @@ def get_config(config=None):
if not conf.exists(base):
return None
- else:
- conf.set_level(base)
- if conf.exists(['hash']):
- salt['hash'] = conf.return_value(['hash'])
+ salt = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ # ID default is dynamic thus we can not use defaults()
+ if 'id' not in salt:
+ salt['id'] = gethostname()
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ default_values = defaults(base)
+ salt = dict_merge(default_values, salt)
- if conf.exists(['master']):
- salt['master'] = conf.return_values(['master'])
-
- if conf.exists(['id']):
- salt['salt_id'] = conf.return_value(['id'])
+ if not conf.exists(base):
+ return None
+ else:
+ conf.set_level(base)
- if conf.exists(['user']):
- salt['user'] = conf.return_value(['user'])
+ return salt
- if conf.exists(['interval']):
- salt['interval'] = conf.return_value(['interval'])
+def verify(salt):
+ if not salt:
+ return None
- if conf.exists(['master-key']):
- salt['master_key'] = conf.return_value(['master-key'])
- salt['verify_master_pubkey_sign'] = 'true'
+ if 'hash' in salt and salt['hash'] == 'sha1':
+ Warning('Do not use sha1 hashing algorithm, upgrade to sha256 or later!')
- return salt
+ if 'source_interface' in salt:
+ verify_interface_exists(salt['source_interface'])
-def verify(salt):
return None
def generate(salt):
if not salt:
return None
- render(config_file, 'salt-minion/minion.tmpl', salt,
- user=salt['user'], group=salt['group'])
+ render(config_file, 'salt-minion/minion.j2', salt, user=user, group=group)
if not os.path.exists(master_keyfile):
- if salt['master_key']:
+ if 'master_key' in salt:
req = PoolManager().request('GET', salt['master_key'], preload_content=False)
-
with open(master_keyfile, 'wb') as f:
while True:
data = req.read(1024)
@@ -100,18 +94,19 @@ def generate(salt):
f.write(data)
req.release_conn()
- chown(master_keyfile, salt['user'], salt['group'])
+ chown(master_keyfile, user, group)
return None
def apply(salt):
+ service_name = 'salt-minion.service'
if not salt:
# Salt removed from running config
- call('systemctl stop salt-minion.service')
+ call(f'systemctl stop {service_name}')
if os.path.exists(config_file):
os.unlink(config_file)
else:
- call('systemctl restart salt-minion.service')
+ call(f'systemctl restart {service_name}')
return None
diff --git a/src/conf_mode/service_console-server.py b/src/conf_mode/service_console-server.py
index 51050e702..a2e411e49 100755
--- a/src/conf_mode/service_console-server.py
+++ b/src/conf_mode/service_console-server.py
@@ -81,7 +81,7 @@ def generate(proxy):
if not proxy:
return None
- render(config_file, 'conserver/conserver.conf.tmpl', proxy)
+ render(config_file, 'conserver/conserver.conf.j2', proxy)
if 'device' in proxy:
for device, device_config in proxy['device'].items():
if 'ssh' not in device_config:
@@ -92,7 +92,7 @@ def generate(proxy):
'port' : device_config['ssh']['port'],
}
render(dropbear_systemd_file.format(**tmp),
- 'conserver/dropbear@.service.tmpl', tmp)
+ 'conserver/dropbear@.service.j2', tmp)
return None
diff --git a/src/conf_mode/service_ids_fastnetmon.py b/src/conf_mode/service_ids_fastnetmon.py
index 67edeb630..ae7e582ec 100755
--- a/src/conf_mode/service_ids_fastnetmon.py
+++ b/src/conf_mode/service_ids_fastnetmon.py
@@ -67,8 +67,8 @@ def generate(fastnetmon):
return
- render(config_file, 'ids/fastnetmon.tmpl', fastnetmon)
- render(networks_list, 'ids/fastnetmon_networks_list.tmpl', fastnetmon)
+ render(config_file, 'ids/fastnetmon.j2', fastnetmon)
+ render(networks_list, 'ids/fastnetmon_networks_list.j2', fastnetmon)
return None
diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py
index 2ebee8018..559d1bcd5 100755
--- a/src/conf_mode/service_ipoe-server.py
+++ b/src/conf_mode/service_ipoe-server.py
@@ -296,10 +296,10 @@ def generate(ipoe):
if not ipoe:
return None
- render(ipoe_conf, 'accel-ppp/ipoe.config.tmpl', ipoe)
+ render(ipoe_conf, 'accel-ppp/ipoe.config.j2', ipoe)
if ipoe['auth_mode'] == 'local':
- render(ipoe_chap_secrets, 'accel-ppp/chap-secrets.ipoe.tmpl', ipoe)
+ render(ipoe_chap_secrets, 'accel-ppp/chap-secrets.ipoe.j2', ipoe)
os.chmod(ipoe_chap_secrets, S_IRUSR | S_IWUSR | S_IRGRP)
else:
diff --git a/src/conf_mode/service_mdns-repeater.py b/src/conf_mode/service_mdns-repeater.py
index d31a0c49e..2383a53fb 100755
--- a/src/conf_mode/service_mdns-repeater.py
+++ b/src/conf_mode/service_mdns-repeater.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2017-2020 VyOS maintainers and contributors
+# Copyright (C) 2017-2022 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
@@ -92,7 +92,7 @@ def generate(mdns):
if len(mdns['interface']) < 2:
return None
- render(config_file, 'mdns-repeater/avahi-daemon.tmpl', mdns)
+ render(config_file, 'mdns-repeater/avahi-daemon.j2', mdns)
return None
def apply(mdns):
diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py
index 8a972b9fe..102a87318 100755
--- a/src/conf_mode/service_monitoring_telegraf.py
+++ b/src/conf_mode/service_monitoring_telegraf.py
@@ -99,6 +99,15 @@ def get_config(config=None):
monitoring['interfaces_ethernet'] = get_interfaces('ethernet', vlan=False)
monitoring['nft_chains'] = get_nft_filter_chains()
+ if 'authentication' in monitoring or \
+ 'url' in monitoring:
+ monitoring['influxdb_configured'] = True
+
+ # Ignore default XML values if config doesn't exists
+ # Delete key from dict
+ if not conf.exists(base + ['prometheus-client']):
+ del monitoring['prometheus_client']
+
return monitoring
def verify(monitoring):
@@ -106,13 +115,23 @@ def verify(monitoring):
if not monitoring:
return None
- if 'authentication' not in monitoring or \
- 'organization' not in monitoring['authentication'] or \
- 'token' not in monitoring['authentication']:
- raise ConfigError(f'Authentication "organization and token" are mandatory!')
+ if 'influxdb_configured' in monitoring:
+ if 'authentication' not in monitoring or \
+ 'organization' not in monitoring['authentication'] or \
+ 'token' not in monitoring['authentication']:
+ raise ConfigError(f'Authentication "organization and token" are mandatory!')
+
+ if 'url' not in monitoring:
+ raise ConfigError(f'Monitoring "url" is mandatory!')
+
+ # Verify Splunk
+ if 'splunk' in monitoring:
+ if 'authentication' not in monitoring['splunk'] or \
+ 'token' not in monitoring['splunk']['authentication']:
+ raise ConfigError(f'Authentication "organization and token" are mandatory!')
- if 'url' not in monitoring:
- raise ConfigError(f'Monitoring "url" is mandatory!')
+ if 'url' not in monitoring['splunk']:
+ raise ConfigError(f'Monitoring splunk "url" is mandatory!')
return None
@@ -145,10 +164,10 @@ def generate(monitoring):
os.mkdir(custom_scripts_dir)
# Render telegraf configuration and systemd override
- render(config_telegraf, 'monitoring/telegraf.tmpl', monitoring)
- render(systemd_telegraf_service, 'monitoring/systemd_vyos_telegraf_service.tmpl', monitoring)
- render(systemd_override, 'monitoring/override.conf.tmpl', monitoring, permission=0o640)
- render(syslog_telegraf, 'monitoring/syslog_telegraf.tmpl', monitoring)
+ render(config_telegraf, 'monitoring/telegraf.j2', monitoring)
+ render(systemd_telegraf_service, 'monitoring/systemd_vyos_telegraf_service.j2', monitoring)
+ render(systemd_override, 'monitoring/override.conf.j2', monitoring, permission=0o640)
+ render(syslog_telegraf, 'monitoring/syslog_telegraf.j2', monitoring)
chown(base_dir, 'telegraf', 'telegraf')
diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py
index 1f31d132d..6086ef859 100755
--- a/src/conf_mode/service_pppoe-server.py
+++ b/src/conf_mode/service_pppoe-server.py
@@ -88,10 +88,10 @@ def generate(pppoe):
for vlan_range in pppoe['interface'][iface]['vlan_range']:
pppoe['interface'][iface]['regex'].append(range_to_regex(vlan_range))
- render(pppoe_conf, 'accel-ppp/pppoe.config.tmpl', pppoe)
+ render(pppoe_conf, 'accel-ppp/pppoe.config.j2', pppoe)
if dict_search('authentication.mode', pppoe) == 'local':
- render(pppoe_chap_secrets, 'accel-ppp/chap-secrets.config_dict.tmpl',
+ render(pppoe_chap_secrets, 'accel-ppp/chap-secrets.config_dict.j2',
pppoe, permission=0o640)
else:
if os.path.exists(pppoe_chap_secrets):
diff --git a/src/conf_mode/service_router-advert.py b/src/conf_mode/service_router-advert.py
index 9afcdd63e..71b758399 100755
--- a/src/conf_mode/service_router-advert.py
+++ b/src/conf_mode/service_router-advert.py
@@ -101,7 +101,7 @@ def generate(rtradv):
if not rtradv:
return None
- render(config_file, 'router-advert/radvd.conf.tmpl', rtradv, permission=0o644)
+ render(config_file, 'router-advert/radvd.conf.j2', rtradv, permission=0o644)
return None
def apply(rtradv):
diff --git a/src/conf_mode/service_upnp.py b/src/conf_mode/service_upnp.py
index d21b31990..36f3e18a7 100755
--- a/src/conf_mode/service_upnp.py
+++ b/src/conf_mode/service_upnp.py
@@ -135,7 +135,7 @@ def generate(upnpd):
if os.path.isfile(config_file):
os.unlink(config_file)
- render(config_file, 'firewall/upnpd.conf.tmpl', upnpd)
+ render(config_file, 'firewall/upnpd.conf.j2', upnpd)
def apply(upnpd):
systemd_service_name = 'miniupnpd.service'
diff --git a/src/conf_mode/service_webproxy.py b/src/conf_mode/service_webproxy.py
index a16cc4aeb..32af31bde 100755
--- a/src/conf_mode/service_webproxy.py
+++ b/src/conf_mode/service_webproxy.py
@@ -61,7 +61,7 @@ def generate_sg_localdb(category, list_type, role, proxy):
user=user_group, group=user_group)
# temporary config file, deleted after generation
- render(sg_tmp_file, 'squid/sg_acl.conf.tmpl', tmp,
+ render(sg_tmp_file, 'squid/sg_acl.conf.j2', tmp,
user=user_group, group=user_group)
call(f'su - {user_group} -c "squidGuard -d -c {sg_tmp_file} -C {db_file}"')
@@ -166,8 +166,8 @@ def generate(proxy):
if not proxy:
return None
- render(squid_config_file, 'squid/squid.conf.tmpl', proxy)
- render(squidguard_config_file, 'squid/squidGuard.conf.tmpl', proxy)
+ render(squid_config_file, 'squid/squid.conf.j2', proxy)
+ render(squidguard_config_file, 'squid/squidGuard.conf.j2', proxy)
cat_dict = {
'local-block' : 'domains',
diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py
index 8ce48780b..ae060580d 100755
--- a/src/conf_mode/snmp.py
+++ b/src/conf_mode/snmp.py
@@ -18,6 +18,7 @@ import os
from sys import exit
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configverify import verify_vrf
@@ -57,9 +58,6 @@ def get_config(config=None):
if conf.exists(['service', 'lldp', 'snmp', 'enable']):
snmp.update({'lldp_snmp' : ''})
- if conf.exists(['system', 'ipv6', 'disable']):
- snmp.update({'ipv6_disabled' : ''})
-
if 'deleted' in snmp:
return snmp
@@ -100,9 +98,8 @@ def get_config(config=None):
snmp['listen_address'] = dict_merge(tmp, snmp['listen_address'])
if '::1' not in snmp['listen_address']:
- if 'ipv6_disabled' not in snmp:
- tmp = {'::1': {'port': '161'}}
- snmp['listen_address'] = dict_merge(tmp, snmp['listen_address'])
+ tmp = {'::1': {'port': '161'}}
+ snmp['listen_address'] = dict_merge(tmp, snmp['listen_address'])
if 'community' in snmp:
default_values = defaults(base + ['community'])
@@ -153,7 +150,7 @@ def verify(snmp):
tmp = extension_opt['script']
if not os.path.isfile(tmp):
- print(f'WARNING: script "{tmp}" does not exist!')
+ Warning(f'script "{tmp}" does not exist!')
else:
chmod_755(extension_opt['script'])
@@ -162,7 +159,7 @@ def verify(snmp):
# We only wan't to configure addresses that exist on the system.
# Hint the user if they don't exist
if not is_addr_assigned(address):
- print(f'WARNING: SNMP listen address "{address}" not configured!')
+ Warning(f'SNMP listen address "{address}" not configured!')
if 'trap_target' in snmp:
for trap, trap_config in snmp['trap_target'].items():
@@ -273,15 +270,15 @@ def generate(snmp):
call(f'/opt/vyatta/sbin/my_delete service snmp v3 user "{user}" privacy plaintext-password > /dev/null')
# Write client config file
- render(config_file_client, 'snmp/etc.snmp.conf.tmpl', snmp)
+ render(config_file_client, 'snmp/etc.snmp.conf.j2', snmp)
# Write server config file
- render(config_file_daemon, 'snmp/etc.snmpd.conf.tmpl', snmp)
+ render(config_file_daemon, 'snmp/etc.snmpd.conf.j2', snmp)
# Write access rights config file
- render(config_file_access, 'snmp/usr.snmpd.conf.tmpl', snmp)
+ render(config_file_access, 'snmp/usr.snmpd.conf.j2', snmp)
# Write access rights config file
- render(config_file_user, 'snmp/var.snmpd.conf.tmpl', snmp)
+ render(config_file_user, 'snmp/var.snmpd.conf.j2', snmp)
# Write daemon configuration file
- render(systemd_override, 'snmp/override.conf.tmpl', snmp)
+ render(systemd_override, 'snmp/override.conf.j2', snmp)
return None
diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py
index 67724b043..487e8c229 100755
--- a/src/conf_mode/ssh.py
+++ b/src/conf_mode/ssh.py
@@ -84,8 +84,8 @@ def generate(ssh):
syslog(LOG_INFO, 'SSH ed25519 host key not found, generating new key!')
call(f'ssh-keygen -q -N "" -t ed25519 -f {key_ed25519}')
- render(config_file, 'ssh/sshd_config.tmpl', ssh)
- render(systemd_override, 'ssh/override.conf.tmpl', ssh)
+ render(config_file, 'ssh/sshd_config.j2', ssh)
+ render(systemd_override, 'ssh/override.conf.j2', ssh)
# Reload systemd manager configuration
call('systemctl daemon-reload')
diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py
index c9c6aa187..c717286ae 100755
--- a/src/conf_mode/system-login.py
+++ b/src/conf_mode/system-login.py
@@ -197,7 +197,7 @@ def generate(login):
pass
if 'radius' in login:
- render(radius_config_file, 'login/pam_radius_auth.conf.tmpl', login,
+ render(radius_config_file, 'login/pam_radius_auth.conf.j2', login,
permission=0o600, user='root', group='root')
else:
if os.path.isfile(radius_config_file):
@@ -241,7 +241,7 @@ def apply(login):
#
# XXX: Should we deny using root at all?
home_dir = getpwnam(user).pw_dir
- render(f'{home_dir}/.ssh/authorized_keys', 'login/authorized_keys.tmpl',
+ render(f'{home_dir}/.ssh/authorized_keys', 'login/authorized_keys.j2',
user_config, permission=0o600,
formater=lambda _: _.replace("&quot;", '"'),
user=user, group='users')
diff --git a/src/conf_mode/system-logs.py b/src/conf_mode/system-logs.py
index e6296656d..c71938a79 100755
--- a/src/conf_mode/system-logs.py
+++ b/src/conf_mode/system-logs.py
@@ -57,13 +57,13 @@ def generate(logs_config):
logrotate_atop = dict_search('logrotate.atop', logs_config)
# generate new config file for atop
syslog.debug('Adding logrotate config for atop')
- render(logrotate_atop_file, 'logs/logrotate/vyos-atop.tmpl', logrotate_atop)
+ render(logrotate_atop_file, 'logs/logrotate/vyos-atop.j2', logrotate_atop)
# get configuration for logrotate rsyslog
logrotate_rsyslog = dict_search('logrotate.messages', logs_config)
# generate new config file for rsyslog
syslog.debug('Adding logrotate config for rsyslog')
- render(logrotate_rsyslog_file, 'logs/logrotate/vyos-rsyslog.tmpl',
+ render(logrotate_rsyslog_file, 'logs/logrotate/vyos-rsyslog.j2',
logrotate_rsyslog)
diff --git a/src/conf_mode/system-option.py b/src/conf_mode/system-option.py
index b1c63e316..36dbf155b 100755
--- a/src/conf_mode/system-option.py
+++ b/src/conf_mode/system-option.py
@@ -74,8 +74,8 @@ def verify(options):
return None
def generate(options):
- render(curlrc_config, 'system/curlrc.tmpl', options)
- render(ssh_config, 'system/ssh_config.tmpl', options)
+ render(curlrc_config, 'system/curlrc.j2', options)
+ render(ssh_config, 'system/ssh_config.j2', options)
return None
def apply(options):
diff --git a/src/conf_mode/system-proxy.py b/src/conf_mode/system-proxy.py
index 02536c2ab..079c43e7e 100755
--- a/src/conf_mode/system-proxy.py
+++ b/src/conf_mode/system-proxy.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018 VyOS maintainers and contributors
+# Copyright (C) 2018-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -13,83 +13,59 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-#
-import sys
import os
-import re
-from vyos import ConfigError
-from vyos.config import Config
+from sys import exit
+from vyos.config import Config
+from vyos.template import render
+from vyos import ConfigError
from vyos import airbag
airbag.enable()
proxy_def = r'/etc/profile.d/vyos-system-proxy.sh'
-
-def get_config():
- c = Config()
- if not c.exists('system proxy'):
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['system', 'proxy']
+ if not conf.exists(base):
return None
- c.set_level('system proxy')
+ proxy = conf.get_config_dict(base, get_first_key=True)
+ return proxy
- cnf = {
- 'url': None,
- 'port': None,
- 'usr': None,
- 'passwd': None
- }
+def verify(proxy):
+ if not proxy:
+ return
- if c.exists('url'):
- cnf['url'] = c.return_value('url')
- if c.exists('port'):
- cnf['port'] = c.return_value('port')
- if c.exists('username'):
- cnf['usr'] = c.return_value('username')
- if c.exists('password'):
- cnf['passwd'] = c.return_value('password')
+ if 'url' not in proxy or 'port' not in proxy:
+ raise ConfigError('Proxy URL and port require a value')
- return cnf
+ if ('username' in proxy and 'password' not in proxy) or \
+ ('username' not in proxy and 'password' in proxy):
+ raise ConfigError('Both username and password need to be defined!')
+def generate(proxy):
+ if not proxy:
+ if os.path.isfile(proxy_def):
+ os.unlink(proxy_def)
+ return
-def verify(c):
- if not c:
- return None
- if not c['url'] or not c['port']:
- raise ConfigError("proxy url and port requires a value")
- elif c['usr'] and not c['passwd']:
- raise ConfigError("proxy password requires a value")
- elif not c['usr'] and c['passwd']:
- raise ConfigError("proxy username requires a value")
-
+ render(proxy_def, 'system/proxy.j2', proxy, permission=0o755)
-def generate(c):
- if not c:
- return None
- if not c['usr']:
- return str("export http_proxy={url}:{port}\nexport https_proxy=$http_proxy\nexport ftp_proxy=$http_proxy"
- .format(url=c['url'], port=c['port']))
- else:
- return str("export http_proxy=http://{usr}:{passwd}@{url}:{port}\nexport https_proxy=$http_proxy\nexport ftp_proxy=$http_proxy"
- .format(url=re.sub('http://', '', c['url']), port=c['port'], usr=c['usr'], passwd=c['passwd']))
-
-
-def apply(ln):
- if not ln and os.path.exists(proxy_def):
- os.remove(proxy_def)
- else:
- open(proxy_def, 'w').write(
- "# generated by system-proxy.py\n{}\n".format(ln))
+def apply(proxy):
+ pass
if __name__ == '__main__':
try:
c = get_config()
verify(c)
- ln = generate(c)
- apply(ln)
+ generate(c)
+ apply(c)
except ConfigError as e:
print(e)
- sys.exit(1)
+ exit(1)
diff --git a/src/conf_mode/system-syslog.py b/src/conf_mode/system-syslog.py
index 309b4bdb0..a9d3bbe31 100755
--- a/src/conf_mode/system-syslog.py
+++ b/src/conf_mode/system-syslog.py
@@ -204,7 +204,7 @@ def generate(c):
return None
conf = '/etc/rsyslog.d/vyos-rsyslog.conf'
- render(conf, 'syslog/rsyslog.conf.tmpl', c)
+ render(conf, 'syslog/rsyslog.conf.j2', c)
# cleanup current logrotate config files
logrotate_files = Path('/etc/logrotate.d/').glob('vyos-rsyslog-generated-*')
@@ -216,7 +216,7 @@ def generate(c):
for filename, fileconfig in c.get('files', {}).items():
if fileconfig['log-file'].startswith('/var/log/user/'):
conf = '/etc/logrotate.d/vyos-rsyslog-generated-' + filename
- render(conf, 'syslog/logrotate.tmpl', { 'config_render': fileconfig })
+ render(conf, 'syslog/logrotate.j2', { 'config_render': fileconfig })
def verify(c):
diff --git a/src/conf_mode/system_console.py b/src/conf_mode/system_console.py
index 19b252513..86985d765 100755
--- a/src/conf_mode/system_console.py
+++ b/src/conf_mode/system_console.py
@@ -103,7 +103,7 @@ def generate(console):
config_file = base_dir + f'/serial-getty@{device}.service'
getty_wants_symlink = base_dir + f'/getty.target.wants/serial-getty@{device}.service'
- render(config_file, 'getty/serial-getty.service.tmpl', device_config)
+ render(config_file, 'getty/serial-getty.service.j2', device_config)
os.symlink(config_file, getty_wants_symlink)
# GRUB
diff --git a/src/conf_mode/system_lcd.py b/src/conf_mode/system_lcd.py
index b5ce32beb..3341dd738 100755
--- a/src/conf_mode/system_lcd.py
+++ b/src/conf_mode/system_lcd.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright 2020 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2020-2022 VyOS maintainers and contributors <maintainers@vyos.io>
#
# 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
@@ -61,9 +61,9 @@ def generate(lcd):
lcd['device'] = find_device_file(lcd['device'])
# Render config file for daemon LCDd
- render(lcdd_conf, 'lcd/LCDd.conf.tmpl', lcd)
+ render(lcdd_conf, 'lcd/LCDd.conf.j2', lcd)
# Render config file for client lcdproc
- render(lcdproc_conf, 'lcd/lcdproc.conf.tmpl', lcd)
+ render(lcdproc_conf, 'lcd/lcdproc.conf.j2', lcd)
return None
diff --git a/src/conf_mode/system_sysctl.py b/src/conf_mode/system_sysctl.py
index 4f16d1ed6..2e0004ffa 100755
--- a/src/conf_mode/system_sysctl.py
+++ b/src/conf_mode/system_sysctl.py
@@ -50,7 +50,7 @@ def generate(sysctl):
os.unlink(config_file)
return None
- render(config_file, 'system/sysctl.conf.tmpl', sysctl)
+ render(config_file, 'system/sysctl.conf.j2', sysctl)
return None
def apply(sysctl):
diff --git a/src/conf_mode/tftp_server.py b/src/conf_mode/tftp_server.py
index ef726670c..c5daccb7f 100755
--- a/src/conf_mode/tftp_server.py
+++ b/src/conf_mode/tftp_server.py
@@ -22,6 +22,7 @@ from copy import deepcopy
from glob import glob
from sys import exit
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configverify import verify_vrf
@@ -68,8 +69,8 @@ def verify(tftpd):
for address, address_config in tftpd['listen_address'].items():
if not is_addr_assigned(address):
- print(f'WARNING: TFTP server listen address "{address}" not ' \
- 'assigned to any interface!')
+ Warning(f'TFTP server listen address "{address}" not ' \
+ 'assigned to any interface!')
verify_vrf(address_config)
return None
@@ -97,7 +98,7 @@ def generate(tftpd):
config['vrf'] = address_config['vrf']
file = config_file + str(idx)
- render(file, 'tftp-server/default.tmpl', config)
+ render(file, 'tftp-server/default.j2', config)
idx = idx + 1
return None
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index 99b82ca2d..bad9cfbd8 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -503,7 +503,7 @@ def generate(ipsec):
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})
+ render(charon_conf, 'ipsec/charon.j2', {'install_routes': default_install_routes})
return
if ipsec['dhcp_no_address']:
@@ -553,25 +553,27 @@ def generate(ipsec):
if not local_prefixes or not remote_prefixes:
continue
- passthrough = []
+ passthrough = None
for local_prefix in local_prefixes:
for remote_prefix in remote_prefixes:
local_net = ipaddress.ip_network(local_prefix)
remote_net = ipaddress.ip_network(remote_prefix)
if local_net.overlaps(remote_net):
+ if passthrough is None:
+ passthrough = []
passthrough.append(local_prefix)
ipsec['site_to_site']['peer'][peer]['tunnel'][tunnel]['passthrough'] = passthrough
- render(ipsec_conf, 'ipsec/ipsec.conf.tmpl', 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)
+ render(ipsec_conf, 'ipsec/ipsec.conf.j2', ipsec)
+ render(ipsec_secrets, 'ipsec/ipsec.secrets.j2', ipsec)
+ render(charon_conf, 'ipsec/charon.j2', ipsec)
+ render(charon_dhcp_conf, 'ipsec/charon/dhcp.conf.j2', ipsec)
+ render(charon_radius_conf, 'ipsec/charon/eap-radius.conf.j2', ipsec)
+ render(interface_conf, 'ipsec/interfaces_use.conf.j2', ipsec)
+ render(swanctl_conf, 'ipsec/swanctl.conf.j2', ipsec)
def resync_nhrp(ipsec):
if ipsec and not ipsec['nhrp_exists']:
diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py
index 818e8fa0b..fd5a4acd8 100755
--- a/src/conf_mode/vpn_l2tp.py
+++ b/src/conf_mode/vpn_l2tp.py
@@ -358,10 +358,10 @@ def generate(l2tp):
if not l2tp:
return None
- render(l2tp_conf, 'accel-ppp/l2tp.config.tmpl', l2tp)
+ render(l2tp_conf, 'accel-ppp/l2tp.config.j2', l2tp)
if l2tp['auth_mode'] == 'local':
- render(l2tp_chap_secrets, 'accel-ppp/chap-secrets.tmpl', l2tp)
+ render(l2tp_chap_secrets, 'accel-ppp/chap-secrets.j2', l2tp)
os.chmod(l2tp_chap_secrets, S_IRUSR | S_IWUSR | S_IRGRP)
else:
diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py
index 51ea1f223..8e0e30bbf 100755
--- a/src/conf_mode/vpn_openconnect.py
+++ b/src/conf_mode/vpn_openconnect.py
@@ -24,6 +24,7 @@ from vyos.pki import wrap_private_key
from vyos.template import render
from vyos.util import call
from vyos.util import is_systemd_service_running
+from vyos.util import dict_search
from vyos.xml import defaults
from vyos import ConfigError
from crypt import crypt, mksalt, METHOD_SHA512
@@ -35,6 +36,7 @@ airbag.enable()
cfg_dir = '/run/ocserv'
ocserv_conf = cfg_dir + '/ocserv.conf'
ocserv_passwd = cfg_dir + '/ocpasswd'
+ocserv_otp_usr = cfg_dir + '/users.oath'
radius_cfg = cfg_dir + '/radiusclient.conf'
radius_servers = cfg_dir + '/radius_servers'
@@ -54,6 +56,16 @@ def get_config():
default_values = defaults(base)
ocserv = dict_merge(default_values, ocserv)
+ # workaround a "know limitation" - https://phabricator.vyos.net/T2665
+ del ocserv['authentication']['local_users']['username']['otp']
+ if not ocserv["authentication"]["local_users"]["username"]:
+ raise ConfigError('openconnect mode local required at least one user')
+ default_ocserv_usr_values = default_values['authentication']['local_users']['username']['otp']
+ for user, params in ocserv['authentication']['local_users']['username'].items():
+ # Not every configuration requires OTP settings
+ if ocserv['authentication']['local_users']['username'][user].get('otp'):
+ ocserv['authentication']['local_users']['username'][user]['otp'] = dict_merge(default_ocserv_usr_values, ocserv['authentication']['local_users']['username'][user]['otp'])
+
if ocserv:
ocserv['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'),
get_first_key=True, no_tag_node_value_mangle=True)
@@ -63,17 +75,34 @@ def get_config():
def verify(ocserv):
if ocserv is None:
return None
-
# Check authentication
if "authentication" in ocserv:
if "mode" in ocserv["authentication"]:
if "local" in ocserv["authentication"]["mode"]:
- if not ocserv["authentication"]["local_users"] or not ocserv["authentication"]["local_users"]["username"]:
- raise ConfigError('openconnect mode local required at leat one user')
+ if "radius" in ocserv["authentication"]["mode"]:
+ raise ConfigError('OpenConnect authentication modes are mutually-exclusive, remove either local or radius from your configuration')
+ if not ocserv["authentication"]["local_users"]:
+ raise ConfigError('openconnect mode local required at least one user')
+ if not ocserv["authentication"]["local_users"]["username"]:
+ raise ConfigError('openconnect mode local required at least one user')
else:
- for user in ocserv["authentication"]["local_users"]["username"]:
- if not "password" in ocserv["authentication"]["local_users"]["username"][user]:
- raise ConfigError(f'password required for user {user}')
+ # For OTP mode: verify that each local user has an OTP key
+ if "otp" in ocserv["authentication"]["mode"]["local"]:
+ users_wo_key = []
+ for user, user_config in ocserv["authentication"]["local_users"]["username"].items():
+ # User has no OTP key defined
+ if dict_search('otp.key', user_config) == None:
+ users_wo_key.append(user)
+ if users_wo_key:
+ raise ConfigError(f'OTP enabled, but no OTP key is configured for these users:\n{users_wo_key}')
+ # For password (and default) mode: verify that each local user has password
+ if "password" in ocserv["authentication"]["mode"]["local"] or "otp" not in ocserv["authentication"]["mode"]["local"]:
+ users_wo_pswd = []
+ for user in ocserv["authentication"]["local_users"]["username"]:
+ if not "password" in ocserv["authentication"]["local_users"]["username"][user]:
+ users_wo_pswd.append(user)
+ if users_wo_pswd:
+ raise ConfigError(f'password required for users:\n{users_wo_pswd}')
else:
raise ConfigError('openconnect authentication mode required')
else:
@@ -122,22 +151,57 @@ def verify(ocserv):
else:
raise ConfigError('openconnect network settings required')
-
def generate(ocserv):
if not ocserv:
return None
if "radius" in ocserv["authentication"]["mode"]:
# Render radius client configuration
- render(radius_cfg, 'ocserv/radius_conf.tmpl', ocserv["authentication"]["radius"])
+ render(radius_cfg, 'ocserv/radius_conf.j2', ocserv["authentication"]["radius"])
# Render radius servers
- render(radius_servers, 'ocserv/radius_servers.tmpl', ocserv["authentication"]["radius"])
+ render(radius_servers, 'ocserv/radius_servers.j2', ocserv["authentication"]["radius"])
+ elif "local" in ocserv["authentication"]["mode"]:
+ # if mode "OTP", generate OTP users file parameters
+ if "otp" in ocserv["authentication"]["mode"]["local"]:
+ if "local_users" in ocserv["authentication"]:
+ for user in ocserv["authentication"]["local_users"]["username"]:
+ # OTP token type from CLI parameters:
+ otp_interval = str(ocserv["authentication"]["local_users"]["username"][user]["otp"].get("interval"))
+ token_type = ocserv["authentication"]["local_users"]["username"][user]["otp"].get("token_type")
+ otp_length = str(ocserv["authentication"]["local_users"]["username"][user]["otp"].get("otp_length"))
+ if token_type == "hotp-time":
+ otp_type = "HOTP/T" + otp_interval
+ elif token_type == "hotp-event":
+ otp_type = "HOTP/E"
+ else:
+ otp_type = "HOTP/T" + otp_interval
+ ocserv["authentication"]["local_users"]["username"][user]["otp"]["token_tmpl"] = otp_type + "/" + otp_length
+ # if there is a password, generate hash
+ if "password" in ocserv["authentication"]["mode"]["local"] or not "otp" in ocserv["authentication"]["mode"]["local"]:
+ if "local_users" in ocserv["authentication"]:
+ for user in ocserv["authentication"]["local_users"]["username"]:
+ ocserv["authentication"]["local_users"]["username"][user]["hash"] = get_hash(ocserv["authentication"]["local_users"]["username"][user]["password"])
+
+ if "password-otp" in ocserv["authentication"]["mode"]["local"]:
+ # Render local users ocpasswd
+ render(ocserv_passwd, 'ocserv/ocserv_passwd.j2', ocserv["authentication"]["local_users"])
+ # Render local users OTP keys
+ render(ocserv_otp_usr, 'ocserv/ocserv_otp_usr.j2', ocserv["authentication"]["local_users"])
+ elif "password" in ocserv["authentication"]["mode"]["local"]:
+ # Render local users ocpasswd
+ render(ocserv_passwd, 'ocserv/ocserv_passwd.j2', ocserv["authentication"]["local_users"])
+ elif "otp" in ocserv["authentication"]["mode"]["local"]:
+ # Render local users OTP keys
+ render(ocserv_otp_usr, 'ocserv/ocserv_otp_usr.j2', ocserv["authentication"]["local_users"])
+ else:
+ # Render local users ocpasswd
+ render(ocserv_passwd, 'ocserv/ocserv_passwd.j2', ocserv["authentication"]["local_users"])
else:
if "local_users" in ocserv["authentication"]:
for user in ocserv["authentication"]["local_users"]["username"]:
ocserv["authentication"]["local_users"]["username"][user]["hash"] = get_hash(ocserv["authentication"]["local_users"]["username"][user]["password"])
# Render local users
- render(ocserv_passwd, 'ocserv/ocserv_passwd.tmpl', ocserv["authentication"]["local_users"])
+ render(ocserv_passwd, 'ocserv/ocserv_passwd.j2', ocserv["authentication"]["local_users"])
if "ssl" in ocserv:
cert_file_path = os.path.join(cfg_dir, 'cert.pem')
@@ -163,13 +227,13 @@ def generate(ocserv):
f.write(wrap_certificate(pki_ca_cert['certificate']))
# Render config
- render(ocserv_conf, 'ocserv/ocserv_config.tmpl', ocserv)
+ render(ocserv_conf, 'ocserv/ocserv_config.j2', ocserv)
def apply(ocserv):
if not ocserv:
call('systemctl stop ocserv.service')
- for file in [ocserv_conf, ocserv_passwd]:
+ for file in [ocserv_conf, ocserv_passwd, ocserv_otp_usr]:
if os.path.exists(file):
os.unlink(file)
else:
diff --git a/src/conf_mode/vpn_pptp.py b/src/conf_mode/vpn_pptp.py
index 30abe4782..7550c411e 100755
--- a/src/conf_mode/vpn_pptp.py
+++ b/src/conf_mode/vpn_pptp.py
@@ -264,10 +264,10 @@ def generate(pptp):
if not pptp:
return None
- render(pptp_conf, 'accel-ppp/pptp.config.tmpl', pptp)
+ render(pptp_conf, 'accel-ppp/pptp.config.j2', pptp)
if pptp['local_users']:
- render(pptp_chap_secrets, 'accel-ppp/chap-secrets.tmpl', pptp)
+ render(pptp_chap_secrets, 'accel-ppp/chap-secrets.j2', pptp)
os.chmod(pptp_chap_secrets, S_IRUSR | S_IWUSR | S_IRGRP)
else:
if os.path.exists(pptp_chap_secrets):
diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py
index 68980e5ab..db53463cf 100755
--- a/src/conf_mode/vpn_sstp.py
+++ b/src/conf_mode/vpn_sstp.py
@@ -114,7 +114,7 @@ def generate(sstp):
return None
# accel-cmd reload doesn't work so any change results in a restart of the daemon
- render(sstp_conf, 'accel-ppp/sstp.config.tmpl', sstp)
+ render(sstp_conf, 'accel-ppp/sstp.config.j2', sstp)
cert_name = sstp['ssl']['certificate']
pki_cert = sstp['pki']['certificate'][cert_name]
@@ -127,7 +127,7 @@ def generate(sstp):
write_file(ca_cert_file_path, wrap_certificate(pki_ca['certificate']))
if dict_search('authentication.mode', sstp) == 'local':
- render(sstp_chap_secrets, 'accel-ppp/chap-secrets.config_dict.tmpl',
+ render(sstp_chap_secrets, 'accel-ppp/chap-secrets.config_dict.j2',
sstp, permission=0o640)
else:
if os.path.exists(sstp_chap_secrets):
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index f79c8a21e..972d0289b 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -83,7 +83,8 @@ def get_config(config=None):
conf = Config()
base = ['vrf']
- vrf = conf.get_config_dict(base, get_first_key=True)
+ vrf = conf.get_config_dict(base, key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True, get_first_key=True)
# determine which VRF has been removed
for name in node_changed(conf, base + ['name']):
@@ -133,10 +134,10 @@ def verify(vrf):
def generate(vrf):
- render(config_file, 'vrf/vrf.conf.tmpl', vrf)
+ render(config_file, 'vrf/vrf.conf.j2', vrf)
# Render nftables zones config
- render(nft_vrf_config, 'firewall/nftables-vrf-zones.tmpl', vrf)
+ render(nft_vrf_config, 'firewall/nftables-vrf-zones.j2', vrf)
return None
@@ -152,7 +153,7 @@ def apply(vrf):
# set the default VRF global behaviour
bind_all = '0'
- if 'bind-to-all' in vrf:
+ if 'bind_to_all' in vrf:
bind_all = '1'
sysctl_write('net.ipv4.tcp_l3mdev_accept', bind_all)
sysctl_write('net.ipv4.udp_l3mdev_accept', bind_all)
@@ -222,6 +223,15 @@ def apply(vrf):
# add VRF description if available
vrf_if.set_alias(config.get('description', ''))
+ # Enable/Disable IPv4 forwarding
+ tmp = dict_search('ip.disable_forwarding', config)
+ value = '0' if (tmp != None) else '1'
+ vrf_if.set_ipv4_forwarding(value)
+ # Enable/Disable IPv6 forwarding
+ tmp = dict_search('ipv6.disable_forwarding', config)
+ value = '0' if (tmp != None) else '1'
+ vrf_if.set_ipv6_forwarding(value)
+
# Enable/Disable of an interface must always be done at the end of the
# derived class to make use of the ref-counting set_admin_state()
# function. We will only enable the interface if 'up' was called as
diff --git a/src/conf_mode/vrf_vni.py b/src/conf_mode/vrf_vni.py
index 1a7bd1f09..585fdbebf 100755
--- a/src/conf_mode/vrf_vni.py
+++ b/src/conf_mode/vrf_vni.py
@@ -40,7 +40,7 @@ def verify(vrf):
return None
def generate(vrf):
- vrf['new_frr_config'] = render_to_string('frr/vrf-vni.frr.tmpl', vrf)
+ vrf['new_frr_config'] = render_to_string('frr/vrf-vni.frr.j2', vrf)
return None
def apply(vrf):
diff --git a/src/conf_mode/zone_policy.py b/src/conf_mode/zone_policy.py
index dc0617353..070a4deea 100755
--- a/src/conf_mode/zone_policy.py
+++ b/src/conf_mode/zone_policy.py
@@ -192,7 +192,7 @@ def generate(zone_policy):
if 'local_zone' in zone_conf:
zone_conf['from_local'] = get_local_from(data, zone)
- render(nftables_conf, 'zone_policy/nftables.tmpl', data)
+ render(nftables_conf, 'zone_policy/nftables.j2', data)
return None
def apply(zone_policy):
diff --git a/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback b/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback
index bb918a468..fa1917ab1 100755
--- a/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback
+++ b/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-2022 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
@@ -23,14 +23,9 @@
from sys import argv
from sys import exit
-from syslog import syslog
-from syslog import openlog
-from syslog import LOG_PID
-from syslog import LOG_INFO
-
from vyos.configquery import ConfigTreeQuery
+from vyos.configdict import get_interface_dict
from vyos.ifconfig import PPPoEIf
-from vyos.util import read_file
# When the ppp link comes up, this script is called with the following
# parameters
@@ -45,15 +40,10 @@ if (len(argv) < 7):
exit(1)
interface = argv[6]
-dialer_pid = read_file(f'/var/run/{interface}.pid')
-
-openlog(ident=f'pppd[{dialer_pid}]', facility=LOG_INFO)
-syslog('executing ' + argv[0])
conf = ConfigTreeQuery()
-pppoe = conf.get_config_dict(['interfaces', 'pppoe', argv[6]],
- get_first_key=True, key_mangling=('-', '_'))
-pppoe['ifname'] = argv[6]
+_, pppoe = get_interface_dict(conf.config, ['interfaces', 'pppoe'], interface)
-p = PPPoEIf(pppoe['ifname'])
+# Update the config
+p = PPPoEIf(interface)
p.update(pppoe)
diff --git a/src/migration-scripts/interfaces/25-to-26 b/src/migration-scripts/interfaces/25-to-26
new file mode 100755
index 000000000..a8936235e
--- /dev/null
+++ b/src/migration-scripts/interfaces/25-to-26
@@ -0,0 +1,54 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 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/>.
+
+# T4384: pppoe: replace default-route CLI option with common CLI nodes already
+# present for DHCP
+
+from sys import argv
+
+from vyos.ethtool import Ethtool
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['interfaces', 'pppoe']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ exit(0)
+
+for ifname in config.list_nodes(base):
+ tmp_config = base + [ifname, 'default-route']
+ if config.exists(tmp_config):
+ # Retrieve current config value
+ value = config.return_value(tmp_config)
+ # Delete old Config node
+ config.delete(tmp_config)
+ if value == 'none':
+ config.set(base + [ifname, 'no-default-route'])
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print(f'Failed to save the modified config: {e}')
+ exit(1)
diff --git a/src/migration-scripts/openconnect/1-to-2 b/src/migration-scripts/openconnect/1-to-2
new file mode 100755
index 000000000..7031fb252
--- /dev/null
+++ b/src/migration-scripts/openconnect/1-to-2
@@ -0,0 +1,54 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 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/>.
+
+# Delete depricated outside-nexthop address
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+cfg_base = ['vpn', 'openconnect']
+
+if not config.exists(cfg_base):
+ # Nothing to do
+ sys.exit(0)
+else:
+ if config.exists(cfg_base + ['authentication', 'mode']):
+ if config.return_value(cfg_base + ['authentication', 'mode']) == 'radius':
+ # if "mode value radius", change to "tag node mode + valueless node radius"
+ config.delete(cfg_base + ['authentication','mode', 'radius'])
+ config.set(cfg_base + ['authentication', 'mode', 'radius'], value=None, replace=True)
+ elif not config.exists(cfg_base + ['authentication', 'mode', 'local']):
+ # if "mode local", change to "tag node mode + node local value password"
+ config.delete(cfg_base + ['authentication', 'mode', 'local'])
+ config.set(cfg_base + ['authentication', 'mode', 'local'], value='password', replace=True)
+ 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))
+ sys.exit(1)
diff --git a/src/migration-scripts/quagga/9-to-10 b/src/migration-scripts/quagga/9-to-10
new file mode 100755
index 000000000..249738822
--- /dev/null
+++ b/src/migration-scripts/quagga/9-to-10
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 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/>.
+
+# re-organize route-map as-path
+
+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()
+
+base = ['policy', 'route-map']
+
+config = ConfigTree(config_file)
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+for route_map in config.list_nodes(base):
+ # Bail out Early
+ if not config.exists(base + [route_map, 'rule']):
+ continue
+
+ for rule in config.list_nodes(base + [route_map, 'rule']):
+ rule_base = base + [route_map, 'rule', rule]
+ if config.exists(rule_base + ['set', 'as-path-exclude']):
+ tmp = config.return_value(rule_base + ['set', 'as-path-exclude'])
+ config.delete(rule_base + ['set', 'as-path-exclude'])
+ config.set(rule_base + ['set', 'as-path', 'exclude'], value=tmp)
+
+ if config.exists(rule_base + ['set', 'as-path-prepend']):
+ tmp = config.return_value(rule_base + ['set', 'as-path-prepend'])
+ config.delete(rule_base + ['set', 'as-path-prepend'])
+ config.set(rule_base + ['set', 'as-path', 'prepend'], value=tmp)
+
+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/system/23-to-24 b/src/migration-scripts/system/23-to-24
new file mode 100755
index 000000000..5ea71d51a
--- /dev/null
+++ b/src/migration-scripts/system/23-to-24
@@ -0,0 +1,85 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+
+from ipaddress import ip_interface
+from ipaddress import ip_address
+from sys import exit, argv
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['protocols', 'static', 'arp']
+tmp_base = ['protocols', 'static', 'arp-tmp']
+config = ConfigTree(config_file)
+
+def fixup_cli(config, path, interface):
+ if config.exists(path + ['address']):
+ for address in config.return_values(path + ['address']):
+ tmp = ip_interface(address)
+ if ip_address(host) in tmp.network.hosts():
+ mac = config.return_value(tmp_base + [host, 'hwaddr'])
+ iface_path = ['protocols', 'static', 'arp', 'interface']
+ config.set(iface_path + [interface, 'address', host, 'mac'], value=mac)
+ config.set_tag(iface_path)
+ config.set_tag(iface_path + [interface, 'address'])
+ continue
+
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+# We need a temporary copy of the config tree as the original one needs to be
+# deleted first due to a change iun thge tagNode structure.
+config.copy(base, tmp_base)
+config.delete(base)
+
+for host in config.list_nodes(tmp_base):
+ for type in config.list_nodes(['interfaces']):
+ for interface in config.list_nodes(['interfaces', type]):
+ if_base = ['interfaces', type, interface]
+ fixup_cli(config, if_base, interface)
+
+ if config.exists(if_base + ['vif']):
+ for vif in config.list_nodes(if_base + ['vif']):
+ vif_base = ['interfaces', type, interface, 'vif', vif]
+ fixup_cli(config, vif_base, f'{interface}.{vif}')
+
+ if config.exists(if_base + ['vif-s']):
+ for vif_s in config.list_nodes(if_base + ['vif-s']):
+ vif_s_base = ['interfaces', type, interface, 'vif-s', vif_s]
+ fixup_cli(config, vif_s_base, f'{interface}.{vif_s}')
+
+ if config.exists(if_base + ['vif-s', vif_s, 'vif-c']):
+ for vif_c in config.list_nodes(if_base + ['vif-s', vif_s, 'vif-c']):
+ vif_c_base = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c', vif_c]
+ fixup_cli(config, vif_c_base, f'{interface}.{vif_s}.{vif_c}')
+
+config.delete(tmp_base)
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print(f'Failed to save the modified config: {e}')
+ exit(1)
diff --git a/src/op_mode/conntrack_sync.py b/src/op_mode/conntrack_sync.py
index 89f6df4b9..e45c38f07 100755
--- a/src/op_mode/conntrack_sync.py
+++ b/src/op_mode/conntrack_sync.py
@@ -77,7 +77,7 @@ def xml_to_stdout(xml):
parsed = xmltodict.parse(line)
out.append(parsed)
- print(render_to_string('conntrackd/conntrackd.op-mode.tmpl', {'data' : out}))
+ print(render_to_string('conntrackd/conntrackd.op-mode.j2', {'data' : out}))
if __name__ == '__main__':
args = parser.parse_args()
diff --git a/src/op_mode/containers_op.py b/src/op_mode/containers_op.py
deleted file mode 100755
index bc317029c..000000000
--- a/src/op_mode/containers_op.py
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 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
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import argparse
-
-from getpass import getuser
-from vyos.configquery import ConfigTreeQuery
-from vyos.util import cmd
-
-parser = argparse.ArgumentParser()
-parser.add_argument("-a", "--all", action="store_true", help="Show all containers")
-parser.add_argument("-i", "--image", action="store_true", help="Show container images")
-parser.add_argument("-n", "--networks", action="store_true", help="Show container images")
-parser.add_argument("-p", "--pull", action="store", help="Pull image for container")
-parser.add_argument("-d", "--remove", action="store", help="Delete container image")
-parser.add_argument("-u", "--update", action="store", help="Update given container image")
-
-config = ConfigTreeQuery()
-base = ['container']
-if not config.exists(base):
- print('Containers not configured')
- exit(0)
-
-if getuser() != 'root':
- raise OSError('This functions needs to be run as root to return correct results!')
-
-if __name__ == '__main__':
- args = parser.parse_args()
-
- if args.all:
- print(cmd('podman ps --all'))
-
- elif args.image:
- print(cmd('podman image ls'))
-
- elif args.networks:
- print(cmd('podman network ls'))
-
- elif args.pull:
- image = args.pull
- try:
- print(cmd(f'podman image pull {image}'))
- except:
- print(f'Can\'t find or download image "{image}"')
-
- elif args.remove:
- image = args.remove
- try:
- print(cmd(f'podman image rm {image}'))
- except:
- print(f'Can\'t delete image "{image}"')
-
- elif args.update:
- tmp = config.get_config_dict(base + ['name', args.update],
- key_mangling=('-', '_'), get_first_key=True)
- try:
- image = tmp['image']
- print(cmd(f'podman image pull {image}'))
- except:
- print(f'Can\'t find or download image "{image}"')
- else:
- parser.print_help()
- exit(1)
-
- exit(0)
diff --git a/src/op_mode/generate_openconnect_otp_key.py b/src/op_mode/generate_openconnect_otp_key.py
new file mode 100755
index 000000000..363bcf3ea
--- /dev/null
+++ b/src/op_mode/generate_openconnect_otp_key.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import argparse
+import os
+
+from vyos.util import popen
+from secrets import token_hex
+from base64 import b32encode
+
+if os.geteuid() != 0:
+ exit("You need to have root privileges to run this script.\nPlease try again, this time using 'sudo'. Exiting.")
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+ parser.add_argument("-u", "--username", type=str, help='Username used for authentication', required=True)
+ parser.add_argument("-i", "--interval", type=str, help='Duration of single time interval', default="30", required=False)
+ parser.add_argument("-d", "--digits", type=str, help='The number of digits in the one-time password', default="6", required=False)
+ args = parser.parse_args()
+
+ hostname = os.uname()[1]
+ username = args.username
+ digits = args.digits
+ period = args.interval
+
+ # check variables:
+ if int(digits) < 6 or int(digits) > 8:
+ print("")
+ quit("The number of digits in the one-time password must be between '6' and '8'")
+
+ if int(period) < 5 or int(period) > 86400:
+ print("")
+ quit("Time token interval must be between '5' and '86400' seconds")
+
+ # generate OTP key, URL & QR:
+ key_hex = token_hex(20)
+ key_base32 = b32encode(bytes.fromhex(key_hex)).decode()
+
+ otp_url=''.join(["otpauth://totp/",username,"@",hostname,"?secret=",key_base32,"&digits=",digits,"&period=",period])
+ qrcode,err = popen('qrencode -t ansiutf8', input=otp_url)
+
+ print("# You can share it with the user, he just needs to scan the QR in his OTP app")
+ print("# username: ", username)
+ print("# OTP KEY: ", key_base32)
+ print("# OTP URL: ", otp_url)
+ print(qrcode)
+ print('# To add this OTP key to configuration, run the following commands:')
+ print(f"set vpn openconnect authentication local-users username {username} otp key '{key_hex}'")
+ if period != "30":
+ print(f"set vpn openconnect authentication local-users username {username} otp interval '{period}'")
+ if digits != "6":
+ print(f"set vpn openconnect authentication local-users username {username} otp otp-length '{digits}'")
diff --git a/src/op_mode/generate_ovpn_client_file.py b/src/op_mode/generate_ovpn_client_file.py
index 29db41e37..0628e6135 100755
--- a/src/op_mode/generate_ovpn_client_file.py
+++ b/src/op_mode/generate_ovpn_client_file.py
@@ -18,6 +18,7 @@ import argparse
import os
from jinja2 import Template
+from textwrap import fill
from vyos.configquery import ConfigTreeQuery
from vyos.ifconfig import Section
@@ -117,8 +118,11 @@ if __name__ == '__main__':
exit(f'OpenVPN certificate key "{key}" does not exist!')
ca = config.value(['pki', 'ca', ca, 'certificate'])
+ ca = fill(ca, width=64)
cert = config.value(['pki', 'certificate', cert, 'certificate'])
+ cert = fill(cert, width=64)
key = config.value(['pki', 'certificate', key, 'private', 'key'])
+ key = fill(key, width=64)
remote_host = config.value(base + [interface, 'local-host'])
ovpn_conf = config.get_config_dict(base + [interface], key_mangling=('-', '_'), get_first_key=True)
diff --git a/src/op_mode/ikev2_profile_generator.py b/src/op_mode/ikev2_profile_generator.py
index 990b06c12..21561d16f 100755
--- a/src/op_mode/ikev2_profile_generator.py
+++ b/src/op_mode/ikev2_profile_generator.py
@@ -222,9 +222,9 @@ except KeyboardInterrupt:
print('\n\n==== <snip> ====')
if args.os == 'ios':
- print(render_to_string('ipsec/ios_profile.tmpl', data))
+ print(render_to_string('ipsec/ios_profile.j2', 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(render_to_string('ipsec/windows_profile.j2', data))
print('==== </snip> ====\n')
diff --git a/src/op_mode/restart_frr.py b/src/op_mode/restart_frr.py
index e5014452f..91b25567a 100755
--- a/src/op_mode/restart_frr.py
+++ b/src/op_mode/restart_frr.py
@@ -22,6 +22,7 @@ import psutil
from logging.handlers import SysLogHandler
from shutil import rmtree
+from vyos.base import Warning
from vyos.util import call
from vyos.util import ask_yes_no
from vyos.util import process_named_running
@@ -163,7 +164,7 @@ if cmd_args.action == 'restart':
if cmd_args.daemon != ['']:
for daemon in cmd_args.daemon:
if not process_named_running(daemon):
- print('WARNING: some of listed daemons are not running!')
+ Warning('some of listed daemons are not running!')
# run command to restart daemon
for daemon in cmd_args.daemon:
diff --git a/src/op_mode/show_dhcp.py b/src/op_mode/show_dhcp.py
index cd6e8ed43..4b1758eea 100755
--- a/src/op_mode/show_dhcp.py
+++ b/src/op_mode/show_dhcp.py
@@ -26,6 +26,7 @@ from datetime import datetime
from isc_dhcp_leases import Lease, IscDhcpLeases
+from vyos.base import Warning
from vyos.config import Config
from vyos.util import is_systemd_service_running
@@ -213,7 +214,7 @@ if __name__ == '__main__':
# if dhcp server is down, inactive leases may still be shown as active, so warn the user.
if not is_systemd_service_running('isc-dhcp-server.service'):
- print("WARNING: DHCP server is configured but not started. Data may be stale.")
+ Warning('DHCP server is configured but not started. Data may be stale.')
if args.leases:
leases = get_leases(conf, lease_file, args.state, args.pool, args.sort)
diff --git a/src/op_mode/show_dhcpv6.py b/src/op_mode/show_dhcpv6.py
index 1f987ff7b..b34b730e6 100755
--- a/src/op_mode/show_dhcpv6.py
+++ b/src/op_mode/show_dhcpv6.py
@@ -26,6 +26,7 @@ from datetime import datetime
from isc_dhcp_leases import Lease, IscDhcpLeases
+from vyos.base import Warning
from vyos.config import Config
from vyos.util import is_systemd_service_running
@@ -203,7 +204,7 @@ if __name__ == '__main__':
# if dhcp server is down, inactive leases may still be shown as active, so warn the user.
if not is_systemd_service_running('isc-dhcp-server6.service'):
- print("WARNING: DHCPv6 server is configured but not started. Data may be stale.")
+ Warning('DHCPv6 server is configured but not started. Data may be stale.')
if args.leases:
leases = get_leases(conf, lease_file, args.state, args.pool, args.sort)
diff --git a/src/op_mode/show_openvpn.py b/src/op_mode/show_openvpn.py
index f7b99cc0d..9a5adcffb 100755
--- a/src/op_mode/show_openvpn.py
+++ b/src/op_mode/show_openvpn.py
@@ -26,10 +26,10 @@ outp_tmpl = """
{% if clients %}
OpenVPN status on {{ intf }}
-Client CN Remote Host Local Host TX bytes RX bytes Connected Since
---------- ----------- ---------- -------- -------- ---------------
+Client CN Remote Host Tunnel IP Local Host TX bytes RX bytes Connected Since
+--------- ----------- --------- ---------- -------- -------- ---------------
{% for c in clients %}
-{{ "%-15s"|format(c.name) }} {{ "%-21s"|format(c.remote) }} {{ "%-21s"|format(local) }} {{ "%-9s"|format(c.tx_bytes) }} {{ "%-9s"|format(c.rx_bytes) }} {{ c.online_since }}
+{{ "%-15s"|format(c.name) }} {{ "%-21s"|format(c.remote) }} {{ "%-15s"|format(c.tunnel) }} {{ "%-21s"|format(local) }} {{ "%-9s"|format(c.tx_bytes) }} {{ "%-9s"|format(c.rx_bytes) }} {{ c.online_since }}
{% endfor %}
{% endif %}
"""
@@ -50,6 +50,19 @@ def bytes2HR(size):
output="{0:.1f} {1}".format(size, suff[suffIdx])
return output
+def get_vpn_tunnel_address(peer, interface):
+ lst = []
+ status_file = '/var/run/openvpn/{}.status'.format(interface)
+
+ with open(status_file, 'r') as f:
+ lines = f.readlines()
+ for line in lines:
+ if peer in line:
+ lst.append(line)
+ tunnel_ip = lst[1].split(',')[0]
+
+ return tunnel_ip
+
def get_status(mode, interface):
status_file = '/var/run/openvpn/{}.status'.format(interface)
# this is an empirical value - I assume we have no more then 999999
@@ -110,7 +123,7 @@ def get_status(mode, interface):
'tx_bytes': bytes2HR(line.split(',')[3]),
'online_since': line.split(',')[4]
}
-
+ client["tunnel"] = get_vpn_tunnel_address(client['remote'], interface)
data['clients'].append(client)
continue
else:
@@ -173,5 +186,7 @@ if __name__ == '__main__':
if len(remote_host) >= 1:
client['remote'] = str(remote_host[0]) + ':' + remote_port
+ client['tunnel'] = 'N/A'
+
tmpl = jinja2.Template(outp_tmpl)
print(tmpl.render(data))
diff --git a/src/op_mode/traceroute.py b/src/op_mode/traceroute.py
new file mode 100755
index 000000000..4299d6e5f
--- /dev/null
+++ b/src/op_mode/traceroute.py
@@ -0,0 +1,207 @@
+#! /usr/bin/env python3
+
+# Copyright (C) 2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import sys
+import socket
+import ipaddress
+
+options = {
+ 'backward-hops': {
+ 'traceroute': '{command} --back',
+ 'type': 'noarg',
+ 'help': 'Display number of backward hops when they different from the forwarded path'
+ },
+ 'bypass': {
+ 'traceroute': '{command} -r',
+ 'type': 'noarg',
+ 'help': 'Bypass the normal routing tables and send directly to a host on an attached network'
+ },
+ 'do-not-fragment': {
+ 'traceroute': '{command} -F',
+ 'type': 'noarg',
+ 'help': 'Do not fragment probe packets.'
+ },
+ 'first-ttl': {
+ 'traceroute': '{command} -f {value}',
+ 'type': '<ttl>',
+ 'help': 'Specifies with what TTL to start. Defaults to 1.'
+ },
+ 'icmp': {
+ 'traceroute': '{command} -I',
+ 'type': 'noarg',
+ 'help': 'Use ICMP ECHO for tracerouting'
+ },
+ 'interface': {
+ 'traceroute': '{command} -i {value}',
+ 'type': '<interface>',
+ 'help': 'Source interface'
+ },
+ 'lookup-as': {
+ 'traceroute': '{command} -A',
+ 'type': 'noarg',
+ 'help': 'Perform AS path lookups'
+ },
+ 'mark': {
+ 'traceroute': '{command} --fwmark={value}',
+ 'type': '<fwmark>',
+ 'help': 'Set the firewall mark for outgoing packets'
+ },
+ 'no-resolve': {
+ 'traceroute': '{command} -n',
+ 'type': 'noarg',
+ 'help': 'Do not resolve hostnames'
+ },
+ 'port': {
+ 'traceroute': '{command} -p {value}',
+ 'type': '<port>',
+ 'help': 'Destination port'
+ },
+ 'source-address': {
+ 'traceroute': '{command} -s {value}',
+ 'type': '<x.x.x.x> <h:h:h:h:h:h:h:h>',
+ 'help': 'Specify source IP v4/v6 address'
+ },
+ 'tcp': {
+ 'traceroute': '{command} -T',
+ 'type': 'noarg',
+ 'help': 'Use TCP SYN for tracerouting (default port is 80)'
+ },
+ 'tos': {
+ 'traceroute': '{commad} -t {value}',
+ 'type': '<tos>',
+ 'help': 'Mark packets with specified TOS'
+ },
+ 'ttl': {
+ 'traceroute': '{command} -m {value}',
+ 'type': '<ttl>',
+ 'help': 'Maximum number of hops'
+ },
+ 'udp': {
+ 'traceroute': '{command} -U',
+ 'type': 'noarg',
+ 'help': 'Use UDP to particular port for tracerouting (default port is 53)'
+ },
+ 'vrf': {
+ 'traceroute': 'sudo ip vrf exec {value} {command}',
+ 'type': '<vrf>',
+ 'help': 'Use specified VRF table',
+ 'dflt': 'default'}
+}
+
+traceroute = {
+ 4: '/bin/traceroute -4',
+ 6: '/bin/traceroute -6',
+}
+
+
+class List (list):
+ def first (self):
+ return self.pop(0) if self else ''
+
+ def last(self):
+ return self.pop() if self else ''
+
+ def prepend(self,value):
+ self.insert(0,value)
+
+
+def expension_failure(option, completions):
+ reason = 'Ambiguous' if completions else 'Invalid'
+ sys.stderr.write('\n\n {} command: {} [{}]\n\n'.format(reason,' '.join(sys.argv), option))
+ if completions:
+ sys.stderr.write(' Possible completions:\n ')
+ sys.stderr.write('\n '.join(completions))
+ sys.stderr.write('\n')
+ sys.stdout.write('<nocomps>')
+ sys.exit(1)
+
+
+def complete(prefix):
+ return [o for o in options if o.startswith(prefix)]
+
+
+def convert(command, args):
+ while args:
+ shortname = args.first()
+ longnames = complete(shortname)
+ if len(longnames) != 1:
+ expension_failure(shortname, longnames)
+ longname = longnames[0]
+ if options[longname]['type'] == 'noarg':
+ command = options[longname]['traceroute'].format(
+ command=command, value='')
+ elif not args:
+ sys.exit(f'traceroute: missing argument for {longname} option')
+ else:
+ command = options[longname]['traceroute'].format(
+ command=command, value=args.first())
+ return command
+
+
+if __name__ == '__main__':
+ args = List(sys.argv[1:])
+ host = args.first()
+
+ if not host:
+ sys.exit("traceroute: Missing host")
+
+ if host == '--get-options':
+ args.first() # pop traceroute
+ args.first() # pop IP
+ while args:
+ option = args.first()
+
+ matched = complete(option)
+ if not args:
+ sys.stdout.write(' '.join(matched))
+ sys.exit(0)
+
+ if len(matched) > 1 :
+ sys.stdout.write(' '.join(matched))
+ sys.exit(0)
+
+ if options[matched[0]]['type'] == 'noarg':
+ continue
+
+ value = args.first()
+ if not args:
+ matched = complete(option)
+ sys.stdout.write(options[matched[0]]['type'])
+ sys.exit(0)
+
+ for name,option in options.items():
+ if 'dflt' in option and name not in args:
+ args.append(name)
+ args.append(option['dflt'])
+
+ try:
+ ip = socket.gethostbyname(host)
+ except UnicodeError:
+ sys.exit(f'tracroute: Unknown host: {host}')
+ except socket.gaierror:
+ ip = host
+
+ try:
+ version = ipaddress.ip_address(ip).version
+ except ValueError:
+ sys.exit(f'traceroute: Unknown host: {host}')
+
+ command = convert(traceroute[version],args)
+
+ # print(f'{command} {host}')
+ os.system(f'{command} {host}')
+
diff --git a/src/op_mode/vpn_ipsec.py b/src/op_mode/vpn_ipsec.py
index 40854fa8f..8955e5a59 100755
--- a/src/op_mode/vpn_ipsec.py
+++ b/src/op_mode/vpn_ipsec.py
@@ -88,7 +88,22 @@ def reset_profile(profile, tunnel):
def debug_peer(peer, tunnel):
if not peer or peer == "all":
- call('sudo /usr/sbin/ipsec statusall')
+ debug_commands = [
+ "sudo ipsec statusall",
+ "sudo swanctl -L",
+ "sudo swanctl -l",
+ "sudo swanctl -P",
+ "sudo ip x sa show",
+ "sudo ip x policy show",
+ "sudo ip tunnel show",
+ "sudo ip address",
+ "sudo ip rule show",
+ "sudo ip route | head -100",
+ "sudo ip route show table 220"
+ ]
+ for debug_cmd in debug_commands:
+ print(f'\n### {debug_cmd} ###')
+ call(debug_cmd)
return
if not tunnel or tunnel == 'all':
diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd
index df9f18d2d..9ae7b1ea9 100755
--- a/src/services/vyos-hostsd
+++ b/src/services/vyos-hostsd
@@ -421,12 +421,12 @@ def pdns_rec_control(command):
def make_resolv_conf(state):
logger.info(f"Writing {RESOLV_CONF_FILE}")
- render(RESOLV_CONF_FILE, 'vyos-hostsd/resolv.conf.tmpl', state,
+ render(RESOLV_CONF_FILE, 'vyos-hostsd/resolv.conf.j2', state,
user='root', group='root')
def make_hosts(state):
logger.info(f"Writing {HOSTS_FILE}")
- render(HOSTS_FILE, 'vyos-hostsd/hosts.tmpl', state,
+ render(HOSTS_FILE, 'vyos-hostsd/hosts.j2', state,
user='root', group='root')
def make_pdns_rec_conf(state):
@@ -437,12 +437,12 @@ def make_pdns_rec_conf(state):
chmod_755(PDNS_REC_RUN_DIR)
render(PDNS_REC_LUA_CONF_FILE,
- 'dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl',
+ 'dns-forwarding/recursor.vyos-hostsd.conf.lua.j2',
state, user=PDNS_REC_USER, group=PDNS_REC_GROUP)
logger.info(f"Writing {PDNS_REC_ZONES_FILE}")
render(PDNS_REC_ZONES_FILE,
- 'dns-forwarding/recursor.forward-zones.conf.tmpl',
+ 'dns-forwarding/recursor.forward-zones.conf.j2',
state, user=PDNS_REC_USER, group=PDNS_REC_GROUP)
def set_host_name(state, data):
diff --git a/src/validators/as-number-list b/src/validators/as-number-list
new file mode 100755
index 000000000..432d44180
--- /dev/null
+++ b/src/validators/as-number-list
@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 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/>.
+
+if [ $# -lt 1 ]; then
+ echo "Illegal number of parameters"
+ exit 1
+fi
+
+for var in "$@"; do
+ ${vyos_validators_dir}/numeric --range 1-4294967294 $var
+ if [ $? -ne 0 ]; then
+ exit 1
+ fi
+done
+
+exit 0
diff --git a/src/validators/port-multi b/src/validators/port-multi
index cef371563..bd6f0ef60 100755
--- a/src/validators/port-multi
+++ b/src/validators/port-multi
@@ -1,6 +1,7 @@
#!/usr/bin/python3
-import sys
+from sys import argv
+from sys import exit
import re
from vyos.util import read_file
@@ -13,12 +14,18 @@ def get_services():
for line in service_data.split("\n"):
if not line or line[0] == '#':
continue
- names.append(line.split(None, 1)[0])
+ tmp = line.split()
+ names.append(tmp[0])
+ if len(tmp) > 2:
+ # Add port aliases to service list, too
+ names.extend(tmp[2:])
+ # remove duplicate entries (e.g. echo) from list
+ names = list(dict.fromkeys(names))
return names
if __name__ == '__main__':
- if len(sys.argv)>1:
- ports = sys.argv[1].split(",")
+ if len(argv)>1:
+ ports = argv[1].split(",")
services = get_services()
for port in ports:
@@ -28,18 +35,18 @@ if __name__ == '__main__':
port_1, port_2 = port.split('-')
if int(port_1) not in range(1, 65536) or int(port_2) not in range(1, 65536):
print(f'Error: {port} is not a valid port range')
- sys.exit(1)
+ exit(1)
if int(port_1) > int(port_2):
print(f'Error: {port} is not a valid port range')
- sys.exit(1)
+ exit(1)
elif port.isnumeric():
if int(port) not in range(1, 65536):
print(f'Error: {port} is not a valid port')
- sys.exit(1)
+ exit(1)
elif port not in services:
print(f'Error: {port} is not a valid service name')
- sys.exit(1)
+ exit(1)
else:
- sys.exit(2)
+ exit(2)
- sys.exit(0)
+ exit(0)