summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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)31
-rwxr-xr-xsrc/conf_mode/firewall.py4
-rwxr-xr-xsrc/conf_mode/flow_accounting_conf.py20
-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-pppoe.py2
-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.py10
-rwxr-xr-xsrc/conf_mode/protocols_nhrp.py13
-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.py74
-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.py20
-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-syslog.py4
-rwxr-xr-xsrc/conf_mode/system_console.py2
-rwxr-xr-xsrc/conf_mode/system_frr.py91
-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.py16
-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/migration-scripts/quagga/9-to-1062
-rwxr-xr-xsrc/op_mode/conntrack_sync.py2
-rwxr-xr-xsrc/op_mode/containers_op.py80
-rwxr-xr-xsrc/op_mode/ikev2_profile_generator.py4
-rwxr-xr-xsrc/op_mode/show_openvpn.py23
-rwxr-xr-xsrc/op_mode/show_uptime.py17
-rwxr-xr-xsrc/op_mode/traceroute.py207
-rwxr-xr-xsrc/validators/as-number-list29
53 files changed, 664 insertions, 217 deletions
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 1cc6f5a35..2110fd9e0 100755
--- a/src/conf_mode/containers.py
+++ b/src/conf_mode/container.py
@@ -15,13 +15,13 @@
# 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
@@ -110,15 +110,21 @@ def verify(container):
if 'image' not in container_config:
raise ConfigError(f'Container image for "{name}" is mandatory!')
- # verify container image exists locally
- image = container_config['image']
-
# Check if requested container image exists locally. If it does not
- # exist locally - inform the user.
+ # 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:
- raise ConfigError(f'Image "{image}" used in contianer "{name}" does not exist '\
- f'locally.\nPlease use "add container image {image}" to add it '\
- 'to the system!')
+ 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:
@@ -254,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/registries.conf.j2', container)
- render(config_containers_storage, 'containers/storage.conf.j2', container)
+ render(config_containers_registry, 'container/registries.conf.j2', container)
+ render(config_containers_storage, 'container/storage.conf.j2', container)
return None
@@ -279,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}}"')
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..7750c1247 100755
--- a/src/conf_mode/flow_accounting_conf.py
+++ b/src/conf_mode/flow_accounting_conf.py
@@ -22,6 +22,7 @@ import ipaddress
from ipaddress import ip_address
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.ifconfig import Section
@@ -109,6 +110,9 @@ def _nftables_config(configured_ifaces, direction, length=None):
iface_prefix = "o" if direction == "egress" else "i"
rule_definition = f'{iface_prefix}ifname "{iface}" counter log group 2 snaplen {length} queue-threshold 100 comment "FLOW_ACCOUNTING_RULE"'
nftable_commands.append(f'nft insert rule {nftables_table} {nftables_chain} {rule_definition}')
+ # Also add IPv6 ingres logging
+ if nftables_table == nftables_nflog_table:
+ nftable_commands.append(f'nft insert rule ip6 {nftables_table} {nftables_chain} {rule_definition}')
# change nftables
for command in nftable_commands:
@@ -172,7 +176,7 @@ def verify(flow_config):
if interface not in Section.interfaces():
# Changed from error to warning to allow adding dynamic interfaces
# and interface templates
- print(f'Warning: Interface "{interface}" is not presented in the system')
+ Warning(f'Interface "{interface}" is not presented in the system')
# check sFlow configuration
if 'sflow' in flow_config:
@@ -200,7 +204,13 @@ def verify(flow_config):
if 'agent_address' in flow_config['sflow']:
tmp = flow_config['sflow']['agent_address']
if not is_addr_assigned(tmp):
- print(f'Warning: Configured "sflow agent-address {tmp}" does not exist in the system!')
+ raise ConfigError(f'Configured "sflow agent-address {tmp}" does not exist in the system!')
+
+ # Check if configured netflow source-address exist in the system
+ if 'source_address' in flow_config['sflow']:
+ if not is_addr_assigned(flow_config['sflow']['source_address']):
+ tmp = flow_config['sflow']['source_address']
+ raise ConfigError(f'Configured "sflow source-address {tmp}" does not exist on the system!')
# check NetFlow configuration
if 'netflow' in flow_config:
@@ -212,7 +222,7 @@ def verify(flow_config):
if 'source_address' in flow_config['netflow']:
if not is_addr_assigned(flow_config['netflow']['source_address']):
tmp = flow_config['netflow']['source_address']
- print(f'Warning: Configured "netflow source-address {tmp}" does not exist on the system!')
+ raise ConfigError(f'Configured "netflow source-address {tmp}" does not exist on the system!')
# Check if engine-id compatible with selected protocol version
if 'engine_id' in flow_config['netflow']:
@@ -239,8 +249,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-pppoe.py b/src/conf_mode/interfaces-pppoe.py
index 26daa8381..e2fdc7a42 100755
--- a/src/conf_mode/interfaces-pppoe.py
+++ b/src/conf_mode/interfaces-pppoe.py
@@ -92,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
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 a9173ab87..cd46cbcb4 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -169,6 +169,16 @@ def verify(bgp):
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
diff --git a/src/conf_mode/protocols_nhrp.py b/src/conf_mode/protocols_nhrp.py
index ff8ae8eeb..56939955d 100755
--- a/src/conf_mode/protocols_nhrp.py
+++ b/src/conf_mode/protocols_nhrp.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
@@ -81,10 +81,15 @@ def verify(nhrp):
for map_name, map_conf in nhrp_conf['dynamic_map'].items():
if 'nbma_domain_name' not in map_conf:
raise ConfigError(f'nbma-domain-name missing on dynamic-map {map_name} on tunnel {name}')
+
+ if 'cisco_authentication' in nhrp_conf:
+ if len(nhrp_conf['cisco_authentication']) > 8:
+ raise ConfigError('Maximum length of the secret is 8 characters!')
+
return None
def generate(nhrp):
- render(opennhrp_conf, 'nhrp/opennhrp.conf.tmpl', nhrp)
+ render(opennhrp_conf, 'nhrp/opennhrp.conf.j2', nhrp)
return None
def apply(nhrp):
@@ -104,8 +109,8 @@ def apply(nhrp):
if rule_handle:
remove_nftables_rule('ip filter', 'VYOS_FW_OUTPUT', rule_handle)
- action = 'reload-or-restart' if nhrp and 'tunnel' in nhrp else 'stop'
- run(f'systemctl {action} opennhrp')
+ action = 'restart' if nhrp and 'tunnel' in nhrp else 'stop'
+ run(f'systemctl {action} opennhrp.service')
return None
if __name__ == '__main__':
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..daf75d740 100755
--- a/src/conf_mode/service_monitoring_telegraf.py
+++ b/src/conf_mode/service_monitoring_telegraf.py
@@ -99,6 +99,32 @@ 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
+
+ # Redefine azure group-metrics 'single-table' and 'table-per-metric'
+ if 'azure_data_explorer' in monitoring:
+ if 'single-table' in monitoring['azure_data_explorer']['group_metrics']:
+ monitoring['azure_data_explorer']['group_metrics'] = 'SingleTable'
+ else:
+ monitoring['azure_data_explorer']['group_metrics'] = 'TablePerMetric'
+ # Set azure env
+ if 'authentication' in monitoring['azure_data_explorer']:
+ auth_config = monitoring['azure_data_explorer']['authentication']
+ if {'client_id', 'client_secret', 'tenant_id'} <= set(auth_config):
+ os.environ['AZURE_CLIENT_ID'] = auth_config['client_id']
+ os.environ['AZURE_CLIENT_SECRET'] = auth_config['client_secret']
+ os.environ['AZURE_TENANT_ID'] = auth_config['tenant_id']
+
+ # Ignore default XML values if config doesn't exists
+ # Delete key from dict
+ if not conf.exists(base + ['prometheus-client']):
+ del monitoring['prometheus_client']
+
+ if not conf.exists(base + ['azure-data-explorer']):
+ del monitoring['azure_data_explorer']
+
return monitoring
def verify(monitoring):
@@ -106,13 +132,41 @@ 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 azure-data-explorer
+ if 'azure_data_explorer' in monitoring:
+ if 'authentication' not in monitoring['azure_data_explorer'] or \
+ 'client_id' not in monitoring['azure_data_explorer']['authentication'] or \
+ 'client_secret' not in monitoring['azure_data_explorer']['authentication'] or \
+ 'tenant_id' not in monitoring['azure_data_explorer']['authentication']:
+ raise ConfigError(f'Authentication "client-id, client-secret and tenant-id" are mandatory!')
+
+ if 'database' not in monitoring['azure_data_explorer']:
+ raise ConfigError(f'Monitoring "database" is mandatory!')
+
+ if 'url' not in monitoring['azure_data_explorer']:
+ raise ConfigError(f'Monitoring "url" is mandatory!')
+
+ if monitoring['azure_data_explorer']['group_metrics'] == 'SingleTable' and \
+ 'table' not in monitoring['azure_data_explorer']:
+ raise ConfigError(f'Monitoring "table" name for single-table mode 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 +199,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..5cd24db32 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
@@ -293,7 +293,15 @@ def apply(snmp):
call(f'systemctl restart {systemd_service}')
# Enable AgentX in FRR
- call('vtysh -c "configure terminal" -c "agentx" >/dev/null')
+ # This should be done for each daemon individually because common command
+ # works only if all the daemons started with SNMP support
+ frr_daemons_list = [
+ 'bgpd', 'ospf6d', 'ospfd', 'ripd', 'ripngd', 'isisd', 'ldpd', 'zebra'
+ ]
+ for frr_daemon in frr_daemons_list:
+ call(
+ f'vtysh -c "configure terminal" -d {frr_daemon} -c "agentx" >/dev/null'
+ )
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-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_frr.py b/src/conf_mode/system_frr.py
new file mode 100755
index 000000000..1af0055f6
--- /dev/null
+++ b/src/conf_mode/system_frr.py
@@ -0,0 +1,91 @@
+#!/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/>.
+
+from pathlib import Path
+from sys import exit
+
+from vyos import ConfigError
+from vyos import airbag
+from vyos.config import Config
+from vyos.logger import syslog
+from vyos.template import render_to_string
+from vyos.util import read_file, write_file, run
+airbag.enable()
+
+# path to daemons config and config status files
+config_file = '/etc/frr/daemons'
+vyos_status_file = '/tmp/vyos-config-status'
+# path to watchfrr for FRR control
+watchfrr = '/usr/lib/frr/watchfrr.sh'
+
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ base = ['system', 'frr']
+ frr_config = conf.get_config_dict(base, get_first_key=True)
+
+ return frr_config
+
+
+def verify(frr_config):
+ # Nothing to verify here
+ pass
+
+
+def generate(frr_config):
+ # read daemons config file
+ daemons_config_current = read_file(config_file)
+ # generate new config file
+ daemons_config_new = render_to_string('frr/daemons.frr.tmpl', frr_config)
+ # update configuration file if this is necessary
+ if daemons_config_new != daemons_config_current:
+ syslog.warning('FRR daemons configuration file need to be changed')
+ write_file(config_file, daemons_config_new)
+ frr_config['config_file_changed'] = True
+
+
+def apply(frr_config):
+ # check if this is initial commit during boot or intiated by CLI
+ # if the file exists, this must be CLI commit
+ commit_type_cli = Path(vyos_status_file).exists()
+ # display warning to user
+ if commit_type_cli and frr_config.get('config_file_changed'):
+ # Since FRR restart is not safe thing, better to give
+ # control over this to users
+ print('''
+ You need to reboot a router (preferred) or restart FRR
+ to apply changes in modules settings
+ ''')
+ # restart FRR automatically. DUring the initial boot this should be
+ # safe in most cases
+ if not commit_type_cli and frr_config.get('config_file_changed'):
+ syslog.warning('Restarting FRR to apply changes in modules')
+ run(f'{watchfrr} restart')
+
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
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 dc134fd1f..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']:
@@ -567,13 +567,13 @@ def generate(ipsec):
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/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/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 c55a48b3c..000000000
--- a/src/op_mode/containers_op.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-#
-# 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
-# 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 argparse
-
-from getpass import getuser
-from vyos.configquery import ConfigTreeQuery
-from vyos.base import Warning
-from vyos.util import cmd
-from subprocess import STDOUT
-
-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 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
- registry_config = '/etc/containers/registries.conf'
- if not os.path.exists(registry_config):
- Warning('No container registry configured. Please use full URL when '\
- 'adding an image. E.g. prefix with docker.io/image-name.')
- try:
- print(os.system(f'podman image pull {image}'))
- except Exception as e:
- print(f'Unable to download image "{image}". {e}')
-
- elif args.remove:
- image = args.remove
- try:
- print(os.system(f'podman image rm {image}'))
- except FileNotFoundError as e:
- print(f'Unable to delete image "{image}". {e}')
-
- 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 Exception as e:
- print(f'Unable to download image "{image}". {e}')
- else:
- parser.print_help()
- exit(1)
-
- exit(0)
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/show_uptime.py b/src/op_mode/show_uptime.py
index 1b5e33fa9..b70c60cf8 100755
--- a/src/op_mode/show_uptime.py
+++ b/src/op_mode/show_uptime.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 as
@@ -26,14 +26,17 @@ def get_uptime_seconds():
def get_load_averages():
from re import search
from vyos.util import cmd
+ from vyos.cpu import get_core_count
data = cmd("uptime")
matches = search(r"load average:\s*(?P<one>[0-9\.]+)\s*,\s*(?P<five>[0-9\.]+)\s*,\s*(?P<fifteen>[0-9\.]+)\s*", data)
+ core_count = get_core_count()
+
res = {}
- res[1] = float(matches["one"])
- res[5] = float(matches["five"])
- res[15] = float(matches["fifteen"])
+ res[1] = float(matches["one"]) / core_count
+ res[5] = float(matches["five"]) / core_count
+ res[15] = float(matches["fifteen"]) / core_count
return res
@@ -53,9 +56,9 @@ def get_formatted_output():
out = "Uptime: {}\n\n".format(data["uptime"])
avgs = data["load_average"]
out += "Load averages:\n"
- out += "1 minute: {:.02f}%\n".format(avgs[1]*100)
- out += "5 minutes: {:.02f}%\n".format(avgs[5]*100)
- out += "15 minutes: {:.02f}%\n".format(avgs[15]*100)
+ out += "1 minute: {:.01f}%\n".format(avgs[1]*100)
+ out += "5 minutes: {:.01f}%\n".format(avgs[5]*100)
+ out += "15 minutes: {:.01f}%\n".format(avgs[15]*100)
return out
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/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