summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/conf_mode/conntrack.py11
-rwxr-xr-xsrc/conf_mode/containers.py199
-rwxr-xr-xsrc/conf_mode/firewall_options.py150
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py5
-rwxr-xr-xsrc/conf_mode/interfaces-pppoe.py93
-rwxr-xr-xsrc/conf_mode/interfaces-wwan.py1
-rwxr-xr-xsrc/conf_mode/nat.py8
-rwxr-xr-xsrc/conf_mode/nat66.py21
-rwxr-xr-xsrc/conf_mode/policy-local-route.py39
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py59
-rwxr-xr-xsrc/conf_mode/protocols_isis.py10
-rwxr-xr-xsrc/conf_mode/protocols_ospf.py15
-rwxr-xr-xsrc/conf_mode/protocols_ospfv3.py4
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py24
-rwxr-xr-xsrc/conf_mode/vrf.py20
-rwxr-xr-xsrc/conf_mode/vrf_vni.py76
-rwxr-xr-xsrc/etc/ipsec.d/vti-up-down2
-rwxr-xr-xsrc/etc/ppp/ip-up.d/99-vyos-pppoe-callback59
-rw-r--r--src/etc/sysctl.d/32-vyos-podman.conf5
-rw-r--r--src/etc/udev/rules.d/90-vyos-serial.rules8
-rwxr-xr-xsrc/etc/update-motd.d/99-reboot7
-rwxr-xr-xsrc/migration-scripts/conntrack/2-to-337
-rwxr-xr-xsrc/migration-scripts/firewall/5-to-663
-rwxr-xr-xsrc/op_mode/containers_op.py49
-rwxr-xr-xsrc/op_mode/dns_forwarding_statistics.py2
-rwxr-xr-xsrc/op_mode/ikev2_profile_generator.py6
-rwxr-xr-xsrc/op_mode/pki.py15
-rwxr-xr-xsrc/op_mode/restart_frr.py2
-rwxr-xr-xsrc/op_mode/show_interfaces.py4
-rwxr-xr-xsrc/op_mode/show_ipsec_sa.py8
-rwxr-xr-xsrc/op_mode/show_nat_rules.py84
-rwxr-xr-xsrc/op_mode/show_system_integrity.py70
-rwxr-xr-xsrc/op_mode/wireguard_client.py3
-rw-r--r--src/systemd/opennhrp.service4
-rwxr-xr-xsrc/validators/bgp-large-community-list36
-rwxr-xr-xsrc/validators/bgp-route-target51
36 files changed, 746 insertions, 504 deletions
diff --git a/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py
index 4e6e39c0f..68877f794 100755
--- a/src/conf_mode/conntrack.py
+++ b/src/conf_mode/conntrack.py
@@ -97,7 +97,7 @@ def apply(conntrack):
# Depending on the enable/disable state of the ALG (Application Layer Gateway)
# modules we need to either insmod or rmmod the helpers.
for module, module_config in module_map.items():
- if dict_search(f'modules.{module}.disable', conntrack) != None:
+ if dict_search(f'modules.{module}', conntrack) is None:
if 'ko' in module_config:
for mod in module_config['ko']:
# Only remove the module if it's loaded
@@ -105,8 +105,9 @@ def apply(conntrack):
cmd(f'rmmod {mod}')
if 'iptables' in module_config:
for rule in module_config['iptables']:
- print(f'iptables --delete {rule}')
- cmd(f'iptables --delete {rule}')
+ # Only install iptables rule if it does not exist
+ tmp = run(f'iptables --check {rule}')
+ if tmp == 0: cmd(f'iptables --delete {rule}')
else:
if 'ko' in module_config:
for mod in module_config['ko']:
@@ -115,9 +116,7 @@ def apply(conntrack):
for rule in module_config['iptables']:
# Only install iptables rule if it does not exist
tmp = run(f'iptables --check {rule}')
- if tmp > 0:
- cmd(f'iptables --insert {rule}')
-
+ if tmp > 0: cmd(f'iptables --insert {rule}')
if process_named_running('conntrackd'):
# Reload conntrack-sync daemon to fetch new sysctl values
diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py
index 21b47f42a..32320a4b2 100755
--- a/src/conf_mode/containers.py
+++ b/src/conf_mode/containers.py
@@ -23,8 +23,11 @@ from ipaddress import ip_network
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 popen
+from vyos.util import run
+from vyos.util import read_file
+from vyos.util import write_file
from vyos.template import render
from vyos.template import is_ipv4
from vyos.template import is_ipv6
@@ -41,27 +44,7 @@ def _cmd(command):
print(command)
return cmd(command)
-# Container management functions
-def container_exists(name):
- '''
- https://docs.podman.io/en/latest/_static/api.html#operation/ContainerExistsLibpod
- Check if container exists. Response codes.
- 204 - container exists
- 404 - no such container
- '''
- tmp = _cmd(f"curl --unix-socket /run/podman/podman.sock 'http://d/v3.0.0/libpod/containers/{name}/exists'")
- # If container exists it return status code "0" - code can not be displayed
- return (tmp == "")
-
-def container_status(name):
- '''
- https://docs.podman.io/en/latest/_static/api.html#operation/ContainerInspectLibpod
- '''
- tmp = _cmd(f"curl --unix-socket /run/podman/podman.sock 'http://d/v3.0.0/libpod/containers/{name}/json'")
- data = json.loads(tmp)
- return data['State']['Status']
-
-def ctnr_network_exists(name):
+def network_exists(name):
# Check explicit name for network, returns True if network exists
c = _cmd(f'podman network ls --quiet --filter name=^{name}$')
return bool(c)
@@ -79,11 +62,20 @@ def get_config(config=None):
# We have gathered the dict representation of the CLI, but there are default
# options which we need to update into the dictionary retrived.
default_values = defaults(base)
+ # container base default values can not be merged here - remove and add them later
+ if 'name' in default_values:
+ del default_values['name']
container = dict_merge(default_values, container)
+ # Merge per-container default values
+ if 'name' in container:
+ default_values = defaults(base + ['name'])
+ for name in container['name']:
+ container['name'][name] = dict_merge(default_values, container['name'][name])
+
# Delete container network, delete containers
tmp = node_changed(conf, ['container', 'network'])
- if tmp: container.update({'net_remove' : tmp})
+ if tmp: container.update({'network_remove' : tmp})
tmp = node_changed(conf, ['container', 'name'])
if tmp: container.update({'container_remove' : tmp})
@@ -102,7 +94,6 @@ def verify(container):
if len(container_config['network']) > 1:
raise ConfigError(f'Only one network can be specified for container "{name}"!')
-
# Check if the specified container network exists
network_name = list(container_config['network'])[0]
if network_name not in container['network']:
@@ -125,8 +116,25 @@ def verify(container):
# We can not use the first IP address of a network prefix as this is used by podman
if ip_address(address) == ip_network(network)[1]:
- raise ConfigError(f'Address "{address}" reserved for the container engine!')
+ raise ConfigError(f'IP address "{address}" can not be used for a container, '\
+ 'reserved for the container engine!')
+
+ if 'environment' in container_config:
+ for var, cfg in container_config['environment'].items():
+ if 'value' not in cfg:
+ raise ConfigError(f'Environment variable {var} has no value assigned!')
+ if 'volume' in container_config:
+ for volume, volume_config in container_config['volume'].items():
+ if 'source' not in volume_config:
+ raise ConfigError(f'Volume "{volume}" has no source path configured!')
+
+ if 'destination' not in volume_config:
+ raise ConfigError(f'Volume "{volume}" has no destination path configured!')
+
+ source = volume_config['source']
+ 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:
@@ -142,9 +150,9 @@ def verify(container):
# Add new network
if 'network' in container:
- v4_prefix = 0
- v6_prefix = 0
for network, network_config in container['network'].items():
+ v4_prefix = 0
+ v6_prefix = 0
# If ipv4-prefix not defined for user-defined network
if 'prefix' not in network_config:
raise ConfigError(f'prefix for network "{net}" must be defined!')
@@ -160,8 +168,8 @@ def verify(container):
# A network attached to a container can not be deleted
- if {'net_remove', 'name'} <= set(container):
- for network in container['net_remove']:
+ if {'network_remove', 'name'} <= set(container):
+ for network in container['network_remove']:
for container, container_config in container['name'].items():
if 'network' in container_config and network in container_config['network']:
raise ConfigError(f'Can not remove network "{network}", used by container "{container}"!')
@@ -183,20 +191,19 @@ def apply(container):
# Option "--force" allows to delete containers with any status
if 'container_remove' in container:
for name in container['container_remove']:
- if container_status(name) == 'running':
- _cmd(f'podman stop {name}')
- _cmd(f'podman rm --force {name}')
+ call(f'podman stop {name}')
+ call(f'podman rm --force {name}')
# Delete old networks if needed
- if 'net_remove' in container:
- for network in container['net_remove']:
- _cmd(f'podman network rm {network}')
+ if 'network_remove' in container:
+ for network in container['network_remove']:
+ call(f'podman network rm --force {network}')
# Add network
if 'network' in container:
for network, network_config in container['network'].items():
# Check if the network has already been created
- if not ctnr_network_exists(network) and 'prefix' in network_config:
+ if not network_exists(network) and 'prefix' in network_config:
tmp = f'podman network create {network}'
# we can not use list comprehension here as the --ipv6 option
# must immediately follow the specified subnet!!!
@@ -206,56 +213,82 @@ def apply(container):
tmp += ' --ipv6'
_cmd(tmp)
+ # Disable masquerading and use traditional bridging so VyOS
+ # can control firewalling/NAT by the real VyOS CLI
+ cni_network_config = f'/etc/cni/net.d/{network}.conflist'
+ tmp = read_file(cni_network_config)
+ config = json.loads(tmp)
+ if 'plugins' in config:
+ for count in range(0, len(config['plugins'])):
+ if 'ipMasq' in config['plugins'][count]:
+ config['plugins'][count]['ipMasq'] = False
+ if 'hairpinMode' in config['plugins'][count]:
+ config['plugins'][count]['hairpinMode'] = False
+
+ write_file(cni_network_config, json.dumps(config, indent=4))
+
# Add container
if 'name' in container:
for name, container_config in container['name'].items():
- # Check if the container has already been created
- if not container_exists(name):
- image = container_config['image']
- # Currently the best way to run a command and immediately print stdout
- print(os.system(f'podman pull {image}'))
-
- # Check/set environment options "-e foo=bar"
- env_opt = ''
- if 'environment' in container_config:
- env_opt = '-e '
- env_opt += " -e ".join(f"{k}={v['value']}" for k, v in container_config['environment'].items())
-
- # Publish ports
- port = ''
- if 'port' in container_config:
- protocol = ''
- for portmap in container_config['port']:
- if 'protocol' in container_config['port'][portmap]:
- protocol = container_config['port'][portmap]['protocol']
- protocol = f'/{protocol}'
- else:
- protocol = '/tcp'
- sport = container_config['port'][portmap]['source']
- dport = container_config['port'][portmap]['destination']
- port += f' -p {sport}:{dport}{protocol}'
-
- # Bind volume
- volume = ''
- if 'volume' in container_config:
- for vol in container_config['volume']:
- svol = container_config['volume'][vol]['source']
- dvol = container_config['volume'][vol]['destination']
- volume += f' -v {svol}:{dvol}'
-
- if 'allow_host_networks' in container_config:
- _cmd(f'podman run -dit --name {name} --net host {port} {volume} {env_opt} {image}')
- else:
- for network in container_config['network']:
- ipparam = ''
- if 'address' in container_config['network'][network]:
- ipparam = '--ip ' + container_config['network'][network]['address']
- _cmd(f'podman run --name {name} -dit --net {network} {ipparam} {port} {volume} {env_opt} {image}')
-
- # Else container is already created. Just start it.
- # It's needed after reboot.
- elif container_status(name) != 'running':
- _cmd(f'podman start {name}')
+ image = container_config['image']
+
+ if 'disable' in container_config:
+ # check if there is a container by that name running
+ tmp = _cmd('podman ps -a --format "{{.Names}}"')
+ if name in tmp:
+ _cmd(f'podman stop {name}')
+ _cmd(f'podman rm --force {name}')
+ continue
+
+ 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}'))
+
+ # Check/set environment options "-e foo=bar"
+ env_opt = ''
+ if 'environment' in container_config:
+ for k, v in container_config['environment'].items():
+ env_opt += f" -e \"{k}={v['value']}\""
+
+ # Publish ports
+ port = ''
+ if 'port' in container_config:
+ protocol = ''
+ for portmap in container_config['port']:
+ if 'protocol' in container_config['port'][portmap]:
+ protocol = container_config['port'][portmap]['protocol']
+ protocol = f'/{protocol}'
+ else:
+ protocol = '/tcp'
+ sport = container_config['port'][portmap]['source']
+ dport = container_config['port'][portmap]['destination']
+ port += f' -p {sport}:{dport}{protocol}'
+
+ # Bind volume
+ volume = ''
+ if 'volume' in container_config:
+ for vol, vol_config in container_config['volume'].items():
+ svol = vol_config['source']
+ dvol = vol_config['destination']
+ volume += f' -v {svol}:{dvol}'
+
+ container_base_cmd = f'podman run --detach --interactive --tty --replace ' \
+ f'--memory {memory}m --memory-swap 0 --restart {restart} ' \
+ f'--name {name} {port} {volume} {env_opt}'
+ if 'allow_host_networks' in container_config:
+ _cmd(f'{container_base_cmd} --net host {image}')
+ else:
+ for network in container_config['network']:
+ ipparam = ''
+ if 'address' in container_config['network'][network]:
+ ipparam = '--ip ' + container_config['network'][network]['address']
+ _cmd(f'{container_base_cmd} --net {network} {ipparam} {image}')
return None
diff --git a/src/conf_mode/firewall_options.py b/src/conf_mode/firewall_options.py
deleted file mode 100755
index 67bf5d0e2..000000000
--- a/src/conf_mode/firewall_options.py
+++ /dev/null
@@ -1,150 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2018 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 sys
-import os
-import copy
-
-from vyos.config import Config
-from vyos import ConfigError
-from vyos.util import call
-
-from vyos import airbag
-airbag.enable()
-
-default_config_data = {
- 'intf_opts': [],
- 'new_chain4': False,
- 'new_chain6': False
-}
-
-def get_config(config=None):
- opts = copy.deepcopy(default_config_data)
- if config:
- conf = config
- else:
- conf = Config()
- if not conf.exists('firewall options'):
- # bail out early
- return opts
- else:
- conf.set_level('firewall options')
-
- # Parse configuration of each individual instance
- if conf.exists('interface'):
- for intf in conf.list_nodes('interface'):
- conf.set_level('firewall options interface {0}'.format(intf))
- config = {
- 'intf': intf,
- 'disabled': False,
- 'mss4': '',
- 'mss6': ''
- }
-
- # Check if individual option is disabled
- if conf.exists('disable'):
- config['disabled'] = True
-
- #
- # Get MSS value IPv4
- #
- if conf.exists('adjust-mss'):
- config['mss4'] = conf.return_value('adjust-mss')
-
- # We need a marker that a new iptables chain needs to be generated
- if not opts['new_chain4']:
- opts['new_chain4'] = True
-
- #
- # Get MSS value IPv6
- #
- if conf.exists('adjust-mss6'):
- config['mss6'] = conf.return_value('adjust-mss6')
-
- # We need a marker that a new ip6tables chain needs to be generated
- if not opts['new_chain6']:
- opts['new_chain6'] = True
-
- # Append interface options to global list
- opts['intf_opts'].append(config)
-
- return opts
-
-def verify(tcp):
- # syntax verification is done via cli
- return None
-
-def apply(tcp):
- target = 'VYOS_FW_OPTIONS'
-
- # always cleanup iptables
- call('iptables --table mangle --delete FORWARD --jump {} >&/dev/null'.format(target))
- call('iptables --table mangle --flush {} >&/dev/null'.format(target))
- call('iptables --table mangle --delete-chain {} >&/dev/null'.format(target))
-
- # always cleanup ip6tables
- call('ip6tables --table mangle --delete FORWARD --jump {} >&/dev/null'.format(target))
- call('ip6tables --table mangle --flush {} >&/dev/null'.format(target))
- call('ip6tables --table mangle --delete-chain {} >&/dev/null'.format(target))
-
- # Setup new iptables rules
- if tcp['new_chain4']:
- call('iptables --table mangle --new-chain {} >&/dev/null'.format(target))
- call('iptables --table mangle --append FORWARD --jump {} >&/dev/null'.format(target))
-
- for opts in tcp['intf_opts']:
- intf = opts['intf']
- mss = opts['mss4']
-
- # Check if this rule iis disabled
- if opts['disabled']:
- continue
-
- # adjust TCP MSS per interface
- if mss:
- call('iptables --table mangle --append {} --out-interface {} --protocol tcp '
- '--tcp-flags SYN,RST SYN --jump TCPMSS --set-mss {} >&/dev/null'.format(target, intf, mss))
-
- # Setup new ip6tables rules
- if tcp['new_chain6']:
- call('ip6tables --table mangle --new-chain {} >&/dev/null'.format(target))
- call('ip6tables --table mangle --append FORWARD --jump {} >&/dev/null'.format(target))
-
- for opts in tcp['intf_opts']:
- intf = opts['intf']
- mss = opts['mss6']
-
- # Check if this rule iis disabled
- if opts['disabled']:
- continue
-
- # adjust TCP MSS per interface
- if mss:
- call('ip6tables --table mangle --append {} --out-interface {} --protocol tcp '
- '--tcp-flags SYN,RST SYN --jump TCPMSS --set-mss {} >&/dev/null'.format(target, intf, mss))
-
- return None
-
-if __name__ == '__main__':
-
- try:
- c = get_config()
- verify(c)
- apply(c)
- except ConfigError as e:
- print(e)
- sys.exit(1)
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index 74e29ed82..6be4e918b 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019-2020 VyOS maintainers and contributors
+# Copyright (C) 2019-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
@@ -273,6 +273,9 @@ def verify(openvpn):
if openvpn['protocol'] == 'tcp-active':
raise ConfigError('Protocol "tcp-active" is not valid in server mode')
+ if dict_search('authentication.username', openvpn) or dict_search('authentication.password', openvpn):
+ raise ConfigError('Cannot specify "authentication" in server mode')
+
if 'remote_port' in openvpn:
raise ConfigError('Cannot specify "remote-port" in server mode')
diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py
index 6c4c6c95b..584adc75e 100755
--- a/src/conf_mode/interfaces-pppoe.py
+++ b/src/conf_mode/interfaces-pppoe.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019-2020 VyOS maintainers and contributors
+# Copyright (C) 2019-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
@@ -22,12 +22,16 @@ 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.configverify import verify_authentication
from vyos.configverify import verify_source_interface
+from vyos.configverify import verify_interface_exists
from vyos.configverify import verify_vrf
from vyos.configverify import verify_mtu_ipv6
+from vyos.ifconfig import PPPoEIf
from vyos.template import render
from vyos.util import call
+from vyos.util import is_systemd_service_running
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -44,6 +48,32 @@ def get_config(config=None):
base = ['interfaces', 'pppoe']
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': {}})
+
return pppoe
def verify(pppoe):
@@ -66,57 +96,42 @@ def generate(pppoe):
# rendered into
ifname = pppoe['ifname']
config_pppoe = f'/etc/ppp/peers/{ifname}'
- script_pppoe_pre_up = f'/etc/ppp/ip-pre-up.d/1000-vyos-pppoe-{ifname}'
- script_pppoe_ip_up = f'/etc/ppp/ip-up.d/1000-vyos-pppoe-{ifname}'
- script_pppoe_ip_down = f'/etc/ppp/ip-down.d/1000-vyos-pppoe-{ifname}'
- script_pppoe_ipv6_up = f'/etc/ppp/ipv6-up.d/1000-vyos-pppoe-{ifname}'
- config_wide_dhcp6c = f'/run/dhcp6c/dhcp6c.{ifname}.conf'
-
- config_files = [config_pppoe, script_pppoe_pre_up, script_pppoe_ip_up,
- script_pppoe_ip_down, script_pppoe_ipv6_up, config_wide_dhcp6c]
if 'deleted' in pppoe or 'disable' in pppoe:
- # stop DHCPv6-PD client
- call(f'systemctl stop dhcp6c@{ifname}.service')
- # Hang-up PPPoE connection
- call(f'systemctl stop ppp@{ifname}.service')
-
- # Delete PPP configuration files
- for file in config_files:
- if os.path.exists(file):
- os.unlink(file)
+ if os.path.exists(config_pppoe):
+ os.unlink(config_pppoe)
return None
# Create PPP configuration files
- render(config_pppoe, 'pppoe/peer.tmpl', pppoe, permission=0o755)
-
- # Create script for ip-pre-up.d
- render(script_pppoe_pre_up, 'pppoe/ip-pre-up.script.tmpl', pppoe,
- permission=0o755)
- # Create script for ip-up.d
- render(script_pppoe_ip_up, 'pppoe/ip-up.script.tmpl', pppoe,
- permission=0o755)
- # Create script for ip-down.d
- render(script_pppoe_ip_down, 'pppoe/ip-down.script.tmpl', pppoe,
- permission=0o755)
- # Create script for ipv6-up.d
- render(script_pppoe_ipv6_up, 'pppoe/ipv6-up.script.tmpl', pppoe,
- permission=0o755)
-
- if 'dhcpv6_options' in pppoe and 'pd' in pppoe['dhcpv6_options']:
- # ipv6.tmpl relies on ifname - this should be made consitent in the
- # future better then double key-ing the same value
- render(config_wide_dhcp6c, 'dhcp-client/ipv6.tmpl', pppoe)
+ render(config_pppoe, 'pppoe/peer.tmpl', pppoe, permission=0o640)
return None
def apply(pppoe):
+ ifname = pppoe['ifname']
if 'deleted' in pppoe or 'disable' in pppoe:
- call('systemctl stop ppp@{ifname}.service'.format(**pppoe))
+ if os.path.isdir(f'/sys/class/net/{ifname}'):
+ p = PPPoEIf(ifname)
+ p.remove()
+ call(f'systemctl stop ppp@{ifname}.service')
return None
- call('systemctl restart ppp@{ifname}.service'.format(**pppoe))
+ # reconnect should only be necessary when certain config options change,
+ # like ACS name, authentication, no-peer-dns, source-interface
+ if ((not is_systemd_service_running(f'ppp@{ifname}.service')) or
+ 'shutdown_required' in pppoe):
+
+ # cleanup system (e.g. FRR routes first)
+ if os.path.isdir(f'/sys/class/net/{ifname}'):
+ p = PPPoEIf(ifname)
+ p.remove()
+
+ call(f'systemctl restart ppp@{ifname}.service')
+ else:
+ if os.path.isdir(f'/sys/class/net/{ifname}'):
+ p = PPPoEIf(ifname)
+ p.update(pppoe)
return None
diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py
index 31c599145..faa5eb628 100755
--- a/src/conf_mode/interfaces-wwan.py
+++ b/src/conf_mode/interfaces-wwan.py
@@ -26,7 +26,6 @@ from vyos.configverify import verify_vrf
from vyos.ifconfig import WWANIf
from vyos.util import cmd
from vyos.util import dict_search
-from vyos.template import render
from vyos import ConfigError
from vyos import airbag
airbag.enable()
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index dae958774..59939d0fb 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -139,12 +139,10 @@ def verify(nat):
for rule, config in dict_search('source.rule', nat).items():
err_msg = f'Source NAT configuration error in rule {rule}:'
if 'outbound_interface' not in config:
- raise ConfigError(f'{err_msg}\n' \
- 'outbound-interface not specified')
- else:
- if config['outbound_interface'] not in 'any' and config['outbound_interface'] not in interfaces():
- print(f'WARNING: rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')
+ raise ConfigError(f'{err_msg} outbound-interface not specified')
+ if config['outbound_interface'] not in 'any' and config['outbound_interface'] not in interfaces():
+ print(f'WARNING: rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')
addr = dict_search('translation.address', config)
if addr != None:
diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py
index e2bd6417d..f8bc073bb 100755
--- a/src/conf_mode/nat66.py
+++ b/src/conf_mode/nat66.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2020-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
@@ -55,7 +55,7 @@ def get_config(config=None):
conf = config
else:
conf = Config()
-
+
base = ['nat66']
nat = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
@@ -90,7 +90,7 @@ def get_config(config=None):
# be done only once
if not get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK'):
nat['helper_functions'] = 'add'
-
+
# Retrieve current table handler positions
nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_IGNORE')
nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_PREROUTING_HOOK')
@@ -109,21 +109,22 @@ def verify(nat):
if 'helper_functions' in nat and nat['helper_functions'] != 'has':
if not (nat['pre_ct_conntrack'] or nat['out_ct_conntrack']):
raise Exception('could not determine nftable ruleset handlers')
-
+
if dict_search('source.rule', nat):
for rule, config in dict_search('source.rule', nat).items():
err_msg = f'Source NAT66 configuration error in rule {rule}:'
if 'outbound_interface' not in config:
- raise ConfigError(f'{err_msg}\n' \
- 'outbound-interface not specified')
- else:
- if config['outbound_interface'] not in interfaces():
- print(f'WARNING: rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')
+ raise ConfigError(f'{err_msg} outbound-interface not specified')
+
+ if config['outbound_interface'] not in interfaces():
+ print(f'WARNING: rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')
addr = dict_search('translation.address', config)
if addr != None:
if addr != 'masquerade' and not is_ipv6(addr):
raise ConfigError(f'Warning: IPv6 address {addr} is not a valid address')
+ else:
+ raise ConfigError(f'{err_msg} translation address not specified')
prefix = dict_search('source.prefix', config)
if prefix != None:
@@ -145,7 +146,7 @@ def verify(nat):
def generate(nat):
render(iptables_nat_config, 'firewall/nftables-nat66.tmpl', nat, permission=0o755)
- render(ndppd_config, 'proxy-ndp/ndppd.conf.tmpl', nat, permission=0o755)
+ render(ndppd_config, 'ndppd/ndppd.conf.tmpl', nat, permission=0o755)
return None
def apply(nat):
diff --git a/src/conf_mode/policy-local-route.py b/src/conf_mode/policy-local-route.py
index 013f22665..539189442 100755
--- a/src/conf_mode/policy-local-route.py
+++ b/src/conf_mode/policy-local-route.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2020-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
@@ -44,17 +44,26 @@ def get_config(config=None):
if tmp:
for rule in (tmp or []):
src = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'source'])
+ fwmk = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'fwmark'])
if src:
dict = dict_merge({'rule_remove' : {rule : {'source' : src}}}, dict)
pbr.update(dict)
+ if fwmk:
+ dict = dict_merge({'rule_remove' : {rule : {'fwmark' : fwmk}}}, dict)
+ pbr.update(dict)
# delete policy local-route rule x source x.x.x.x
+ # delete policy local-route rule x fwmark x
if 'rule' in pbr:
for rule in pbr['rule']:
src = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'source'])
+ fwmk = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'fwmark'])
if src:
dict = dict_merge({'rule_remove' : {rule : {'source' : src}}}, dict)
pbr.update(dict)
+ if fwmk:
+ dict = dict_merge({'rule_remove' : {rule : {'fwmark' : fwmk}}}, dict)
+ pbr.update(dict)
return pbr
@@ -65,8 +74,8 @@ def verify(pbr):
if 'rule' in pbr:
for rule in pbr['rule']:
- if 'source' not in pbr['rule'][rule]:
- raise ConfigError('Source address required!')
+ if 'source' not in pbr['rule'][rule] and 'fwmark' not in pbr['rule'][rule]:
+ raise ConfigError('Source address or fwmark is required!')
else:
if 'set' not in pbr['rule'][rule] or 'table' not in pbr['rule'][rule]['set']:
raise ConfigError('Table set is required!')
@@ -86,16 +95,34 @@ def apply(pbr):
# Delete old rule if needed
if 'rule_remove' in pbr:
for rule in pbr['rule_remove']:
- for src in pbr['rule_remove'][rule]['source']:
- call(f'ip rule del prio {rule} from {src}')
+ if 'source' in pbr['rule_remove'][rule]:
+ for src in pbr['rule_remove'][rule]['source']:
+ call(f'ip rule del prio {rule} from {src}')
+ if 'fwmark' in pbr['rule_remove'][rule]:
+ for fwmk in pbr['rule_remove'][rule]['fwmark']:
+ call(f'ip rule del prio {rule} from all fwmark {fwmk}')
# Generate new config
if 'rule' in pbr:
for rule in pbr['rule']:
table = pbr['rule'][rule]['set']['table']
- if pbr['rule'][rule]['source']:
+ # Only source in the rule
+ # set policy local-route rule 100 source '203.0.113.1'
+ if 'source' in pbr['rule'][rule] and not 'fwmark' in pbr['rule'][rule]:
for src in pbr['rule'][rule]['source']:
call(f'ip rule add prio {rule} from {src} lookup {table}')
+ # Only fwmark in the rule
+ # set policy local-route rule 101 fwmark '23'
+ if 'fwmark' in pbr['rule'][rule] and not 'source' in pbr['rule'][rule]:
+ fwmk = pbr['rule'][rule]['fwmark']
+ call(f'ip rule add prio {rule} from all fwmark {fwmk} lookup {table}')
+ # Source and fwmark in the rule
+ # set policy local-route rule 100 source '203.0.113.1'
+ # set policy local-route rule 100 fwmark '23'
+ if 'source' in pbr['rule'][rule] and 'fwmark' in pbr['rule'][rule]:
+ fwmk = pbr['rule'][rule]['fwmark']
+ for src in pbr['rule'][rule]['source']:
+ call(f'ip rule add prio {rule} from {src} fwmark {fwmk} lookup {table}')
return None
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index 9ecfd07fe..7d05eed9f 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -23,6 +23,7 @@ from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configverify import verify_prefix_list
from vyos.configverify import verify_route_map
+from vyos.configverify import verify_vrf
from vyos.template import is_ip
from vyos.template import is_interface
from vyos.template import render_to_string
@@ -221,27 +222,47 @@ def verify(bgp):
raise ConfigError(f'Peer-group "{peer_group}" requires remote-as to be set!')
# Throw an error if the global administrative distance parameters aren't all filled out.
- if dict_search('parameters.distance', bgp) == None:
- pass
- else:
- if dict_search('parameters.distance.global', bgp):
- for key in ['external', 'internal', 'local']:
- if dict_search(f'parameters.distance.global.{key}', bgp) == None:
- raise ConfigError('Missing mandatory configuration option for '\
- f'global administrative distance {key}!')
-
- # Throw an error if the address family specific administrative distance parameters aren't all filled out.
- if dict_search('address_family', bgp) == None:
- pass
- else:
- for address_family_name in dict_search('address_family', bgp):
- if dict_search(f'address_family.{address_family_name}.distance', bgp) == None:
- pass
- else:
+ if dict_search('parameters.distance.global', bgp) != None:
+ for key in ['external', 'internal', 'local']:
+ if dict_search(f'parameters.distance.global.{key}', bgp) == None:
+ raise ConfigError('Missing mandatory configuration option for '\
+ f'global administrative distance {key}!')
+
+ # Address Family specific validation
+ if 'address_family' in bgp:
+ for afi, afi_config in bgp['address_family'].items():
+ if 'distance' in afi_config:
+ # Throw an error if the address family specific administrative
+ # distance parameters aren't all filled out.
for key in ['external', 'internal', 'local']:
- if dict_search(f'address_family.{address_family_name}.distance.{key}', bgp) == None:
+ if key not in afi_config['distance']:
raise ConfigError('Missing mandatory configuration option for '\
- f'{address_family_name} administrative distance {key}!')
+ f'{afi} administrative distance {key}!')
+
+ if afi in ['ipv4_unicast', 'ipv6_unicast']:
+ if 'import' in afi_config and 'vrf' in afi_config['import']:
+ # Check if VRF exists
+ verify_vrf(afi_config['import']['vrf'])
+
+ # FRR error: please unconfigure vpn to vrf commands before
+ # using import vrf commands
+ if 'vpn' in afi_config['import'] or dict_search('export.vpn', afi_config) != None:
+ raise ConfigError('Please unconfigure VPN to VRF commands before '\
+ 'using "import vrf" commands!')
+
+ # Verify that the export/import route-maps do exist
+ for export_import in ['export', 'import']:
+ tmp = dict_search(f'route_map.vpn.{export_import}', afi_config)
+ if tmp: verify_route_map(tmp, bgp)
+
+ if afi in ['l2vpn_evpn'] and 'vrf' not in bgp:
+ # Some L2VPN EVPN AFI options are only supported under VRF
+ if 'vni' in afi_config:
+ for vni, vni_config in afi_config['vni'].items():
+ if 'rd' in vni_config:
+ raise ConfigError('VNI route-distinguisher is only supported under EVPN VRF')
+ if 'route_target' in vni_config:
+ raise ConfigError('VNI route-target is only supported under EVPN VRF')
return None
diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py
index d4c82249b..4cf0312e9 100755
--- a/src/conf_mode/protocols_isis.py
+++ b/src/conf_mode/protocols_isis.py
@@ -113,9 +113,13 @@ def verify(isis):
# Interface MTU must be >= configured lsp-mtu
mtu = Interface(interface).get_mtu()
area_mtu = isis['lsp_mtu']
- if mtu < int(area_mtu):
- raise ConfigError(f'Interface {interface} has MTU {mtu}, minimum ' \
- f'area MTU is {area_mtu}!')
+ # Recommended maximum PDU size = interface MTU - 3 bytes
+ recom_area_mtu = mtu - 3
+ if mtu < int(area_mtu) or int(area_mtu) > recom_area_mtu:
+ raise ConfigError(f'Interface {interface} has MTU {mtu}, ' \
+ f'current area MTU is {area_mtu}! \n' \
+ f'Recommended area lsp-mtu {recom_area_mtu} or less ' \
+ '(calculated on MTU size).')
if 'vrf' in isis:
# If interface specific options are set, we must ensure that the
diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py
index 78c1c82bd..06a29106d 100755
--- a/src/conf_mode/protocols_ospf.py
+++ b/src/conf_mode/protocols_ospf.py
@@ -149,14 +149,23 @@ def verify(ospf):
if route_map_name: verify_route_map(route_map_name, ospf)
if 'interface' in ospf:
- for interface in ospf['interface']:
+ for interface, interface_config in ospf['interface'].items():
verify_interface_exists(interface)
# One can not use dead-interval and hello-multiplier at the same
# time. FRR will only activate the last option set via CLI.
- if {'hello_multiplier', 'dead_interval'} <= set(ospf['interface'][interface]):
+ if {'hello_multiplier', 'dead_interval'} <= set(interface_config):
raise ConfigError(f'Can not use hello-multiplier and dead-interval ' \
f'concurrently for {interface}!')
+ # One can not use the "network <prefix> area <id>" command and an
+ # per interface area assignment at the same time. FRR will error
+ # out using: "Please remove all network commands first."
+ if 'area' in ospf and 'area' in interface_config:
+ for area, area_config in ospf['area'].items():
+ if 'network' in area_config:
+ raise ConfigError('Can not use OSPF interface area and area ' \
+ 'network configuration at the same time!')
+
if 'vrf' in ospf:
# If interface specific options are set, we must ensure that the
# interface is bound to our requesting VRF. Due to the VyOS
@@ -177,7 +186,7 @@ def generate(ospf):
ospf['protocol'] = 'ospf' # required for frr/vrf.route-map.frr.tmpl
ospf['frr_zebra_config'] = render_to_string('frr/vrf.route-map.frr.tmpl', ospf)
- ospf['frr_ospfd_config'] = render_to_string('frr/ospf.frr.tmpl', ospf)
+ ospf['frr_ospfd_config'] = render_to_string('frr/ospfd.frr.tmpl', ospf)
return None
def apply(ospf):
diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py
index fef0f509b..536ffa690 100755
--- a/src/conf_mode/protocols_ospfv3.py
+++ b/src/conf_mode/protocols_ospfv3.py
@@ -65,7 +65,7 @@ def verify(ospfv3):
if 'ifmtu' in if_config:
mtu = Interface(ifname).get_mtu()
if int(if_config['ifmtu']) > int(mtu):
- raise ConfigError(f'OSPFv3 ifmtu cannot go beyond physical MTU of "{mtu}"')
+ raise ConfigError(f'OSPFv3 ifmtu can not exceed physical MTU of "{mtu}"')
return None
@@ -74,7 +74,7 @@ def generate(ospfv3):
ospfv3['new_frr_config'] = ''
return None
- ospfv3['new_frr_config'] = render_to_string('frr/ospfv3.frr.tmpl', ospfv3)
+ ospfv3['new_frr_config'] = render_to_string('frr/ospf6d.frr.tmpl', ospfv3)
return None
def apply(ospfv3):
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index d3065fc47..ff6090e22 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -286,20 +286,34 @@ def verify(ipsec):
if 'pre_shared_secret' not in ra_conf['authentication']:
raise ConfigError(f"Missing pre-shared-key on {name} remote-access config")
+ if 'client_mode' not in ra_conf['authentication']:
+ raise ConfigError('Client authentication method is required!')
- if 'client_mode' in ra_conf['authentication']:
- if ra_conf['authentication']['client_mode'] == 'eap-radius':
- if 'radius' not in ipsec['remote_access'] or 'server' not in ipsec['remote_access']['radius'] or len(ipsec['remote_access']['radius']['server']) == 0:
- raise ConfigError('RADIUS authentication requires at least one server')
+ if dict_search('authentication.client_mode', ra_conf) == 'eap-radius':
+ if dict_search('remote_access.radius.server', ipsec) == None:
+ raise ConfigError('RADIUS authentication requires at least one server')
if 'pool' in ra_conf:
+ if {'dhcp', 'radius'} <= set(ra_conf['pool']):
+ raise ConfigError(f'Can not use both DHCP and RADIUS for address allocation '\
+ f'at the same time for "{name}"!')
+
if 'dhcp' in ra_conf['pool'] and len(ra_conf['pool']) > 1:
- raise ConfigError(f'Can not use both DHCP and a predefined address pool for "{name}"!')
+ raise ConfigError(f'Can not use DHCP and a predefined address pool for "{name}"!')
+
+ if 'radius' in ra_conf['pool'] and len(ra_conf['pool']) > 1:
+ raise ConfigError(f'Can not use RADIUS and a predefined address pool for "{name}"!')
for pool in ra_conf['pool']:
if pool == 'dhcp':
if dict_search('remote_access.dhcp.server', ipsec) == None:
raise ConfigError('IPSec DHCP server is not configured!')
+ elif pool == 'radius':
+ if dict_search('remote_access.radius.server', ipsec) == None:
+ raise ConfigError('IPSec RADIUS server is not configured!')
+
+ if dict_search('authentication.client_mode', ra_conf) != 'eap-radius':
+ raise ConfigError('RADIUS IP pool requires eap-radius client authentication!')
elif 'pool' not in ipsec['remote_access'] or pool not in ipsec['remote_access']['pool']:
raise ConfigError(f'Requested pool "{pool}" does not exist!')
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index c1cfc1dcb..919083ac4 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -24,7 +24,6 @@ from vyos.config import Config
from vyos.configdict import node_changed
from vyos.ifconfig import Interface
from vyos.template import render
-from vyos.template import render_to_string
from vyos.util import call
from vyos.util import cmd
from vyos.util import dict_search
@@ -32,12 +31,9 @@ from vyos.util import get_interface_config
from vyos.util import popen
from vyos.util import run
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
-frr_daemon = 'zebra'
-
config_file = r'/etc/iproute2/rt_tables.d/vyos-vrf.conf'
def list_rules():
@@ -131,7 +127,6 @@ def verify(vrf):
def generate(vrf):
render(config_file, 'vrf/vrf.conf.tmpl', vrf)
- vrf['new_frr_config'] = render_to_string('frr/vrf.frr.tmpl', vrf)
# Render nftables zones config
vrf['nft_vrf_zones'] = NamedTemporaryFile().name
render(vrf['nft_vrf_zones'], 'firewall/nftables-vrf-zones.tmpl', vrf)
@@ -242,21 +237,6 @@ def apply(vrf):
if tmp == 0:
cmd('nft delete table inet vrf_zones')
- # T3694: Somehow we hit a priority inversion here as we need to remove the
- # VRF assigned VNI before we can remove a BGP bound VRF instance. Maybe
- # move this to an individual helper script that set's up the VNI for the
- # given VRF after any routing protocol.
- #
- # # add configuration to FRR
- # frr_cfg = frr.FRRConfig()
- # frr_cfg.load_configuration(frr_daemon)
- # frr_cfg.modify_section(f'^vrf [a-zA-Z-]*$', '')
- # frr_cfg.add_before(r'(interface .*|line vty)', vrf['new_frr_config'])
- # frr_cfg.commit_configuration(frr_daemon)
- #
- # # Save configuration to /run/frr/config/frr.conf
- # frr.save_configuration()
-
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/vrf_vni.py b/src/conf_mode/vrf_vni.py
new file mode 100755
index 000000000..87ee8f2d1
--- /dev/null
+++ b/src/conf_mode/vrf_vni.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020-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 sys import argv
+from sys import exit
+
+from vyos.config import Config
+from vyos.template import render_to_string
+from vyos import ConfigError
+from vyos import frr
+from vyos import airbag
+airbag.enable()
+
+frr_daemon = 'zebra'
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ # This script only works with a passed VRF name
+ if len(argv) < 1:
+ raise NotImplementedError
+ vrf = argv[1]
+
+ # "assemble" dict - easier here then use a full blown get_config_dict()
+ # on a single leafNode
+ vni = { 'vrf' : vrf }
+ tmp = conf.return_value(['vrf', 'name', vrf, 'vni'])
+ if tmp: vni.update({ 'vni' : tmp })
+
+ return vni
+
+def verify(vni):
+ return None
+
+def generate(vni):
+ vni['new_frr_config'] = render_to_string('frr/vrf-vni.frr.tmpl', vni)
+ return None
+
+def apply(vni):
+ # add configuration to FRR
+ frr_cfg = frr.FRRConfig()
+ frr_cfg.load_configuration(frr_daemon)
+ frr_cfg.modify_section(f'^vrf [a-zA-Z-]*$', '')
+ frr_cfg.add_before(r'(interface .*|line vty)', vni['new_frr_config'])
+ frr_cfg.commit_configuration(frr_daemon)
+
+ # Save configuration to /run/frr/config/frr.conf
+ frr.save_configuration()
+
+ return None
+
+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/etc/ipsec.d/vti-up-down b/src/etc/ipsec.d/vti-up-down
index 281c9bf2b..011013a2e 100755
--- a/src/etc/ipsec.d/vti-up-down
+++ b/src/etc/ipsec.d/vti-up-down
@@ -55,7 +55,7 @@ if __name__ == '__main__':
syslog(f'Interface {interface} not found')
sys.exit(0)
- vti_link_up = (vti_link['operstate'] == 'UP' if 'operstate' in vti_link else False)
+ vti_link_up = (vti_link['operstate'] != 'DOWN' if 'operstate' in vti_link else False)
config = ConfigTreeQuery()
vti_dict = config.get_config_dict(['interfaces', 'vti', interface],
diff --git a/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback b/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback
new file mode 100755
index 000000000..bb918a468
--- /dev/null
+++ b/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback
@@ -0,0 +1,59 @@
+#!/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/>.
+
+# This is a Python hook script which is invoked whenever a PPPoE session goes
+# "ip-up". It will call into our vyos.ifconfig library and will then execute
+# common tasks for the PPPoE interface. The reason we have to "hook" this is
+# that we can not create a pppoeX interface in advance in linux and then connect
+# pppd to this already existing interface.
+
+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.ifconfig import PPPoEIf
+from vyos.util import read_file
+
+# When the ppp link comes up, this script is called with the following
+# parameters
+# $1 the interface name used by pppd (e.g. ppp3)
+# $2 the tty device name
+# $3 the tty device speed
+# $4 the local IP address for the interface
+# $5 the remote IP address
+# $6 the parameter specified by the 'ipparam' option to pppd
+
+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]
+
+p = PPPoEIf(pppoe['ifname'])
+p.update(pppoe)
diff --git a/src/etc/sysctl.d/32-vyos-podman.conf b/src/etc/sysctl.d/32-vyos-podman.conf
new file mode 100644
index 000000000..7068bf88d
--- /dev/null
+++ b/src/etc/sysctl.d/32-vyos-podman.conf
@@ -0,0 +1,5 @@
+# Increase inotify watchers as per https://bugzilla.redhat.com/show_bug.cgi?id=1829596
+fs.inotify.max_queued_events = 1048576
+fs.inotify.max_user_instances = 1048576
+fs.inotify.max_user_watches = 1048576
+
diff --git a/src/etc/udev/rules.d/90-vyos-serial.rules b/src/etc/udev/rules.d/90-vyos-serial.rules
index 3f10f4924..872fd4fea 100644
--- a/src/etc/udev/rules.d/90-vyos-serial.rules
+++ b/src/etc/udev/rules.d/90-vyos-serial.rules
@@ -8,7 +8,7 @@ SUBSYSTEMS=="pci", IMPORT{builtin}="hwdb --subsystem=pci"
SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id", IMPORT{builtin}="hwdb --subsystem=usb"
# /dev/serial/by-path/, /dev/serial/by-id/ for USB devices
-KERNEL!="ttyUSB[0-9]*|ttyACM[0-9]*", GOTO="serial_end"
+KERNEL!="ttyUSB[0-9]*", GOTO="serial_end"
SUBSYSTEMS=="usb-serial", ENV{.ID_PORT}="$attr{port_number}"
@@ -18,11 +18,11 @@ IMPORT{builtin}="path_id", IMPORT{builtin}="usb_id"
#
# - $env{ID_PATH} usually is a name like: "pci-0000:00:10.0-usb-0:2.3.3.4:1.0-port0" so we strip the "pci-*"
# portion and only use the usb part
-# - Transform the USB "speach" to the tree like structure so we start with "usb0" as root-complex 0.
+# - Transform the USB "speech" to the tree like structure so we start with "usb0" as root-complex 0.
# (tr -d -) does the replacement
# - Replace the first group after ":" to represent the bus relation (sed -e 0,/:/s//b/) indicated by "b"
# - Replace the next group after ":" to represent the port relation (sed -e 0,/:/s//p/) indicated by "p"
-ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="", PROGRAM="/bin/sh -c 'D=$env{ID_PATH}; echo ${D:17} | tr -d - | sed -e 0,/:/s//b/ | sed -e 0,/:/s//p/'", SYMLINK+="serial/by-bus/$result"
-ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="?*", PROGRAM="/bin/sh -c 'D=$env{ID_PATH}; echo ${D:17} | tr -d - | sed -e 0,/:/s//b/ | sed -e 0,/:/s//p/'", SYMLINK+="serial/by-bus/$result"
+ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="", PROGRAM="/bin/sh -c 'echo $env{ID_PATH:17} | tr -d - | sed -e 0,/:/s//b/ | sed -e 0,/:/s//p/'", SYMLINK+="serial/by-bus/$result"
+ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="?*", PROGRAM="/bin/sh -c 'echo $env{ID_PATH:17} | tr -d - | sed -e 0,/:/s//b/ | sed -e 0,/:/s//p/'", SYMLINK+="serial/by-bus/$result"
LABEL="serial_end"
diff --git a/src/etc/update-motd.d/99-reboot b/src/etc/update-motd.d/99-reboot
new file mode 100755
index 000000000..718be1a7a
--- /dev/null
+++ b/src/etc/update-motd.d/99-reboot
@@ -0,0 +1,7 @@
+#!/bin/vbash
+source /opt/vyatta/etc/functions/script-template
+if [ -f /run/systemd/shutdown/scheduled ]; then
+ echo
+ run show reboot
+fi
+exit
diff --git a/src/migration-scripts/conntrack/2-to-3 b/src/migration-scripts/conntrack/2-to-3
new file mode 100755
index 000000000..8a8b43279
--- /dev/null
+++ b/src/migration-scripts/conntrack/2-to-3
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+
+# Conntrack syntax version 3
+# Enables all conntrack modules (previous default behaviour) and omits manually disabled modules.
+
+import sys
+
+from vyos.configtree import ConfigTree
+from vyos.version import get_version
+
+if len(sys.argv) < 1:
+ print('Must specify file name!')
+ sys.exit(1)
+
+filename = sys.argv[1]
+
+with open(filename, 'r') as f:
+ config = ConfigTree(f.read())
+
+module_path = ['system', 'conntrack', 'modules']
+
+# Go over all conntrack modules available as of v1.3.0.
+for module in ['ftp', 'h323', 'nfs', 'pptp', 'sip', 'sqlnet', 'tftp']:
+ # 'disable' is being phased out.
+ if config.exists(module_path + [module, 'disable']):
+ config.delete(module_path + [module])
+ # If it wasn't manually 'disable'd, it was enabled by default.
+ else:
+ config.set(module_path + [module])
+
+try:
+ if config.exists(module_path):
+ with open(filename, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print(f'Failed to save the modified config: {e}')
+ sys.exit(1)
diff --git a/src/migration-scripts/firewall/5-to-6 b/src/migration-scripts/firewall/5-to-6
new file mode 100755
index 000000000..ccb86830a
--- /dev/null
+++ b/src/migration-scripts/firewall/5-to-6
@@ -0,0 +1,63 @@
+#!/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/>.
+
+# T3090: migrate "firewall options interface <name> adjust-mss" to the
+# individual interface.
+
+from sys import argv
+from sys import exit
+
+from vyos.configtree import ConfigTree
+from vyos.ifconfig import Section
+
+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 = ['firewall', 'options', 'interface']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+for interface in config.list_nodes(base):
+ if config.exists(base + [interface, 'disable']):
+ continue
+
+ if config.exists(base + [interface, 'adjust-mss']):
+ section = Section.section(interface)
+ tmp = config.return_value(base + [interface, 'adjust-mss'])
+ config.set(['interfaces', section, interface, 'ip', 'adjust-mss'], value=tmp)
+
+ if config.exists(base + [interface, 'adjust-mss6']):
+ section = Section.section(interface)
+ tmp = config.return_value(base + [interface, 'adjust-mss6'])
+ config.set(['interfaces', section, interface, 'ipv6', 'adjust-mss'], value=tmp)
+
+config.delete(['firewall', 'options'])
+
+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/containers_op.py b/src/op_mode/containers_op.py
index 1e3fc3a8f..bc317029c 100755
--- a/src/op_mode/containers_op.py
+++ b/src/op_mode/containers_op.py
@@ -15,10 +15,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import argparse
-from vyos.configquery import query_context, ConfigQueryError
-from vyos.util import cmd
-config, op = query_context()
+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")
@@ -26,34 +26,53 @@ parser.add_argument("-i", "--image", action="store_true", help="Show container i
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")
-if not config.exists(['container']):
+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'))
- exit(0)
- if args.image:
+
+ elif args.image:
print(cmd('podman image ls'))
- exit(0)
- if args.networks:
+
+ elif args.networks:
print(cmd('podman network ls'))
- exit(0)
- if args.pull:
+
+ elif args.pull:
image = args.pull
try:
- print(cmd(f'sudo podman image pull {image}'))
+ print(cmd(f'podman image pull {image}'))
except:
print(f'Can\'t find or download image "{image}"')
- exit(0)
- if args.remove:
+
+ elif args.remove:
image = args.remove
try:
- print(cmd(f'sudo podman image rm {image}'))
+ print(cmd(f'podman image rm {image}'))
except:
print(f'Can\'t delete image "{image}"')
- exit(0)
+
+ 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/dns_forwarding_statistics.py b/src/op_mode/dns_forwarding_statistics.py
index 1fb61d263..d79b6c024 100755
--- a/src/op_mode/dns_forwarding_statistics.py
+++ b/src/op_mode/dns_forwarding_statistics.py
@@ -11,7 +11,7 @@ PDNS_CMD='/usr/bin/rec_control --socket-dir=/run/powerdns'
OUT_TMPL_SRC = """
DNS forwarding statistics:
-Cache entries: {{ cache_entries -}}
+Cache entries: {{ cache_entries }}
Cache size: {{ cache_size }} kbytes
"""
diff --git a/src/op_mode/ikev2_profile_generator.py b/src/op_mode/ikev2_profile_generator.py
index d45525431..990b06c12 100755
--- a/src/op_mode/ikev2_profile_generator.py
+++ b/src/op_mode/ikev2_profile_generator.py
@@ -21,7 +21,7 @@ from sys import exit
from socket import getfqdn
from cryptography.x509.oid import NameOID
-from vyos.config import Config
+from vyos.configquery import ConfigTreeQuery
from vyos.pki import load_certificate
from vyos.template import render_to_string
from vyos.util import ask_input
@@ -117,7 +117,7 @@ args = parser.parse_args()
ipsec_base = ['vpn', 'ipsec']
config_base = ipsec_base + ['remote-access', 'connection']
pki_base = ['pki']
-conf = Config()
+conf = ConfigTreeQuery()
if not conf.exists(config_base):
exit('IPSec remote-access is not configured!')
@@ -153,7 +153,7 @@ cert = load_certificate(pki['certificate'][cert_name]['certificate'])
data['ca_cn'] = ca_cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
data['cert_cn'] = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
-data['ca_cert'] = conf.return_value(pki_base + ['ca', ca_name, 'certificate'])
+data['ca_cert'] = conf.value(pki_base + ['ca', ca_name, 'certificate'])
esp_proposals = conf.get_config_dict(ipsec_base + ['esp-group', data['esp_group'], 'proposal'],
key_mangling=('-', '_'), get_first_key=True)
diff --git a/src/op_mode/pki.py b/src/op_mode/pki.py
index 297270cf1..36891d080 100755
--- a/src/op_mode/pki.py
+++ b/src/op_mode/pki.py
@@ -24,7 +24,7 @@ import tabulate
from cryptography import x509
from cryptography.x509.oid import ExtendedKeyUsageOID
-from vyos.config import Config
+from vyos.configquery import ConfigTreeQuery
from vyos.configdict import dict_merge
from vyos.pki import encode_certificate, encode_public_key, encode_private_key, encode_dh_parameters
from vyos.pki import create_certificate, create_certificate_request, create_certificate_revocation_list
@@ -41,10 +41,10 @@ CERT_REQ_END = '-----END CERTIFICATE REQUEST-----'
auth_dir = '/config/auth'
# Helper Functions
-
+conf = ConfigTreeQuery()
def get_default_values():
# Fetch default x509 values
- conf = Config()
+
base = ['pki', 'x509', 'default']
x509_defaults = conf.get_config_dict(base, key_mangling=('-', '_'),
get_first_key=True, no_tag_node_value_mangle=True)
@@ -53,9 +53,7 @@ def get_default_values():
def get_config_ca_certificate(name=None):
# Fetch ca certificates from config
- conf = Config()
base = ['pki', 'ca']
-
if not conf.exists(base):
return False
@@ -69,9 +67,7 @@ def get_config_ca_certificate(name=None):
def get_config_certificate(name=None):
# Get certificates from config
- conf = Config()
base = ['pki', 'certificate']
-
if not conf.exists(base):
return False
@@ -100,7 +96,6 @@ def get_certificate_ca(cert, ca_certs):
def get_config_revoked_certificates():
# Fetch revoked certificates from config
- conf = Config()
ca_base = ['pki', 'ca']
cert_base = ['pki', 'certificate']
@@ -464,7 +459,7 @@ def generate_certificate_sign(name, ca_name, install=False, file=False):
if not cert_req:
print("Invalid certificate request")
return None
-
+
cert = generate_certificate(cert_req, ca_cert, ca_private_key, is_ca=False)
passphrase = ask_passphrase()
@@ -813,7 +808,7 @@ if __name__ == '__main__':
elif args.self_sign:
generate_certificate_selfsign(args.certificate, install=args.install, file=args.file)
else:
- generate_certificate_request(name=args.certificate, install=args.install)
+ generate_certificate_request(name=args.certificate, install=args.install, file=args.file)
elif args.crl:
generate_certificate_revocation_list(args.crl, install=args.install, file=args.file)
elif args.ssh:
diff --git a/src/op_mode/restart_frr.py b/src/op_mode/restart_frr.py
index d1b66b33f..0b2322478 100755
--- a/src/op_mode/restart_frr.py
+++ b/src/op_mode/restart_frr.py
@@ -155,7 +155,7 @@ def _check_args_daemon(daemons):
# define program arguments
cmd_args_parser = argparse.ArgumentParser(description='restart frr daemons')
cmd_args_parser.add_argument('--action', choices=['restart'], required=True, help='action to frr daemons')
-cmd_args_parser.add_argument('--daemon', choices=['bfdd', 'bgpd', 'ospfd', 'ospf6d', 'ripd', 'ripngd', 'staticd', 'zebra'], required=False, nargs='*', help='select single or multiple daemons')
+cmd_args_parser.add_argument('--daemon', choices=['bfdd', 'bgpd', 'ospfd', 'ospf6d', 'isisd', 'ripd', 'ripngd', 'staticd', 'zebra'], required=False, nargs='*', help='select single or multiple daemons')
# parse arguments
cmd_args = cmd_args_parser.parse_args()
diff --git a/src/op_mode/show_interfaces.py b/src/op_mode/show_interfaces.py
index 20d5d9e17..241fba4f4 100755
--- a/src/op_mode/show_interfaces.py
+++ b/src/op_mode/show_interfaces.py
@@ -120,10 +120,6 @@ def split_text(text, used=0):
yield line[1:]
-def get_vrrp_intf():
- return [intf for intf in Section.interfaces() if intf.is_vrrp()]
-
-
def get_counter_val(clear, now):
"""
attempt to correct a counter if it wrapped, copied from perl
diff --git a/src/op_mode/show_ipsec_sa.py b/src/op_mode/show_ipsec_sa.py
index e491267fd..c964caaeb 100755
--- a/src/op_mode/show_ipsec_sa.py
+++ b/src/op_mode/show_ipsec_sa.py
@@ -23,6 +23,12 @@ import hurry.filesize
import vyos.util
+def convert(text):
+ return int(text) if text.isdigit() else text.lower()
+
+def alphanum_key(key):
+ return [convert(c) for c in re.split('([0-9]+)', str(key))]
+
def format_output(conns, sas):
sa_data = []
@@ -111,7 +117,7 @@ if __name__ == '__main__':
headers = ["Connection", "State", "Uptime", "Bytes In/Out", "Packets In/Out", "Remote address", "Remote ID", "Proposal"]
sa_data = format_output(conns, sas)
- sa_data = sorted(sa_data, key=lambda peer: peer[0])
+ sa_data = sorted(sa_data, key=alphanum_key)
output = tabulate.tabulate(sa_data, headers)
print(output)
except PermissionError:
diff --git a/src/op_mode/show_nat_rules.py b/src/op_mode/show_nat_rules.py
index 0f40ecabe..4a059c848 100755
--- a/src/op_mode/show_nat_rules.py
+++ b/src/op_mode/show_nat_rules.py
@@ -67,46 +67,54 @@ if args.source or args.destination:
continue
interface = dict_search('match.right', data['expr'][0])
srcdest = ''
- for i in [1, 2]:
- srcdest_json = dict_search('match.right', data['expr'][i])
- if not srcdest_json:
- continue
-
- if isinstance(srcdest_json,str):
- srcdest += srcdest_json + ' '
- elif 'prefix' in srcdest_json:
- addr_tmp = dict_search('match.right.prefix.addr', data['expr'][i])
- len_tmp = dict_search('match.right.prefix.len', data['expr'][i])
- if addr_tmp and len_tmp:
- srcdest = addr_tmp + '/' + str(len_tmp) + ' '
- elif 'set' in srcdest_json:
- if isinstance(srcdest_json['set'][0],str):
- srcdest += 'port ' + str(srcdest_json['set'][0]) + ' '
- else:
- port_range = srcdest_json['set'][0]['range']
- srcdest += 'port ' + str(port_range[0]) + '-' + str(port_range[1]) + ' '
-
+ srcdests = []
tran_addr = ''
- tran_addr_json = dict_search('snat.addr' if args.source else 'dnat.addr', data['expr'][3])
- if tran_addr_json:
- if isinstance(tran_addr_json,str):
- tran_addr = tran_addr_json
- elif 'prefix' in tran_addr_json:
- addr_tmp = dict_search('snat.addr.prefix.addr' if args.source else 'dnat.addr.prefix.addr', data['expr'][3])
- len_tmp = dict_search('snat.addr.prefix.len' if args.source else 'dnat.addr.prefix.len', data['expr'][3])
- if addr_tmp and len_tmp:
- tran_addr = addr_tmp + '/' + str(len_tmp)
- else:
- if 'masquerade' in data['expr'][3]:
- tran_addr = 'masquerade'
- elif 'log' in data['expr'][3]:
- continue
-
- tran_port = dict_search('snat.port' if args.source else 'dnat.port', data['expr'][3])
- if tran_port:
- tran_addr += ' port ' + str(tran_port)
+ for i in range(1,len(data['expr']) + 1):
+ srcdest_json = dict_search('match.right', data['expr'][i])
+ if srcdest_json:
+ if isinstance(srcdest_json,str):
+ if srcdest != '':
+ srcdests.append(srcdest)
+ srcdest = ''
+ srcdest = srcdest_json + ' '
+ elif 'prefix' in srcdest_json:
+ addr_tmp = dict_search('match.right.prefix.addr', data['expr'][i])
+ len_tmp = dict_search('match.right.prefix.len', data['expr'][i])
+ if addr_tmp and len_tmp:
+ srcdest = addr_tmp + '/' + str(len_tmp) + ' '
+ elif 'set' in srcdest_json:
+ if isinstance(srcdest_json['set'][0],int):
+ srcdest += 'port ' + str(srcdest_json['set'][0]) + ' '
+ else:
+ port_range = srcdest_json['set'][0]['range']
+ srcdest += 'port ' + str(port_range[0]) + '-' + str(port_range[1]) + ' '
+
+ tran_addr_json = dict_search('snat' if args.source else 'dnat', data['expr'][i])
+ if tran_addr_json:
+ if isinstance(tran_addr_json['addr'],str):
+ tran_addr += tran_addr_json['addr'] + ' '
+ elif 'prefix' in tran_addr_json['addr']:
+ addr_tmp = dict_search('snat.addr.prefix.addr' if args.source else 'dnat.addr.prefix.addr', data['expr'][3])
+ len_tmp = dict_search('snat.addr.prefix.len' if args.source else 'dnat.addr.prefix.len', data['expr'][3])
+ if addr_tmp and len_tmp:
+ tran_addr += addr_tmp + '/' + str(len_tmp) + ' '
+
+ if isinstance(tran_addr_json['port'],int):
+ tran_addr += 'port ' + tran_addr_json['port']
+
+ else:
+ if 'masquerade' in data['expr'][i]:
+ tran_addr = 'masquerade'
+ elif 'log' in data['expr'][i]:
+ continue
- print(format_nat_rule.format(rule, srcdest, tran_addr, interface))
+ if srcdest != '':
+ srcdests.append(srcdest)
+ srcdest = ''
+ print(format_nat_rule.format(rule, srcdests[0], tran_addr, interface))
+
+ for i in range(1, list(srcdest)):
+ print(format_nat_rule.format(' ', srcdests[i], ' ', ' '))
exit(0)
else:
diff --git a/src/op_mode/show_system_integrity.py b/src/op_mode/show_system_integrity.py
deleted file mode 100755
index c34d41e80..000000000
--- a/src/op_mode/show_system_integrity.py
+++ /dev/null
@@ -1,70 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2020 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 sys
-import os
-import re
-import json
-from datetime import datetime, timedelta
-
-version_file = r'/usr/share/vyos/version.json'
-
-
-def _get_sys_build_version():
- if not os.path.exists(version_file):
- return None
- buf = open(version_file, 'r').read()
- j = json.loads(buf)
- if not 'built_on' in j:
- return None
- return datetime.strptime(j['built_on'], '%a %d %b %Y %H:%M %Z')
-
-
-def _check_pkgs(build_stamp):
- pkg_diffs = {
- 'buildtime': str(build_stamp),
- 'pkg': {}
- }
-
- pkg_info = os.listdir('/var/lib/dpkg/info/')
- for file in pkg_info:
- if re.search('\.list$', file):
- fts = os.stat('/var/lib/dpkg/info/' + file).st_mtime
- dt_str = (datetime.utcfromtimestamp(
- fts).strftime('%Y-%m-%d %H:%M:%S'))
- fdt = datetime.strptime(dt_str, '%Y-%m-%d %H:%M:%S')
- if fdt > build_stamp:
- pkg_diffs['pkg'].update(
- {str(re.sub('\.list', '', file)): str(fdt)})
-
- if len(pkg_diffs['pkg']) != 0:
- return pkg_diffs
- else:
- return None
-
-
-if __name__ == '__main__':
- built_date = _get_sys_build_version()
- if not built_date:
- sys.exit(1)
- pkgs = _check_pkgs(built_date)
- if pkgs:
- print (
- "The following packages don\'t fit the image creation time\nbuild time:\t" + pkgs['buildtime'])
- for k, v in pkgs['pkg'].items():
- print ("installed: " + v + '\t' + k)
diff --git a/src/op_mode/wireguard_client.py b/src/op_mode/wireguard_client.py
index 7661254da..76c1ff7d1 100755
--- a/src/op_mode/wireguard_client.py
+++ b/src/op_mode/wireguard_client.py
@@ -39,10 +39,11 @@ To enable this configuration on a VyOS router you can use the following commands
set interfaces wireguard {{ interface }} peer {{ name }} allowed-ips '{{ addr }}'
{% endfor %}
set interfaces wireguard {{ interface }} peer {{ name }} public-key '{{ pubkey }}'
+
+=== RoadWarrior (client) configuration ===
"""
client_config = """
-=== RoadWarrior (client) configuration ===
[Interface]
PrivateKey = {{ privkey }}
diff --git a/src/systemd/opennhrp.service b/src/systemd/opennhrp.service
index 70235f89d..c9a44de29 100644
--- a/src/systemd/opennhrp.service
+++ b/src/systemd/opennhrp.service
@@ -6,8 +6,8 @@ StartLimitIntervalSec=0
[Service]
Type=forking
-ExecStart=/usr/sbin/opennhrp -d -v -a /run/opennhrp.socket -c /run/opennhrp/opennhrp.conf -s /etc/opennhrp/opennhrp-script.py -p /run/opennhrp.pid
+ExecStart=/usr/sbin/opennhrp -d -v -a /run/opennhrp.socket -c /run/opennhrp/opennhrp.conf -s /etc/opennhrp/opennhrp-script.py -p /run/opennhrp/opennhrp.pid
ExecReload=/usr/bin/kill -HUP $MAINPID
-PIDFile=/run/opennhrp.pid
+PIDFile=/run/opennhrp/opennhrp.pid
Restart=on-failure
RestartSec=20
diff --git a/src/validators/bgp-large-community-list b/src/validators/bgp-large-community-list
new file mode 100755
index 000000000..c07268e81
--- /dev/null
+++ b/src/validators/bgp-large-community-list
@@ -0,0 +1,36 @@
+#!/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 re
+import sys
+
+from vyos.template import is_ipv4
+
+pattern = '(.*):(.*):(.*)'
+
+if __name__ == '__main__':
+ if len(sys.argv) != 2:
+ sys.exit(1)
+
+ value = sys.argv[1].split(':')
+ if not len(value) == 3:
+ sys.exit(1)
+
+ if not (re.match(pattern, sys.argv[1]) and
+ (is_ipv4(value[0]) or value[0].isdigit()) and value[1].isdigit()):
+ sys.exit(1)
+
+ sys.exit(0)
diff --git a/src/validators/bgp-route-target b/src/validators/bgp-route-target
new file mode 100755
index 000000000..e7e4d403f
--- /dev/null
+++ b/src/validators/bgp-route-target
@@ -0,0 +1,51 @@
+#!/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 argparse import ArgumentParser
+from vyos.template import is_ipv4
+
+parser = ArgumentParser()
+group = parser.add_mutually_exclusive_group()
+group.add_argument('--single', action='store', help='Validate and allow only one route-target')
+group.add_argument('--multi', action='store', help='Validate multiple, whitespace separated route-targets')
+args = parser.parse_args()
+
+def is_valid_rt(rt):
+ # every route target needs to have a colon and must consists of two parts
+ value = rt.split(':')
+ if len(value) != 2:
+ return False
+ # A route target must either be only numbers, or the first part must be an
+ # IPv4 address
+ if (is_ipv4(value[0]) or value[0].isdigit()) and value[1].isdigit():
+ return True
+ return False
+
+if __name__ == '__main__':
+ if args.single:
+ if not is_valid_rt(args.single):
+ exit(1)
+
+ elif args.multi:
+ for rt in args.multi.split(' '):
+ if not is_valid_rt(rt):
+ exit(1)
+
+ else:
+ parser.print_help()
+ exit(1)
+
+ exit(0)