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.py7
-rwxr-xr-xsrc/conf_mode/container.py (renamed from src/conf_mode/containers.py)86
-rwxr-xr-xsrc/conf_mode/dhcpv6_server.py33
-rwxr-xr-xsrc/conf_mode/firewall.py4
-rwxr-xr-xsrc/conf_mode/flow_accounting_conf.py4
-rwxr-xr-xsrc/conf_mode/high-availability.py10
-rwxr-xr-xsrc/conf_mode/http-api.py2
-rwxr-xr-xsrc/conf_mode/https.py4
-rwxr-xr-xsrc/conf_mode/igmp_proxy.py2
-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.py2
-rwxr-xr-xsrc/conf_mode/interfaces-geneve.py8
-rwxr-xr-xsrc/conf_mode/interfaces-l2tpv3.py6
-rwxr-xr-xsrc/conf_mode/interfaces-loopback.py2
-rwxr-xr-xsrc/conf_mode/interfaces-macsec.py2
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py9
-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.py8
-rwxr-xr-xsrc/conf_mode/interfaces-wireguard.py10
-rwxr-xr-xsrc/conf_mode/interfaces-wireless.py8
-rwxr-xr-xsrc/conf_mode/interfaces-wwan.py28
-rwxr-xr-xsrc/conf_mode/lldp.py4
-rwxr-xr-xsrc/conf_mode/nat.py2
-rwxr-xr-xsrc/conf_mode/nat66.py4
-rwxr-xr-xsrc/conf_mode/policy-route.py2
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py16
-rwxr-xr-xsrc/conf_mode/protocols_nhrp.py2
-rwxr-xr-xsrc/conf_mode/protocols_static.py5
-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.py10
-rwxr-xr-xsrc/conf_mode/ssh.py19
-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.py2
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py20
-rwxr-xr-xsrc/conf_mode/vpn_l2tp.py4
-rwxr-xr-xsrc/conf_mode/vpn_openconnect.py18
-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/zone_policy.py2
-rw-r--r--src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper2
-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/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/show_openvpn.py23
-rwxr-xr-xsrc/op_mode/traceroute.py207
-rwxr-xr-xsrc/op_mode/vpn_ipsec.py17
-rwxr-xr-xsrc/validators/as-number-list29
-rwxr-xr-xsrc/validators/port-multi27
78 files changed, 953 insertions, 481 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..c4b2bb488 100755
--- a/src/conf_mode/conntrack_sync.py
+++ b/src/conf_mode/conntrack_sync.py
@@ -111,11 +111,12 @@ 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
def apply(conntrack):
+ systemd_service = 'conntrackd.service'
if not conntrack:
# Failover mechanism daemon should be indicated that it no longer needs
# to execute conntrackd actions on transition. This is only required
@@ -123,7 +124,7 @@ def apply(conntrack):
if process_named_running('conntrackd'):
resync_vrrp()
- call('systemctl stop conntrackd.service')
+ call(f'systemctl stop {systemd_service}')
return None
# Failover mechanism daemon should be indicated that it needs to execute
@@ -132,7 +133,7 @@ def apply(conntrack):
if not process_named_running('conntrackd'):
resync_vrrp()
- call('systemctl restart conntrackd.service')
+ call(f'systemctl reload-or-restart {systemd_service}')
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/containers.py b/src/conf_mode/container.py
index 516671844..2110fd9e0 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
@@ -15,20 +15,19 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
-import json
from ipaddress import ip_address
from ipaddress import ip_network
from time import sleep
from json import dumps as json_write
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
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,26 @@ 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!')
+
+ # Check if requested container image exists locally. If it does not
+ # exist locally - inform the user. This is required as there is a
+ # shared container image storage accross all VyOS images. A user can
+ # delete a container image from the system, boot into another version
+ # of VyOS and then it would fail to boot. This is to prevent any
+ # configuration error when container images are deleted from the
+ # global storage. A per image local storage would be a super waste
+ # of diskspace as there will be a full copy (up tu several GB/image)
+ # on upgrade. This is the "cheapest" and fastest solution in terms
+ # of image upgrade and deletion.
+ image = container_config['image']
+ if run(f'podman image exists {image}') != 0:
+ Warning(f'Image "{image}" used in contianer "{name}" does not exist '\
+ f'locally. Please use "add container image {image}" to add it '\
+ f'to the system! Container "{name}" will not be started!')
+
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 +184,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 +223,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 +260,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
@@ -252,6 +285,11 @@ def apply(container):
for name, container_config in container['name'].items():
image = container_config['image']
+ if run(f'podman image exists {image}') != 0:
+ # container image does not exist locally - user already got
+ # informed by a WARNING in verfiy() - bail out early
+ continue
+
if 'disable' in container_config:
# check if there is a container by that name running
tmp = _cmd('podman ps -a --format "{{.Names}}"')
@@ -263,13 +301,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 +349,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 +357,10 @@ def apply(container):
address = container_config['network'][network]['address']
ipparam = f'--ip {address}'
- run(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)
+ _run_rerun(f'{container_base_cmd} --net {network} {ipparam} {image}')
return None
-
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py
index 9922f2c5c..078ff327c 100755
--- a/src/conf_mode/dhcpv6_server.py
+++ b/src/conf_mode/dhcpv6_server.py
@@ -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, no_tag_node_value_mangle=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!')
@@ -166,15 +173,15 @@ def generate(dhcpv6):
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/firewall.py b/src/conf_mode/firewall.py
index de78d53a8..6924bf555 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -327,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):
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..e14050dd3 100755
--- a/src/conf_mode/high-availability.py
+++ b/src/conf_mode/high-availability.py
@@ -28,7 +28,6 @@ from vyos.template import render
from vyos.template import is_ipv4
from vyos.template import is_ipv6
from vyos.util import call
-from vyos.util import is_systemd_service_running
from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
@@ -152,7 +151,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):
@@ -161,12 +160,7 @@ def apply(ha):
call(f'systemctl stop {service_name}')
return None
- # XXX: T3944 - reload keepalived configuration if service is already running
- # to not cause any service disruption when applying changes.
- if is_systemd_service_running(service_name):
- call(f'systemctl reload {service_name}')
- else:
- call(f'systemctl restart {service_name}')
+ call(f'systemctl reload-or-restart {service_name}')
return None
if __name__ == '__main__':
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 37df3dc92..de6a51c64 100755
--- a/src/conf_mode/igmp_proxy.py
+++ b/src/conf_mode/igmp_proxy.py
@@ -96,7 +96,7 @@ def generate(igmp_proxy):
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 333d39e0e..fec4456fb 100755
--- a/src/conf_mode/interfaces-ethernet.py
+++ b/src/conf_mode/interfaces-ethernet.py
@@ -65,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
diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py
index 26d248579..b9cf2fa3c 100755
--- a/src/conf_mode/interfaces-geneve.py
+++ b/src/conf_mode/interfaces-geneve.py
@@ -22,7 +22,7 @@ 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 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
@@ -43,16 +43,16 @@ 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, cli_option):
+ if leaf_node_changed(conf, base + [ifname, cli_option]):
geneve.update({'rebuild_required': {}})
- if node_changed(conf, ['parameters'], recursive=True):
+ if is_node_changed(conf, base + [ifname, 'parameters']):
geneve.update({'rebuild_required': {}})
return geneve
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 c71863e61..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:
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index a9be093c2..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.
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 53704827e..f44d754ba 100755
--- a/src/conf_mode/interfaces-vxlan.py
+++ b/src/conf_mode/interfaces-vxlan.py
@@ -23,7 +23,7 @@ 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 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
@@ -46,17 +46,17 @@ 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']:
- if leaf_node_changed(conf, cli_option):
+ if leaf_node_changed(conf, base + [ifname, cli_option]):
vxlan.update({'rebuild_required': {}})
- if node_changed(conf, ['parameters'], recursive=True):
+ 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
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 7fc22cdab..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
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 2bb615eb7..c703c1fe0 100755
--- a/src/conf_mode/lldp.py
+++ b/src/conf_mode/lldp.py
@@ -111,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 8aaebf9ff..85819a77e 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -181,7 +181,7 @@ 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}')
diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py
index 1cd15811f..0972151a0 100755
--- a/src/conf_mode/nat66.py
+++ b/src/conf_mode/nat66.py
@@ -146,8 +146,8 @@ def verify(nat):
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/policy-route.py b/src/conf_mode/policy-route.py
index 09d181d43..5de341beb 100755
--- a/src/conf_mode/policy-route.py
+++ b/src/conf_mode/policy-route.py
@@ -204,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/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index 8d9d3e99a..cd46cbcb4 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -164,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
diff --git a/src/conf_mode/protocols_nhrp.py b/src/conf_mode/protocols_nhrp.py
index 7eeb5cd30..b6371d09f 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):
diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py
index 87432bc1c..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
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 e35bb8a0c..ae060580d 100755
--- a/src/conf_mode/snmp.py
+++ b/src/conf_mode/snmp.py
@@ -270,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 487e8c229..28669694b 100755
--- a/src/conf_mode/ssh.py
+++ b/src/conf_mode/ssh.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
@@ -33,6 +33,9 @@ airbag.enable()
config_file = r'/run/sshd/sshd_config'
systemd_override = r'/etc/systemd/system/ssh.service.d/override.conf'
+sshguard_config_file = '/etc/sshguard/sshguard.conf'
+sshguard_whitelist = '/etc/sshguard/whitelist'
+
key_rsa = '/etc/ssh/ssh_host_rsa_key'
key_dsa = '/etc/ssh/ssh_host_dsa_key'
key_ed25519 = '/etc/ssh/ssh_host_ed25519_key'
@@ -54,6 +57,11 @@ def get_config(config=None):
# pass config file path - used in override template
ssh['config_file'] = config_file
+ # Ignore default XML values if config doesn't exists
+ # Delete key from dict
+ if not conf.exists(base + ['dynamic-protection']):
+ del ssh['dynamic_protection']
+
return ssh
def verify(ssh):
@@ -86,6 +94,10 @@ def generate(ssh):
render(config_file, 'ssh/sshd_config.j2', ssh)
render(systemd_override, 'ssh/override.conf.j2', ssh)
+
+ if 'dynamic_protection' in ssh:
+ render(sshguard_config_file, 'ssh/sshguard_config.j2', ssh)
+ render(sshguard_whitelist, 'ssh/sshguard_whitelist.j2', ssh)
# Reload systemd manager configuration
call('systemctl daemon-reload')
@@ -95,7 +107,12 @@ def apply(ssh):
if not ssh:
# SSH access is removed in the commit
call('systemctl stop ssh.service')
+ call('systemctl stop sshguard.service')
return None
+ if 'dynamic_protection' not in ssh:
+ call('systemctl stop sshguard.service')
+ else:
+ call('systemctl restart sshguard.service')
call('systemctl restart ssh.service')
return None
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 95050624e..c5daccb7f 100755
--- a/src/conf_mode/tftp_server.py
+++ b/src/conf_mode/tftp_server.py
@@ -98,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 84d31f9a5..8e0e30bbf 100755
--- a/src/conf_mode/vpn_openconnect.py
+++ b/src/conf_mode/vpn_openconnect.py
@@ -157,9 +157,9 @@ def generate(ocserv):
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"]:
@@ -184,24 +184,24 @@ def generate(ocserv):
if "password-otp" in ocserv["authentication"]["mode"]["local"]:
# Render local users ocpasswd
- render(ocserv_passwd, 'ocserv/ocserv_passwd.tmpl', ocserv["authentication"]["local_users"])
+ render(ocserv_passwd, 'ocserv/ocserv_passwd.j2', ocserv["authentication"]["local_users"])
# Render local users OTP keys
- render(ocserv_otp_usr, 'ocserv/ocserv_otp_usr.tmpl', ocserv["authentication"]["local_users"])
+ 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.tmpl', ocserv["authentication"]["local_users"])
+ 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.tmpl', ocserv["authentication"]["local_users"])
+ render(ocserv_otp_usr, 'ocserv/ocserv_otp_usr.j2', ocserv["authentication"]["local_users"])
else:
# Render local users ocpasswd
- render(ocserv_passwd, 'ocserv/ocserv_passwd.tmpl', ocserv["authentication"]["local_users"])
+ 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')
@@ -227,7 +227,7 @@ 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):
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/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/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper
index 74a7e83bf..5d879471d 100644
--- a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper
+++ b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper
@@ -26,7 +26,7 @@ function iptovtysh () {
local VTYSH_GATEWAY=""
local VTYSH_DEV=""
local VTYSH_TAG="210"
- local VTYSH_DISTANCE=""
+ local VTYSH_DISTANCE=$IF_METRIC
# convert default route to 0.0.0.0/0
if [ "$4" == "default" ] ; then
VTYSH_NETADDR="0.0.0.0/0"
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/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/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/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)