summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/completion/list_consoles.sh4
-rwxr-xr-xsrc/conf_mode/firewall-interface.py186
-rwxr-xr-xsrc/conf_mode/firewall.py292
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py3
-rwxr-xr-xsrc/conf_mode/interfaces-wireguard.py32
-rwxr-xr-xsrc/conf_mode/interfaces-wwan.py2
-rwxr-xr-xsrc/conf_mode/nat.py29
-rwxr-xr-xsrc/conf_mode/nat66.py11
-rwxr-xr-xsrc/conf_mode/policy-route.py4
-rwxr-xr-xsrc/conf_mode/policy.py132
-rwxr-xr-xsrc/conf_mode/protocols_isis.py22
-rwxr-xr-xsrc/conf_mode/protocols_nhrp.py32
-rwxr-xr-xsrc/conf_mode/protocols_ospf.py52
-rwxr-xr-xsrc/conf_mode/service_console-server.py7
-rwxr-xr-xsrc/conf_mode/service_ipoe-server.py289
-rwxr-xr-xsrc/conf_mode/service_monitoring_telegraf.py2
-rwxr-xr-xsrc/conf_mode/service_pppoe-server.py30
-rwxr-xr-xsrc/conf_mode/ssh.py3
-rwxr-xr-xsrc/conf_mode/system-login.py8
-rwxr-xr-xsrc/conf_mode/system_update_check.py93
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py17
-rwxr-xr-xsrc/conf_mode/vpn_openconnect.py22
-rwxr-xr-xsrc/conf_mode/zone_policy.py213
-rw-r--r--src/etc/sysctl.d/30-vyos-router.conf4
-rwxr-xr-xsrc/etc/telegraf/custom_scripts/show_firewall_input_filter.py6
-rwxr-xr-xsrc/migration-scripts/firewall/7-to-898
-rwxr-xr-xsrc/migration-scripts/ids/0-to-156
-rwxr-xr-xsrc/migration-scripts/ipoe-server/0-to-1133
-rwxr-xr-xsrc/migration-scripts/ipsec/9-to-10134
-rwxr-xr-xsrc/migration-scripts/isis/1-to-246
-rwxr-xr-xsrc/migration-scripts/policy/3-to-4162
-rwxr-xr-xsrc/migration-scripts/pppoe-server/5-to-652
-rwxr-xr-xsrc/op_mode/bridge.py8
-rwxr-xr-xsrc/op_mode/conntrack.py11
-rwxr-xr-xsrc/op_mode/firewall.py52
-rwxr-xr-xsrc/op_mode/ikev2_profile_generator.py8
-rwxr-xr-xsrc/op_mode/nat.py13
-rwxr-xr-x[-rw-r--r--]src/op_mode/route.py0
-rwxr-xr-xsrc/op_mode/show_nat66_statistics.py2
-rwxr-xr-xsrc/op_mode/show_nat_statistics.py2
-rwxr-xr-xsrc/op_mode/storage.py60
-rwxr-xr-xsrc/op_mode/system.py92
-rwxr-xr-xsrc/op_mode/uptime.py (renamed from src/op_mode/show_uptime.py)36
-rwxr-xr-xsrc/op_mode/vpn_ike_sa.py2
-rw-r--r--src/services/api/graphql/graphql/directives.py64
-rw-r--r--src/services/api/graphql/graphql/mutations.py26
-rw-r--r--src/services/api/graphql/graphql/queries.py23
-rw-r--r--src/services/api/graphql/graphql/schema/config_file.graphql29
-rw-r--r--src/services/api/graphql/graphql/schema/configsession.graphql115
-rw-r--r--src/services/api/graphql/graphql/schema/dhcp_server.graphql36
-rw-r--r--src/services/api/graphql/graphql/schema/firewall_group.graphql101
-rw-r--r--src/services/api/graphql/graphql/schema/image.graphql31
-rw-r--r--src/services/api/graphql/graphql/schema/interface_ethernet.graphql19
-rw-r--r--src/services/api/graphql/graphql/schema/schema.graphql24
-rw-r--r--src/services/api/graphql/graphql/schema/show.graphql15
-rw-r--r--src/services/api/graphql/graphql/schema/show_config.graphql21
-rwxr-xr-xsrc/services/api/graphql/session/composite/system_status.py4
-rw-r--r--src/services/api/graphql/session/session.py44
-rw-r--r--src/services/api/graphql/utils/config_session_function.py28
-rwxr-xr-xsrc/services/api/graphql/utils/schema_from_config_session.py119
-rwxr-xr-xsrc/services/api/graphql/utils/schema_from_op_mode.py30
-rw-r--r--src/services/api/graphql/utils/util.py24
-rwxr-xr-xsrc/system/vyos-system-update-check.py70
-rw-r--r--src/systemd/vyos-system-update.service11
-rwxr-xr-xsrc/validators/accel-radius-dictionary13
-rwxr-xr-xsrc/validators/bgp-extended-community55
-rwxr-xr-xsrc/validators/bgp-large-community53
-rwxr-xr-xsrc/validators/bgp-regular-community50
-rwxr-xr-xsrc/validators/range56
69 files changed, 1925 insertions, 1598 deletions
diff --git a/src/completion/list_consoles.sh b/src/completion/list_consoles.sh
new file mode 100755
index 000000000..52278c4cb
--- /dev/null
+++ b/src/completion/list_consoles.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+# For lines like `aliases "foo";`, regex matches everything between the quotes
+grep -oP '(?<=aliases ").+(?=";)' /run/conserver/conserver.cf \ No newline at end of file
diff --git a/src/conf_mode/firewall-interface.py b/src/conf_mode/firewall-interface.py
deleted file mode 100755
index ab1c69259..000000000
--- a/src/conf_mode/firewall-interface.py
+++ /dev/null
@@ -1,186 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2021 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import os
-import re
-
-from sys import argv
-from sys import exit
-
-from vyos.config import Config
-from vyos.configdict import leaf_node_changed
-from vyos.ifconfig import Section
-from vyos.template import render
-from vyos.util import cmd
-from vyos.util import dict_search_args
-from vyos.util import run
-from vyos import ConfigError
-from vyos import airbag
-airbag.enable()
-
-NAME_PREFIX = 'NAME_'
-NAME6_PREFIX = 'NAME6_'
-
-NFT_CHAINS = {
- 'in': 'VYOS_FW_FORWARD',
- 'out': 'VYOS_FW_FORWARD',
- 'local': 'VYOS_FW_LOCAL'
-}
-NFT6_CHAINS = {
- 'in': 'VYOS_FW6_FORWARD',
- 'out': 'VYOS_FW6_FORWARD',
- 'local': 'VYOS_FW6_LOCAL'
-}
-
-def get_config(config=None):
- if config:
- conf = config
- else:
- conf = Config()
-
- ifname = argv[1]
- ifpath = Section.get_config_path(ifname)
- if_firewall_path = f'interfaces {ifpath} firewall'
-
- if_firewall = conf.get_config_dict(if_firewall_path, key_mangling=('-', '_'), get_first_key=True,
- no_tag_node_value_mangle=True)
-
- if_firewall['ifname'] = ifname
- if_firewall['firewall'] = conf.get_config_dict(['firewall'], key_mangling=('-', '_'), get_first_key=True,
- no_tag_node_value_mangle=True)
-
- return if_firewall
-
-def verify_chain(table, chain):
- # Verify firewall applied
- code = run(f'nft list chain {table} {chain}')
- return code == 0
-
-def verify(if_firewall):
- # bail out early - looks like removal from running config
- if not if_firewall:
- return None
-
- for direction in ['in', 'out', 'local']:
- if direction in if_firewall:
- if 'name' in if_firewall[direction]:
- name = if_firewall[direction]['name']
-
- if 'name' not in if_firewall['firewall']:
- raise ConfigError('Firewall name not configured')
-
- if name not in if_firewall['firewall']['name']:
- raise ConfigError(f'Invalid firewall name "{name}"')
-
- if not verify_chain('ip filter', f'{NAME_PREFIX}{name}'):
- raise ConfigError('Firewall did not apply')
-
- if 'ipv6_name' in if_firewall[direction]:
- name = if_firewall[direction]['ipv6_name']
-
- if 'ipv6_name' not in if_firewall['firewall']:
- raise ConfigError('Firewall ipv6-name not configured')
-
- if name not in if_firewall['firewall']['ipv6_name']:
- raise ConfigError(f'Invalid firewall ipv6-name "{name}"')
-
- if not verify_chain('ip6 filter', f'{NAME6_PREFIX}{name}'):
- raise ConfigError('Firewall did not apply')
-
- return None
-
-def generate(if_firewall):
- return None
-
-def cleanup_rule(table, chain, prefix, ifname, new_name=None):
- results = cmd(f'nft -a list chain {table} {chain}').split("\n")
- retval = None
- for line in results:
- if f'{prefix}ifname "{ifname}"' in line:
- if new_name and f'jump {new_name}' in line:
- # new_name is used to clear rules for any previously referenced chains
- # returns true when rule exists and doesn't need to be created
- retval = True
- continue
-
- handle_search = re.search('handle (\d+)', line)
- if handle_search:
- run(f'nft delete rule {table} {chain} handle {handle_search[1]}')
- return retval
-
-def state_policy_handle(table, chain):
- # Find any state-policy rule to ensure interface rules are only inserted afterwards
- results = cmd(f'nft -a list chain {table} {chain}').split("\n")
- for line in results:
- if 'jump VYOS_STATE_POLICY' in line:
- handle_search = re.search('handle (\d+)', line)
- if handle_search:
- return handle_search[1]
- return None
-
-def apply(if_firewall):
- ifname = if_firewall['ifname']
-
- for direction in ['in', 'out', 'local']:
- chain = NFT_CHAINS[direction]
- ipv6_chain = NFT6_CHAINS[direction]
- if_prefix = 'i' if direction in ['in', 'local'] else 'o'
-
- name = dict_search_args(if_firewall, direction, 'name')
- if name:
- rule_exists = cleanup_rule('ip filter', chain, if_prefix, ifname, f'{NAME_PREFIX}{name}')
-
- if not rule_exists:
- rule_action = 'insert'
- rule_prefix = ''
-
- handle = state_policy_handle('ip filter', chain)
- if handle:
- rule_action = 'add'
- rule_prefix = f'position {handle}'
-
- run(f'nft {rule_action} rule ip filter {chain} {rule_prefix} {if_prefix}ifname {ifname} counter jump {NAME_PREFIX}{name}')
- else:
- cleanup_rule('ip filter', chain, if_prefix, ifname)
-
- ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name')
- if ipv6_name:
- rule_exists = cleanup_rule('ip6 filter', ipv6_chain, if_prefix, ifname, f'{NAME6_PREFIX}{ipv6_name}')
-
- if not rule_exists:
- rule_action = 'insert'
- rule_prefix = ''
-
- handle = state_policy_handle('ip6 filter', ipv6_chain)
- if handle:
- rule_action = 'add'
- rule_prefix = f'position {handle}'
-
- run(f'nft {rule_action} rule ip6 filter {ipv6_chain} {rule_prefix} {if_prefix}ifname {ifname} counter jump {NAME6_PREFIX}{ipv6_name}')
- else:
- cleanup_rule('ip6 filter', ipv6_chain, if_prefix, ifname)
-
- 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/conf_mode/firewall.py b/src/conf_mode/firewall.py
index f0ea1a1e5..cbd9cbe90 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -26,6 +26,7 @@ from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
from vyos.configdiff import get_config_diff, Diff
+# from vyos.configverify import verify_interface_exists
from vyos.firewall import geoip_update
from vyos.firewall import get_ips_domains_dict
from vyos.firewall import nft_add_set_elements
@@ -38,7 +39,7 @@ from vyos.util import cmd
from vyos.util import dict_search_args
from vyos.util import dict_search_recursive
from vyos.util import process_named_running
-from vyos.util import run
+from vyos.util import rc_cmd
from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
@@ -47,7 +48,6 @@ airbag.enable()
policy_route_conf_script = '/usr/libexec/vyos/conf_mode/policy-route.py'
nftables_conf = '/run/nftables.conf'
-nftables_defines_conf = '/run/nftables_defines.conf'
sysfs_config = {
'all_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_all', 'enable': '0', 'disable': '1'},
@@ -63,28 +63,6 @@ sysfs_config = {
'twa_hazards_protection': {'sysfs': '/proc/sys/net/ipv4/tcp_rfc1337'}
}
-NAME_PREFIX = 'NAME_'
-NAME6_PREFIX = 'NAME6_'
-
-preserve_chains = [
- 'INPUT',
- 'FORWARD',
- 'OUTPUT',
- 'VYOS_FW_FORWARD',
- 'VYOS_FW_LOCAL',
- 'VYOS_FW_OUTPUT',
- 'VYOS_POST_FW',
- 'VYOS_FRAG_MARK',
- 'VYOS_FW6_FORWARD',
- 'VYOS_FW6_LOCAL',
- 'VYOS_FW6_OUTPUT',
- 'VYOS_POST_FW6',
- 'VYOS_FRAG6_MARK'
-]
-
-nft_iface_chains = ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL']
-nft6_iface_chains = ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL']
-
valid_groups = [
'address_group',
'domain_group',
@@ -97,16 +75,6 @@ nested_group_types = [
'port_group', 'ipv6_address_group', 'ipv6_network_group'
]
-group_set_prefix = {
- 'A_': 'address_group',
- 'A6_': 'ipv6_address_group',
- 'D_': 'domain_group',
- 'M_': 'mac_group',
- 'N_': 'network_group',
- 'N6_': 'ipv6_network_group',
- 'P_': 'port_group'
-}
-
snmp_change_type = {
'unknown': 0,
'add': 1,
@@ -117,51 +85,6 @@ snmp_event_source = 1
snmp_trap_mib = 'VYATTA-TRAP-MIB'
snmp_trap_name = 'mgmtEventTrap'
-def get_firewall_interfaces(conf):
- out = {}
- interfaces = conf.get_config_dict(['interfaces'], key_mangling=('-', '_'), get_first_key=True,
- no_tag_node_value_mangle=True)
- def find_interfaces(iftype_conf, output={}, prefix=''):
- for ifname, if_conf in iftype_conf.items():
- if 'firewall' in if_conf:
- output[prefix + ifname] = if_conf['firewall']
- for vif in ['vif', 'vif_s', 'vif_c']:
- if vif in if_conf:
- output.update(find_interfaces(if_conf[vif], output, f'{prefix}{ifname}.'))
- return output
- for iftype, iftype_conf in interfaces.items():
- out.update(find_interfaces(iftype_conf))
- return out
-
-def get_firewall_zones(conf):
- used_v4 = []
- used_v6 = []
- zone_policy = conf.get_config_dict(['zone-policy'], key_mangling=('-', '_'), get_first_key=True,
- no_tag_node_value_mangle=True)
-
- if 'zone' in zone_policy:
- for zone, zone_conf in zone_policy['zone'].items():
- if 'from' in zone_conf:
- for from_zone, from_conf in zone_conf['from'].items():
- name = dict_search_args(from_conf, 'firewall', 'name')
- if name:
- used_v4.append(name)
-
- ipv6_name = dict_search_args(from_conf, 'firewall', 'ipv6_name')
- if ipv6_name:
- used_v6.append(ipv6_name)
-
- if 'intra_zone_filtering' in zone_conf:
- name = dict_search_args(zone_conf, 'intra_zone_filtering', 'firewall', 'name')
- if name:
- used_v4.append(name)
-
- ipv6_name = dict_search_args(zone_conf, 'intra_zone_filtering', 'firewall', 'ipv6_name')
- if ipv6_name:
- used_v6.append(ipv6_name)
-
- return {'name': used_v4, 'ipv6_name': used_v6}
-
def geoip_updated(conf, firewall):
diff = get_config_diff(conf)
node_diff = diff.get_child_nodes_diff(['firewall'], expand_nodes=Diff.DELETE, recursive=True)
@@ -215,6 +138,9 @@ def get_config(config=None):
if tmp in default_values:
del default_values[tmp]
+ if 'zone' in default_values:
+ del default_values['zone']
+
firewall = dict_merge(default_values, firewall)
# Merge in defaults for IPv4 ruleset
@@ -231,9 +157,12 @@ def get_config(config=None):
firewall['ipv6_name'][ipv6_name] = dict_merge(default_values,
firewall['ipv6_name'][ipv6_name])
+ if 'zone' in firewall:
+ default_values = defaults(base + ['zone'])
+ for zone in firewall['zone']:
+ firewall['zone'][zone] = dict_merge(default_values, firewall['zone'][zone])
+
firewall['policy_resync'] = bool('group' in firewall or node_changed(conf, base + ['group']))
- firewall['interfaces'] = get_firewall_interfaces(conf)
- firewall['zone_policy'] = get_firewall_zones(conf)
if 'config_trap' in firewall and firewall['config_trap'] == 'enable':
diff = get_config_diff(conf)
@@ -250,6 +179,20 @@ def verify_rule(firewall, rule_conf, ipv6):
if 'action' not in rule_conf:
raise ConfigError('Rule action must be defined')
+ if 'jump' in rule_conf['action'] and 'jump_target' not in rule_conf:
+ raise ConfigError('Action set to jump, but no jump-target specified')
+
+ if 'jump_target' in rule_conf:
+ if 'jump' not in rule_conf['action']:
+ raise ConfigError('jump-target defined, but action jump needed and it is not defined')
+ target = rule_conf['jump_target']
+ if not ipv6:
+ if target not in dict_search_args(firewall, 'name'):
+ raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system')
+ else:
+ if target not in dict_search_args(firewall, 'ipv6_name'):
+ raise ConfigError(f'Invalid jump-target. Firewall ipv6-name {target} does not exist on the system')
+
if 'fragment' in rule_conf:
if {'match_frag', 'match_non_frag'} <= set(rule_conf['fragment']):
raise ConfigError('Cannot specify both "match-frag" and "match-non-frag"')
@@ -358,109 +301,111 @@ def verify(firewall):
for name in ['name', 'ipv6_name']:
if name in firewall:
for name_id, name_conf in firewall[name].items():
- if name_id in preserve_chains:
- raise ConfigError(f'Firewall name "{name_id}" is reserved for VyOS')
-
- if name_id.startswith("VZONE"):
- raise ConfigError(f'Firewall name "{name_id}" uses reserved prefix')
+ if 'jump' in name_conf['default_action'] and 'default_jump_target' not in name_conf:
+ raise ConfigError('default-action set to jump, but no default-jump-target specified')
+ if 'default_jump_target' in name_conf:
+ target = name_conf['default_jump_target']
+ if 'jump' not in name_conf['default_action']:
+ raise ConfigError('default-jump-target defined,but default-action jump needed and it is not defined')
+ if name_conf['default_jump_target'] == name_id:
+ raise ConfigError(f'Loop detected on default-jump-target.')
+ ## Now need to check that default-jump-target exists (other firewall chain/name)
+ if target not in dict_search_args(firewall, name):
+ raise ConfigError(f'Invalid jump-target. Firewall {name} {target} does not exist on the system')
if 'rule' in name_conf:
for rule_id, rule_conf in name_conf['rule'].items():
verify_rule(firewall, rule_conf, name == 'ipv6_name')
- for ifname, if_firewall in firewall['interfaces'].items():
- for direction in ['in', 'out', 'local']:
- name = dict_search_args(if_firewall, direction, 'name')
- ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name')
+ if 'interface' in firewall:
+ for ifname, if_firewall in firewall['interface'].items():
+ # verify ifname needs to be disabled, dynamic devices come up later
+ # verify_interface_exists(ifname)
- if name and dict_search_args(firewall, 'name', name) == None:
- raise ConfigError(f'Firewall name "{name}" is still referenced on interface {ifname}')
+ for direction in ['in', 'out', 'local']:
+ name = dict_search_args(if_firewall, direction, 'name')
+ ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name')
- if ipv6_name and dict_search_args(firewall, 'ipv6_name', ipv6_name) == None:
- raise ConfigError(f'Firewall ipv6-name "{ipv6_name}" is still referenced on interface {ifname}')
+ if name and dict_search_args(firewall, 'name', name) == None:
+ raise ConfigError(f'Invalid firewall name "{name}" referenced on interface {ifname}')
- for fw_name, used_names in firewall['zone_policy'].items():
- for name in used_names:
- if dict_search_args(firewall, fw_name, name) == None:
- raise ConfigError(f'Firewall {fw_name.replace("_", "-")} "{name}" is still referenced in zone-policy')
+ if ipv6_name and dict_search_args(firewall, 'ipv6_name', ipv6_name) == None:
+ raise ConfigError(f'Invalid firewall ipv6-name "{ipv6_name}" referenced on interface {ifname}')
- return None
+ local_zone = False
+ zone_interfaces = []
-def cleanup_commands(firewall):
- commands = []
- commands_chains = []
- commands_sets = []
- for table in ['ip filter', 'ip6 filter']:
- name_node = 'name' if table == 'ip filter' else 'ipv6_name'
- chain_prefix = NAME_PREFIX if table == 'ip filter' else NAME6_PREFIX
- state_chain = 'VYOS_STATE_POLICY' if table == 'ip filter' else 'VYOS_STATE_POLICY6'
- iface_chains = nft_iface_chains if table == 'ip filter' else nft6_iface_chains
-
- geoip_list = []
- if firewall['geoip_updated']:
- geoip_key = 'deleted_ipv6_name' if table == 'ip6 filter' else 'deleted_name'
- geoip_list = dict_search_args(firewall, 'geoip_updated', geoip_key) or []
-
- json_str = cmd(f'nft -t -j list table {table}')
- obj = loads(json_str)
-
- if 'nftables' not in obj:
- continue
-
- for item in obj['nftables']:
- if 'chain' in item:
- chain = item['chain']['name']
- if chain in preserve_chains or chain.startswith("VZONE"):
- continue
+ if 'zone' in firewall:
+ for zone, zone_conf in firewall['zone'].items():
+ if 'local_zone' not in zone_conf and 'interface' not in zone_conf:
+ raise ConfigError(f'Zone "{zone}" has no interfaces and is not the local zone')
+
+ if 'local_zone' in zone_conf:
+ if local_zone:
+ raise ConfigError('There cannot be multiple local zones')
+ if 'interface' in zone_conf:
+ raise ConfigError('Local zone cannot have interfaces assigned')
+ if 'intra_zone_filtering' in zone_conf:
+ raise ConfigError('Local zone cannot use intra-zone-filtering')
+ local_zone = True
- if chain == state_chain:
- command = 'delete' if 'state_policy' not in firewall else 'flush'
- commands_chains.append(f'{command} chain {table} {chain}')
- elif dict_search_args(firewall, name_node, chain.replace(chain_prefix, "", 1)) != None:
- commands.append(f'flush chain {table} {chain}')
- else:
- commands_chains.append(f'delete chain {table} {chain}')
+ if 'interface' in zone_conf:
+ found_duplicates = [intf for intf in zone_conf['interface'] if intf in zone_interfaces]
- if 'rule' in item:
- rule = item['rule']
- chain = rule['chain']
- handle = rule['handle']
+ if found_duplicates:
+ raise ConfigError(f'Interfaces cannot be assigned to multiple zones')
- if chain in iface_chains:
- target, _ = next(dict_search_recursive(rule['expr'], 'target'))
+ zone_interfaces += zone_conf['interface']
- if target == state_chain and 'state_policy' not in firewall:
- commands.append(f'delete rule {table} {chain} handle {handle}')
+ if 'intra_zone_filtering' in zone_conf:
+ intra_zone = zone_conf['intra_zone_filtering']
- if target.startswith(chain_prefix):
- if dict_search_args(firewall, name_node, target.replace(chain_prefix, "", 1)) == None:
- commands.append(f'delete rule {table} {chain} handle {handle}')
+ if len(intra_zone) > 1:
+ raise ConfigError('Only one intra-zone-filtering action must be specified')
- if 'set' in item:
- set_name = item['set']['name']
+ if 'firewall' in intra_zone:
+ v4_name = dict_search_args(intra_zone, 'firewall', 'name')
+ if v4_name and not dict_search_args(firewall, 'name', v4_name):
+ raise ConfigError(f'Firewall name "{v4_name}" does not exist')
- if set_name.startswith('GEOIP_CC_') and set_name in geoip_list:
- commands_sets.append(f'delete set {table} {set_name}')
- continue
+ v6_name = dict_search_args(intra_zone, 'firewall', 'ipv6_name')
+ if v6_name and not dict_search_args(firewall, 'ipv6_name', v6_name):
+ raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist')
- if set_name.startswith("RECENT_"):
- commands_sets.append(f'delete set {table} {set_name}')
- continue
+ if not v4_name and not v6_name:
+ raise ConfigError('No firewall names specified for intra-zone-filtering')
+
+ if 'from' in zone_conf:
+ for from_zone, from_conf in zone_conf['from'].items():
+ if from_zone not in firewall['zone']:
+ raise ConfigError(f'Zone "{zone}" refers to a non-existent or deleted zone "{from_zone}"')
+
+ v4_name = dict_search_args(from_conf, 'firewall', 'name')
+ if v4_name and not dict_search_args(firewall, 'name', v4_name):
+ raise ConfigError(f'Firewall name "{v4_name}" does not exist')
+
+ v6_name = dict_search_args(from_conf, 'firewall', 'ipv6_name')
+ if v6_name and not dict_search_args(firewall, 'ipv6_name', v6_name):
+ raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist')
- for prefix, group_type in group_set_prefix.items():
- if set_name.startswith(prefix):
- group_name = set_name.replace(prefix, "", 1)
- if dict_search_args(firewall, 'group', group_type, group_name) != None:
- commands_sets.append(f'flush set {table} {set_name}')
- else:
- commands_sets.append(f'delete set {table} {set_name}')
- return commands + commands_chains + commands_sets
+ return None
def generate(firewall):
if not os.path.exists(nftables_conf):
firewall['first_install'] = True
- else:
- firewall['cleanup_commands'] = cleanup_commands(firewall)
+
+ if 'zone' in firewall:
+ for local_zone, local_zone_conf in firewall['zone'].items():
+ if 'local_zone' not in local_zone_conf:
+ continue
+
+ local_zone_conf['from_local'] = {}
+
+ for zone, zone_conf in firewall['zone'].items():
+ if zone == local_zone or 'from' not in zone_conf:
+ continue
+ if local_zone in zone_conf['from']:
+ local_zone_conf['from_local'][zone] = zone_conf['from'][local_zone]
render(nftables_conf, 'firewall/nftables.j2', firewall)
return None
@@ -521,26 +466,16 @@ def post_apply_trap(firewall):
cmd(base_cmd + ' '.join(objects))
-def state_policy_rule_exists():
- # Determine if state policy rules already exist in nft
- search_str = cmd(f'nft list chain ip filter VYOS_FW_FORWARD')
- return 'VYOS_STATE_POLICY' in search_str
-
def resync_policy_route():
# Update policy route as firewall groups were updated
- tmp = run(policy_route_conf_script)
+ tmp, out = rc_cmd(policy_route_conf_script)
if tmp > 0:
- Warning('Failed to re-apply policy route configuration!')
+ Warning(f'Failed to re-apply policy route configuration! {out}')
def apply(firewall):
- if 'first_install' in firewall:
- run('nfct helper add rpc inet tcp')
- run('nfct helper add rpc inet udp')
- run('nfct helper add tns inet tcp')
-
- install_result = run(f'nft -f {nftables_conf}')
+ install_result, output = rc_cmd(f'nft -f {nftables_conf}')
if install_result == 1:
- raise ConfigError('Failed to apply firewall')
+ raise ConfigError(f'Failed to apply firewall: {output}')
# set firewall group domain-group xxx
if 'group' in firewall:
@@ -563,13 +498,6 @@ def apply(firewall):
else:
call('systemctl stop vyos-domain-group-resolve.service')
- if 'state_policy' in firewall and not state_policy_rule_exists():
- for chain in ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL']:
- cmd(f'nft insert rule ip filter {chain} jump VYOS_STATE_POLICY')
-
- for chain in ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL']:
- cmd(f'nft insert rule ip6 filter {chain} jump VYOS_STATE_POLICY6')
-
apply_sysfs(firewall)
if firewall['policy_resync']:
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index ef745d737..8155f36c2 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -55,6 +55,7 @@ from vyos.util import chown
from vyos.util import cmd
from vyos.util import dict_search
from vyos.util import dict_search_args
+from vyos.util import is_list_equal
from vyos.util import makedir
from vyos.util import read_file
from vyos.util import write_file
@@ -274,7 +275,7 @@ def verify(openvpn):
elif v6remAddr and not v6loAddr:
raise ConfigError('IPv6 "remote-address" requires IPv6 "local-address"')
- if (v4loAddr == v4remAddr) or (v6remAddr == v4remAddr):
+ if is_list_equal(v4loAddr, v4remAddr) or is_list_equal(v6loAddr, v6remAddr):
raise ConfigError('"local-address" and "remote-address" cannot be the same')
if dict_search('local_host', openvpn) in dict_search('local_address', openvpn):
diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py
index 61bab2feb..8d738f55e 100755
--- a/src/conf_mode/interfaces-wireguard.py
+++ b/src/conf_mode/interfaces-wireguard.py
@@ -14,16 +14,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-import os
-
from sys import exit
-from copy import deepcopy
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configdict import get_interface_dict
-from vyos.configdict import node_changed
-from vyos.configdict import leaf_node_changed
+from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
from vyos.configverify import verify_address
from vyos.configverify import verify_bridge_delete
@@ -50,17 +46,20 @@ def get_config(config=None):
ifname, wireguard = get_interface_dict(conf, base)
# Check if a port was changed
- wireguard['port_changed'] = leaf_node_changed(conf, base + [ifname, 'port'])
+ tmp = is_node_changed(conf, base + [ifname, 'port'])
+ if tmp: wireguard['port_changed'] = {}
# Determine which Wireguard peer has been removed.
# Peers can only be removed with their public key!
- dict = {}
- tmp = node_changed(conf, base + [ifname, 'peer'], key_mangling=('-', '_'))
- for peer in (tmp or []):
- public_key = leaf_node_changed(conf, base + [ifname, 'peer', peer, 'public_key'])
- if public_key:
- dict = dict_merge({'peer_remove' : {peer : {'public_key' : public_key[0]}}}, dict)
- wireguard.update(dict)
+ if 'peer' in wireguard:
+ peer_remove = {}
+ for peer, peer_config in wireguard['peer'].items():
+ # T4702: If anything on a peer changes we remove the peer first and re-add it
+ if is_node_changed(conf, base + [ifname, 'peer', peer]):
+ if 'public_key' in peer_config:
+ peer_remove = dict_merge({'peer_remove' : {peer : peer_config['public_key']}}, peer_remove)
+ if peer_remove:
+ wireguard.update(peer_remove)
return wireguard
@@ -81,12 +80,11 @@ def verify(wireguard):
if 'peer' not in wireguard:
raise ConfigError('At least one Wireguard peer is required!')
- if 'port' in wireguard and wireguard['port_changed']:
+ if 'port' in wireguard and 'port_changed' in wireguard:
listen_port = int(wireguard['port'])
if check_port_availability('0.0.0.0', listen_port, 'udp') is not True:
- raise ConfigError(
- f'The UDP port {listen_port} is busy or unavailable and cannot be used for the interface'
- )
+ raise ConfigError(f'UDP port {listen_port} is busy or unavailable and '
+ 'cannot be used for the interface!')
# run checks on individual configured WireGuard peer
for tmp in wireguard['peer']:
diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py
index 97b3a6396..a14a992ae 100755
--- a/src/conf_mode/interfaces-wwan.py
+++ b/src/conf_mode/interfaces-wwan.py
@@ -116,7 +116,7 @@ def generate(wwan):
# disconnect - e.g. happens during RF signal loss. The script watches every
# WWAN interface - so there is only one instance.
if not os.path.exists(cron_script):
- write_file(cron_script, '*/5 * * * * root /usr/libexec/vyos/vyos-check-wwan.py')
+ write_file(cron_script, '*/5 * * * * root /usr/libexec/vyos/vyos-check-wwan.py\n')
return None
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index e75418ba5..8b1a5a720 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -147,14 +147,10 @@ def verify(nat):
Warning(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')
addr = dict_search('translation.address', config)
- if addr != None:
- if addr != 'masquerade' and not is_ip_network(addr):
- for ip in addr.split('-'):
- if not is_addr_assigned(ip):
- Warning(f'IP address {ip} does not exist on the system!')
- elif 'exclude' not in config:
- raise ConfigError(f'{err_msg}\n' \
- 'translation address not specified')
+ if addr != None and addr != 'masquerade' and not is_ip_network(addr):
+ for ip in addr.split('-'):
+ if not is_addr_assigned(ip):
+ Warning(f'IP address {ip} does not exist on the system!')
# common rule verification
verify_rule(config, err_msg)
@@ -167,14 +163,8 @@ def verify(nat):
if 'inbound_interface' not in config:
raise ConfigError(f'{err_msg}\n' \
'inbound-interface not specified')
- else:
- if config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces():
- Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')
-
-
- if dict_search('translation.address', config) == None and 'exclude' not in config:
- raise ConfigError(f'{err_msg}\n' \
- 'translation address not specified')
+ elif config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces():
+ Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')
# common rule verification
verify_rule(config, err_msg)
@@ -193,6 +183,9 @@ def verify(nat):
return None
def generate(nat):
+ if not os.path.exists(nftables_nat_config):
+ nat['first_install'] = True
+
render(nftables_nat_config, 'firewall/nftables-nat.j2', nat)
render(nftables_static_nat_conf, 'firewall/nftables-static-nat.j2', nat)
@@ -201,7 +194,9 @@ def generate(nat):
if tmp > 0:
raise ConfigError('Configuration file errors encountered!')
- tmp = run(f'nft -c -f {nftables_nat_config}')
+ tmp = run(f'nft -c -f {nftables_static_nat_conf}')
+ if tmp > 0:
+ raise ConfigError('Configuration file errors encountered!')
return None
diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py
index f64102d88..d8f913b0c 100755
--- a/src/conf_mode/nat66.py
+++ b/src/conf_mode/nat66.py
@@ -36,7 +36,7 @@ airbag.enable()
k_mod = ['nft_nat', 'nft_chain_nat']
-nftables_nat66_config = '/tmp/vyos-nat66-rules.nft'
+nftables_nat66_config = '/run/nftables_nat66.nft'
ndppd_config = '/run/ndppd/ndppd.conf'
def get_handler(json, chain, target):
@@ -147,6 +147,9 @@ def verify(nat):
return None
def generate(nat):
+ if not os.path.exists(nftables_nat66_config):
+ nat['first_install'] = True
+
render(nftables_nat66_config, 'firewall/nftables-nat66.j2', nat, permission=0o755)
render(ndppd_config, 'ndppd/ndppd.conf.j2', nat, permission=0o755)
return None
@@ -154,15 +157,15 @@ def generate(nat):
def apply(nat):
if not nat:
return None
- cmd(f'{nftables_nat66_config}')
+
+ cmd(f'nft -f {nftables_nat66_config}')
+
if 'deleted' in nat or not dict_search('source.rule', nat):
cmd('systemctl stop ndppd')
if os.path.isfile(ndppd_config):
os.unlink(ndppd_config)
else:
cmd('systemctl restart ndppd')
- if os.path.isfile(nftables_nat66_config):
- os.unlink(nftables_nat66_config)
return None
diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py
index 9fddbd2c6..00539b9c7 100755
--- a/src/conf_mode/policy-route.py
+++ b/src/conf_mode/policy-route.py
@@ -92,7 +92,7 @@ def get_config(config=None):
return policy
-def verify_rule(policy, name, rule_conf, ipv6):
+def verify_rule(policy, name, rule_conf, ipv6, rule_id):
icmp = 'icmp' if not ipv6 else 'icmpv6'
if icmp in rule_conf:
icmp_defined = False
@@ -166,7 +166,7 @@ def verify(policy):
for name, pol_conf in policy[route].items():
if 'rule' in pol_conf:
for rule_id, rule_conf in pol_conf['rule'].items():
- verify_rule(policy, name, rule_conf, ipv6)
+ verify_rule(policy, name, rule_conf, ipv6, rule_id)
for ifname, if_policy in policy['interfaces'].items():
name = dict_search_args(if_policy, 'route')
diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py
index 3008a20e0..a0d288e91 100755
--- a/src/conf_mode/policy.py
+++ b/src/conf_mode/policy.py
@@ -23,8 +23,42 @@ from vyos.util import dict_search
from vyos import ConfigError
from vyos import frr
from vyos import airbag
+
airbag.enable()
+
+def community_action_compatibility(actions: dict) -> bool:
+ """
+ Check compatibility of values in community and large community sections
+ :param actions: dictionary with community
+ :type actions: dict
+ :return: true if compatible, false if not
+ :rtype: bool
+ """
+ if ('none' in actions) and ('replace' in actions or 'add' in actions):
+ return False
+ if 'replace' in actions and 'add' in actions:
+ return False
+ if ('delete' in actions) and ('none' in actions or 'replace' in actions):
+ return False
+ return True
+
+
+def extcommunity_action_compatibility(actions: dict) -> bool:
+ """
+ Check compatibility of values in extended community sections
+ :param actions: dictionary with community
+ :type actions: dict
+ :return: true if compatible, false if not
+ :rtype: bool
+ """
+ if ('none' in actions) and (
+ 'rt' in actions or 'soo' in actions or 'bandwidth' in actions or 'bandwidth_non_transitive' in actions):
+ return False
+ if ('bandwidth_non_transitive' in actions) and ('bandwidth' not in actions):
+ return False
+ return True
+
def routing_policy_find(key, dictionary):
# Recursively traverse a dictionary and extract the value assigned to
# a given key as generator object. This is made for routing policies,
@@ -46,6 +80,7 @@ def routing_policy_find(key, dictionary):
for result in routing_policy_find(key, d):
yield result
+
def get_config(config=None):
if config:
conf = config
@@ -53,7 +88,8 @@ def get_config(config=None):
conf = Config()
base = ['policy']
- policy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True,
+ policy = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
no_tag_node_value_mangle=True)
# We also need some additional information from the config, prefix-lists
@@ -67,12 +103,14 @@ def get_config(config=None):
policy = dict_merge(tmp, policy)
return policy
+
def verify(policy):
if not policy:
return None
for policy_type in ['access_list', 'access_list6', 'as_path_list',
- 'community_list', 'extcommunity_list', 'large_community_list',
+ 'community_list', 'extcommunity_list',
+ 'large_community_list',
'prefix_list', 'prefix_list6', 'route_map']:
# Bail out early and continue with next policy type
if policy_type not in policy:
@@ -97,15 +135,18 @@ def verify(policy):
if 'source' not in rule_config:
raise ConfigError(f'A source {mandatory_error}')
- if int(instance) in range(100, 200) or int(instance) in range(2000, 2700):
+ if int(instance) in range(100, 200) or int(
+ instance) in range(2000, 2700):
if 'destination' not in rule_config:
- raise ConfigError(f'A destination {mandatory_error}')
+ raise ConfigError(
+ f'A destination {mandatory_error}')
if policy_type == 'access_list6':
if 'source' not in rule_config:
raise ConfigError(f'A source {mandatory_error}')
- if policy_type in ['as_path_list', 'community_list', 'extcommunity_list',
+ if policy_type in ['as_path_list', 'community_list',
+ 'extcommunity_list',
'large_community_list']:
if 'regex' not in rule_config:
raise ConfigError(f'A regex {mandatory_error}')
@@ -115,10 +156,10 @@ def verify(policy):
raise ConfigError(f'A prefix {mandatory_error}')
if rule_config in entries:
- raise ConfigError(f'Rule "{rule}" contains a duplicate prefix definition!')
+ raise ConfigError(
+ f'Rule "{rule}" contains a duplicate prefix definition!')
entries.append(rule_config)
-
# route-maps tend to be a bit more complex so they get their own verify() section
if 'route_map' in policy:
for route_map, route_map_config in policy['route_map'].items():
@@ -127,19 +168,23 @@ def verify(policy):
for rule, rule_config in route_map_config['rule'].items():
# Specified community-list must exist
- tmp = dict_search('match.community.community_list', rule_config)
+ tmp = dict_search('match.community.community_list',
+ rule_config)
if tmp and tmp not in policy.get('community_list', []):
raise ConfigError(f'community-list {tmp} does not exist!')
# Specified extended community-list must exist
tmp = dict_search('match.extcommunity', rule_config)
if tmp and tmp not in policy.get('extcommunity_list', []):
- raise ConfigError(f'extcommunity-list {tmp} does not exist!')
+ raise ConfigError(
+ f'extcommunity-list {tmp} does not exist!')
# Specified large-community-list must exist
- tmp = dict_search('match.large_community.large_community_list', rule_config)
+ tmp = dict_search('match.large_community.large_community_list',
+ rule_config)
if tmp and tmp not in policy.get('large_community_list', []):
- raise ConfigError(f'large-community-list {tmp} does not exist!')
+ raise ConfigError(
+ f'large-community-list {tmp} does not exist!')
# Specified prefix-list must exist
tmp = dict_search('match.ip.address.prefix_list', rule_config)
@@ -147,49 +192,87 @@ def verify(policy):
raise ConfigError(f'prefix-list {tmp} does not exist!')
# Specified prefix-list must exist
- tmp = dict_search('match.ipv6.address.prefix_list', rule_config)
+ tmp = dict_search('match.ipv6.address.prefix_list',
+ rule_config)
if tmp and tmp not in policy.get('prefix_list6', []):
raise ConfigError(f'prefix-list6 {tmp} does not exist!')
-
+
# Specified access_list6 in nexthop must exist
- tmp = dict_search('match.ipv6.nexthop.access_list', rule_config)
+ tmp = dict_search('match.ipv6.nexthop.access_list',
+ rule_config)
if tmp and tmp not in policy.get('access_list6', []):
raise ConfigError(f'access_list6 {tmp} does not exist!')
# Specified prefix-list6 in nexthop must exist
- tmp = dict_search('match.ipv6.nexthop.prefix_list', rule_config)
+ tmp = dict_search('match.ipv6.nexthop.prefix_list',
+ rule_config)
if tmp and tmp not in policy.get('prefix_list6', []):
raise ConfigError(f'prefix-list6 {tmp} does not exist!')
+ tmp = dict_search('set.community.delete', rule_config)
+ if tmp and tmp not in policy.get('community_list', []):
+ raise ConfigError(f'community-list {tmp} does not exist!')
+
+ tmp = dict_search('set.large_community.delete',
+ rule_config)
+ if tmp and tmp not in policy.get('large_community_list', []):
+ raise ConfigError(
+ f'large-community-list {tmp} does not exist!')
+
+ if 'set' in rule_config:
+ rule_action = rule_config['set']
+ if 'community' in rule_action:
+ if not community_action_compatibility(
+ rule_action['community']):
+ raise ConfigError(
+ f'Unexpected combination between action replace, add, delete or none in community')
+ if 'large_community' in rule_action:
+ if not community_action_compatibility(
+ rule_action['large_community']):
+ raise ConfigError(
+ f'Unexpected combination between action replace, add, delete or none in large-community')
+ if 'extcommunity' in rule_action:
+ if not extcommunity_action_compatibility(
+ rule_action['extcommunity']):
+ raise ConfigError(
+ f'Unexpected combination between none, rt, soo, bandwidth, bandwidth-non-transitive in extended-community')
# When routing protocols are active some use prefix-lists, route-maps etc.
# to apply the systems routing policy to the learned or redistributed routes.
# When the "routing policy" changes and policies, route-maps etc. are deleted,
# it is our responsibility to verify that the policy can not be deleted if it
# is used by any routing protocol
if 'protocols' in policy:
- for policy_type in ['access_list', 'access_list6', 'as_path_list', 'community_list',
- 'extcommunity_list', 'large_community_list', 'prefix_list', 'route_map']:
+ for policy_type in ['access_list', 'access_list6', 'as_path_list',
+ 'community_list',
+ 'extcommunity_list', 'large_community_list',
+ 'prefix_list', 'route_map']:
if policy_type in policy:
- for policy_name in list(set(routing_policy_find(policy_type, policy['protocols']))):
+ for policy_name in list(set(routing_policy_find(policy_type,
+ policy[
+ 'protocols']))):
found = False
if policy_name in policy[policy_type]:
found = True
# BGP uses prefix-list for selecting both an IPv4 or IPv6 AFI related
# list - we need to go the extra mile here and check both prefix-lists
- if policy_type == 'prefix_list' and 'prefix_list6' in policy and policy_name in policy['prefix_list6']:
+ if policy_type == 'prefix_list' and 'prefix_list6' in policy and policy_name in \
+ policy['prefix_list6']:
found = True
if not found:
- tmp = policy_type.replace('_','-')
- raise ConfigError(f'Can not delete {tmp} "{policy_name}", still in use!')
+ tmp = policy_type.replace('_', '-')
+ raise ConfigError(
+ f'Can not delete {tmp} "{policy_name}", still in use!')
return None
+
def generate(policy):
if not policy:
return None
policy['new_frr_config'] = render_to_string('frr/policy.frr.j2', policy)
return None
+
def apply(policy):
bgp_daemon = 'bgpd'
zebra_daemon = 'zebra'
@@ -203,7 +286,8 @@ def apply(policy):
frr_cfg.modify_section(r'^bgp community-list .*')
frr_cfg.modify_section(r'^bgp extcommunity-list .*')
frr_cfg.modify_section(r'^bgp large-community-list .*')
- frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit', remove_stop_mark=True)
+ frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit',
+ remove_stop_mark=True)
if 'new_frr_config' in policy:
frr_cfg.add_before(frr.default_add_before, policy['new_frr_config'])
frr_cfg.commit_configuration(bgp_daemon)
@@ -214,13 +298,15 @@ def apply(policy):
frr_cfg.modify_section(r'^ipv6 access-list .*')
frr_cfg.modify_section(r'^ip prefix-list .*')
frr_cfg.modify_section(r'^ipv6 prefix-list .*')
- frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit', remove_stop_mark=True)
+ frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit',
+ remove_stop_mark=True)
if 'new_frr_config' in policy:
frr_cfg.add_before(frr.default_add_before, policy['new_frr_config'])
frr_cfg.commit_configuration(zebra_daemon)
return None
+
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py
index 5dafd26d0..cb8ea3be4 100755
--- a/src/conf_mode/protocols_isis.py
+++ b/src/conf_mode/protocols_isis.py
@@ -203,6 +203,28 @@ def verify(isis):
if list(set(global_range) & set(local_range)):
raise ConfigError(f'Segment-Routing Global Block ({g_low_label_value}/{g_high_label_value}) '\
f'conflicts with Local Block ({l_low_label_value}/{l_high_label_value})!')
+
+ # Check for a blank or invalid value per prefix
+ if dict_search('segment_routing.prefix', isis):
+ for prefix, prefix_config in isis['segment_routing']['prefix'].items():
+ if 'absolute' in prefix_config:
+ if prefix_config['absolute'].get('value') is None:
+ raise ConfigError(f'Segment routing prefix {prefix} absolute value cannot be blank.')
+ elif 'index' in prefix_config:
+ if prefix_config['index'].get('value') is None:
+ raise ConfigError(f'Segment routing prefix {prefix} index value cannot be blank.')
+
+ # Check for explicit-null and no-php-flag configured at the same time per prefix
+ if dict_search('segment_routing.prefix', isis):
+ for prefix, prefix_config in isis['segment_routing']['prefix'].items():
+ if 'absolute' in prefix_config:
+ if ("explicit_null" in prefix_config['absolute']) and ("no_php_flag" in prefix_config['absolute']):
+ raise ConfigError(f'Segment routing prefix {prefix} cannot have both explicit-null '\
+ f'and no-php-flag configured at the same time.')
+ elif 'index' in prefix_config:
+ if ("explicit_null" in prefix_config['index']) and ("no_php_flag" in prefix_config['index']):
+ raise ConfigError(f'Segment routing prefix {prefix} cannot have both explicit-null '\
+ f'and no-php-flag configured at the same time.')
return None
diff --git a/src/conf_mode/protocols_nhrp.py b/src/conf_mode/protocols_nhrp.py
index b247ce2ab..d28ced4fd 100755
--- a/src/conf_mode/protocols_nhrp.py
+++ b/src/conf_mode/protocols_nhrp.py
@@ -14,10 +14,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import os
+
from vyos.config import Config
from vyos.configdict import node_changed
-from vyos.firewall import find_nftables_rule
-from vyos.firewall import remove_nftables_rule
from vyos.template import render
from vyos.util import process_named_running
from vyos.util import run
@@ -26,6 +26,7 @@ from vyos import airbag
airbag.enable()
opennhrp_conf = '/run/opennhrp/opennhrp.conf'
+nhrp_nftables_conf = '/run/nftables_nhrp.conf'
def get_config(config=None):
if config:
@@ -84,28 +85,23 @@ def verify(nhrp):
return None
def generate(nhrp):
+ if not os.path.exists(nhrp_nftables_conf):
+ nhrp['first_install'] = True
+
render(opennhrp_conf, 'nhrp/opennhrp.conf.j2', nhrp)
+ render(nhrp_nftables_conf, 'nhrp/nftables.conf.j2', nhrp)
return None
def apply(nhrp):
- if 'tunnel' in nhrp:
- for tunnel, tunnel_conf in nhrp['tunnel'].items():
- if 'source_address' in nhrp['if_tunnel'][tunnel]:
- comment = f'VYOS_NHRP_{tunnel}'
- source_address = nhrp['if_tunnel'][tunnel]['source_address']
-
- rule_handle = find_nftables_rule('ip filter', 'VYOS_FW_OUTPUT', ['ip protocol gre', f'ip saddr {source_address}', 'ip daddr 224.0.0.0/4'])
- if not rule_handle:
- run(f'sudo nft insert rule ip filter VYOS_FW_OUTPUT ip protocol gre ip saddr {source_address} ip daddr 224.0.0.0/4 counter drop comment "{comment}"')
-
- for tunnel in nhrp['del_tunnels']:
- comment = f'VYOS_NHRP_{tunnel}'
- rule_handle = find_nftables_rule('ip filter', 'VYOS_FW_OUTPUT', [f'comment "{comment}"'])
- if rule_handle:
- remove_nftables_rule('ip filter', 'VYOS_FW_OUTPUT', rule_handle)
+ nft_rc = run(f'nft -f {nhrp_nftables_conf}')
+ if nft_rc != 0:
+ raise ConfigError('Failed to apply NHRP tunnel firewall rules')
action = 'restart' if nhrp and 'tunnel' in nhrp else 'stop'
- run(f'systemctl {action} opennhrp.service')
+ service_rc = run(f'systemctl {action} opennhrp.service')
+ if service_rc != 0:
+ raise ConfigError(f'Failed to {action} the NHRP service')
+
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py
index 5b4874ba2..0582d32be 100755
--- a/src/conf_mode/protocols_ospf.py
+++ b/src/conf_mode/protocols_ospf.py
@@ -198,6 +198,58 @@ def verify(ospf):
if 'master' not in tmp or tmp['master'] != vrf:
raise ConfigError(f'Interface {interface} is not a member of VRF {vrf}!')
+ # Segment routing checks
+ if dict_search('segment_routing.global_block', ospf):
+ g_high_label_value = dict_search('segment_routing.global_block.high_label_value', ospf)
+ g_low_label_value = dict_search('segment_routing.global_block.low_label_value', ospf)
+
+ # If segment routing global block high or low value is blank, throw error
+ if not (g_low_label_value or g_high_label_value):
+ raise ConfigError('Segment routing global-block requires both low and high value!')
+
+ # If segment routing global block low value is higher than the high value, throw error
+ if int(g_low_label_value) > int(g_high_label_value):
+ raise ConfigError('Segment routing global-block low value must be lower than high value')
+
+ if dict_search('segment_routing.local_block', ospf):
+ if dict_search('segment_routing.global_block', ospf) == None:
+ raise ConfigError('Segment routing local-block requires global-block to be configured!')
+
+ l_high_label_value = dict_search('segment_routing.local_block.high_label_value', ospf)
+ l_low_label_value = dict_search('segment_routing.local_block.low_label_value', ospf)
+
+ # If segment routing local-block high or low value is blank, throw error
+ if not (l_low_label_value or l_high_label_value):
+ raise ConfigError('Segment routing local-block requires both high and low value!')
+
+ # If segment routing local-block low value is higher than the high value, throw error
+ if int(l_low_label_value) > int(l_high_label_value):
+ raise ConfigError('Segment routing local-block low value must be lower than high value')
+
+ # local-block most live outside global block
+ global_range = range(int(g_low_label_value), int(g_high_label_value) +1)
+ local_range = range(int(l_low_label_value), int(l_high_label_value) +1)
+
+ # Check for overlapping ranges
+ if list(set(global_range) & set(local_range)):
+ raise ConfigError(f'Segment-Routing Global Block ({g_low_label_value}/{g_high_label_value}) '\
+ f'conflicts with Local Block ({l_low_label_value}/{l_high_label_value})!')
+
+ # Check for a blank or invalid value per prefix
+ if dict_search('segment_routing.prefix', ospf):
+ for prefix, prefix_config in ospf['segment_routing']['prefix'].items():
+ if 'index' in prefix_config:
+ if prefix_config['index'].get('value') is None:
+ raise ConfigError(f'Segment routing prefix {prefix} index value cannot be blank.')
+
+ # Check for explicit-null and no-php-flag configured at the same time per prefix
+ if dict_search('segment_routing.prefix', ospf):
+ for prefix, prefix_config in ospf['segment_routing']['prefix'].items():
+ if 'index' in prefix_config:
+ if ("explicit_null" in prefix_config['index']) and ("no_php_flag" in prefix_config['index']):
+ raise ConfigError(f'Segment routing prefix {prefix} cannot have both explicit-null '\
+ f'and no-php-flag configured at the same time.')
+
return None
def generate(ospf):
diff --git a/src/conf_mode/service_console-server.py b/src/conf_mode/service_console-server.py
index a2e411e49..ee4fe42ab 100755
--- a/src/conf_mode/service_console-server.py
+++ b/src/conf_mode/service_console-server.py
@@ -61,6 +61,7 @@ def verify(proxy):
if not proxy:
return None
+ aliases = []
processes = process_iter(['name', 'cmdline'])
if 'device' in proxy:
for device, device_config in proxy['device'].items():
@@ -75,6 +76,12 @@ def verify(proxy):
if 'ssh' in device_config and 'port' not in device_config['ssh']:
raise ConfigError(f'Port "{device}" requires SSH port to be set!')
+ if 'alias' in device_config:
+ if device_config['alias'] in aliases:
+ raise ConfigError("Console aliases must be unique")
+ else:
+ aliases.append(device_config['alias'])
+
return None
def generate(proxy):
diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py
index 61f484129..e9afd6a55 100755
--- a/src/conf_mode/service_ipoe-server.py
+++ b/src/conf_mode/service_ipoe-server.py
@@ -15,266 +15,34 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
-import re
-from copy import deepcopy
-from stat import S_IRUSR, S_IWUSR, S_IRGRP
from sys import exit
from vyos.config import Config
+from vyos.configdict import get_accel_dict
+from vyos.configverify import verify_accel_ppp_base_service
+from vyos.configverify import verify_interface_exists
from vyos.template import render
-from vyos.template import is_ipv4
-from vyos.template import is_ipv6
-from vyos.util import call, get_half_cpus
+from vyos.util import call
+from vyos.util import dict_search
from vyos import ConfigError
-
from vyos import airbag
airbag.enable()
ipoe_conf = '/run/accel-pppd/ipoe.conf'
ipoe_chap_secrets = '/run/accel-pppd/ipoe.chap-secrets'
-default_config_data = {
- 'auth_mode': 'local',
- 'auth_interfaces': [],
- 'chap_secrets_file': ipoe_chap_secrets, # used in Jinja2 template
- 'interfaces': [],
- 'dnsv4': [],
- 'dnsv6': [],
- 'client_named_ip_pool': [],
- 'client_ipv6_pool': [],
- 'client_ipv6_delegate_prefix': [],
- 'radius_server': [],
- 'radius_acct_inter_jitter': '',
- 'radius_acct_tmo': '3',
- 'radius_max_try': '3',
- 'radius_timeout': '3',
- 'radius_nas_id': '',
- 'radius_nas_ip': '',
- 'radius_source_address': '',
- 'radius_shaper_attr': '',
- 'radius_shaper_enable': False,
- 'radius_shaper_multiplier': '',
- 'radius_shaper_vendor': '',
- 'radius_dynamic_author': '',
- 'thread_cnt': get_half_cpus()
-}
-
def get_config(config=None):
if config:
conf = config
else:
conf = Config()
- base_path = ['service', 'ipoe-server']
- if not conf.exists(base_path):
+ base = ['service', 'ipoe-server']
+ if not conf.exists(base):
return None
- conf.set_level(base_path)
- ipoe = deepcopy(default_config_data)
-
- for interface in conf.list_nodes(['interface']):
- tmp = {
- 'mode': 'L2',
- 'name': interface,
- 'shared': '1',
- # may need a config option, can be dhcpv4 or up for unclassified pkts
- 'sess_start': 'dhcpv4',
- 'range': None,
- 'ifcfg': '1',
- 'vlan_mon': []
- }
-
- conf.set_level(base_path + ['interface', interface])
-
- if conf.exists(['network-mode']):
- tmp['mode'] = conf.return_value(['network-mode'])
-
- if conf.exists(['network']):
- mode = conf.return_value(['network'])
- if mode == 'vlan':
- tmp['shared'] = '0'
-
- if conf.exists(['vlan-id']):
- tmp['vlan_mon'] += conf.return_values(['vlan-id'])
-
- if conf.exists(['vlan-range']):
- tmp['vlan_mon'] += conf.return_values(['vlan-range'])
-
- if conf.exists(['client-subnet']):
- tmp['range'] = conf.return_value(['client-subnet'])
-
- ipoe['interfaces'].append(tmp)
-
- conf.set_level(base_path)
-
- if conf.exists(['name-server']):
- for name_server in conf.return_values(['name-server']):
- if is_ipv4(name_server):
- ipoe['dnsv4'].append(name_server)
- else:
- ipoe['dnsv6'].append(name_server)
-
- if conf.exists(['authentication', 'mode']):
- ipoe['auth_mode'] = conf.return_value(['authentication', 'mode'])
-
- if conf.exists(['authentication', 'interface']):
- for interface in conf.list_nodes(['authentication', 'interface']):
- tmp = {
- 'name': interface,
- 'mac': []
- }
- for mac in conf.list_nodes(['authentication', 'interface', interface, 'mac-address']):
- client = {
- 'address': mac,
- 'rate_download': '',
- 'rate_upload': '',
- 'vlan_id': ''
- }
- conf.set_level(base_path + ['authentication', 'interface', interface, 'mac-address', mac])
-
- if conf.exists(['rate-limit', 'download']):
- client['rate_download'] = conf.return_value(['rate-limit', 'download'])
-
- if conf.exists(['rate-limit', 'upload']):
- client['rate_upload'] = conf.return_value(['rate-limit', 'upload'])
-
- if conf.exists(['vlan-id']):
- client['vlan'] = conf.return_value(['vlan-id'])
-
- tmp['mac'].append(client)
-
- ipoe['auth_interfaces'].append(tmp)
-
- conf.set_level(base_path)
-
- #
- # authentication mode radius servers and settings
- if conf.exists(['authentication', 'mode', 'radius']):
- for server in conf.list_nodes(['authentication', 'radius', 'server']):
- radius = {
- 'server' : server,
- 'key' : '',
- 'fail_time' : 0,
- 'port' : '1812',
- 'acct_port' : '1813'
- }
-
- conf.set_level(base_path + ['authentication', 'radius', 'server', server])
-
- if conf.exists(['fail-time']):
- radius['fail_time'] = conf.return_value(['fail-time'])
-
- if conf.exists(['port']):
- radius['port'] = conf.return_value(['port'])
-
- if conf.exists(['acct-port']):
- radius['acct_port'] = conf.return_value(['acct-port'])
-
- if conf.exists(['key']):
- radius['key'] = conf.return_value(['key'])
-
- if not conf.exists(['disable']):
- ipoe['radius_server'].append(radius)
-
- #
- # advanced radius-setting
- conf.set_level(base_path + ['authentication', 'radius'])
-
- if conf.exists(['acct-interim-jitter']):
- ipoe['radius_acct_inter_jitter'] = conf.return_value(['acct-interim-jitter'])
-
- if conf.exists(['acct-timeout']):
- ipoe['radius_acct_tmo'] = conf.return_value(['acct-timeout'])
-
- if conf.exists(['max-try']):
- ipoe['radius_max_try'] = conf.return_value(['max-try'])
-
- if conf.exists(['timeout']):
- ipoe['radius_timeout'] = conf.return_value(['timeout'])
-
- if conf.exists(['nas-identifier']):
- ipoe['radius_nas_id'] = conf.return_value(['nas-identifier'])
-
- if conf.exists(['nas-ip-address']):
- ipoe['radius_nas_ip'] = conf.return_value(['nas-ip-address'])
-
- if conf.exists(['rate-limit', 'attribute']):
- ipoe['radius_shaper_attr'] = conf.return_value(['rate-limit', 'attribute'])
-
- if conf.exists(['rate-limit', 'enable']):
- ipoe['radius_shaper_enable'] = True
-
- if conf.exists(['rate-limit', 'multiplier']):
- ipoe['radius_shaper_multiplier'] = conf.return_value(['rate-limit', 'multiplier'])
-
- if conf.exists(['rate-limit', 'vendor']):
- ipoe['radius_shaper_vendor'] = conf.return_value(['rate-limit', 'vendor'])
-
- if conf.exists(['source-address']):
- ipoe['radius_source_address'] = conf.return_value(['source-address'])
-
- # Dynamic Authorization Extensions (DOA)/Change Of Authentication (COA)
- if conf.exists(['dynamic-author']):
- dae = {
- 'port' : '',
- 'server' : '',
- 'key' : ''
- }
-
- if conf.exists(['dynamic-author', 'server']):
- dae['server'] = conf.return_value(['dynamic-author', 'server'])
-
- if conf.exists(['dynamic-author', 'port']):
- dae['port'] = conf.return_value(['dynamic-author', 'port'])
-
- if conf.exists(['dynamic-author', 'key']):
- dae['key'] = conf.return_value(['dynamic-author', 'key'])
-
- ipoe['radius_dynamic_author'] = dae
-
-
- conf.set_level(base_path)
- # Named client-ip-pool
- if conf.exists(['client-ip-pool', 'name']):
- for name in conf.list_nodes(['client-ip-pool', 'name']):
- tmp = {
- 'name': name,
- 'gateway_address': '',
- 'subnet': ''
- }
-
- if conf.exists(['client-ip-pool', 'name', name, 'gateway-address']):
- tmp['gateway_address'] += conf.return_value(['client-ip-pool', 'name', name, 'gateway-address'])
- if conf.exists(['client-ip-pool', 'name', name, 'subnet']):
- tmp['subnet'] += conf.return_value(['client-ip-pool', 'name', name, 'subnet'])
-
- ipoe['client_named_ip_pool'].append(tmp)
-
- if conf.exists(['client-ipv6-pool', 'prefix']):
- for prefix in conf.list_nodes(['client-ipv6-pool', 'prefix']):
- tmp = {
- 'prefix': prefix,
- 'mask': '64'
- }
-
- if conf.exists(['client-ipv6-pool', 'prefix', prefix, 'mask']):
- tmp['mask'] = conf.return_value(['client-ipv6-pool', 'prefix', prefix, 'mask'])
-
- ipoe['client_ipv6_pool'].append(tmp)
-
-
- if conf.exists(['client-ipv6-pool', 'delegate']):
- for prefix in conf.list_nodes(['client-ipv6-pool', 'delegate']):
- tmp = {
- 'prefix': prefix,
- 'mask': ''
- }
-
- if conf.exists(['client-ipv6-pool', 'delegate', prefix, 'delegation-prefix']):
- tmp['mask'] = conf.return_value(['client-ipv6-pool', 'delegate', prefix, 'delegation-prefix'])
-
- ipoe['client_ipv6_delegate_prefix'].append(tmp)
-
+ # retrieve common dictionary keys
+ ipoe = get_accel_dict(conf, base, ipoe_chap_secrets)
return ipoe
@@ -282,26 +50,17 @@ def verify(ipoe):
if not ipoe:
return None
- if not ipoe['interfaces']:
+ if 'interface' not in ipoe:
raise ConfigError('No IPoE interface configured')
- if len(ipoe['dnsv4']) > 2:
- raise ConfigError('Not more then two IPv4 DNS name-servers can be configured')
-
- if len(ipoe['dnsv6']) > 3:
- raise ConfigError('Not more then three IPv6 DNS name-servers can be configured')
-
- if ipoe['auth_mode'] == 'radius':
- if len(ipoe['radius_server']) == 0:
- raise ConfigError('RADIUS authentication requires at least one server')
+ for interface in ipoe['interface']:
+ verify_interface_exists(interface)
- for radius in ipoe['radius_server']:
- if not radius['key']:
- server = radius['server']
- raise ConfigError(f'Missing RADIUS secret key for server "{ server }"')
+ #verify_accel_ppp_base_service(ipoe, local_users=False)
- if ipoe['client_ipv6_delegate_prefix'] and not ipoe['client_ipv6_pool']:
- raise ConfigError('IPoE IPv6 deletate-prefix requires IPv6 prefix to be configured!')
+ if 'client_ipv6_pool' in ipoe:
+ if 'delegate' in ipoe['client_ipv6_pool'] and 'prefix' not in ipoe['client_ipv6_pool']:
+ raise ConfigError('IPoE IPv6 deletate-prefix requires IPv6 prefix to be configured!')
return None
@@ -312,27 +71,23 @@ def generate(ipoe):
render(ipoe_conf, 'accel-ppp/ipoe.config.j2', ipoe)
- if ipoe['auth_mode'] == 'local':
- render(ipoe_chap_secrets, 'accel-ppp/chap-secrets.ipoe.j2', ipoe)
- os.chmod(ipoe_chap_secrets, S_IRUSR | S_IWUSR | S_IRGRP)
-
- else:
- if os.path.exists(ipoe_chap_secrets):
- os.unlink(ipoe_chap_secrets)
-
+ if dict_search('authentication.mode', ipoe) == 'local':
+ render(ipoe_chap_secrets, 'accel-ppp/chap-secrets.ipoe.j2',
+ ipoe, permission=0o640)
return None
def apply(ipoe):
+ systemd_service = 'accel-ppp@ipoe.service'
if ipoe == None:
- call('systemctl stop accel-ppp@ipoe.service')
+ call(f'systemctl stop {systemd_service}')
for file in [ipoe_conf, ipoe_chap_secrets]:
if os.path.exists(file):
os.unlink(file)
return None
- call('systemctl restart accel-ppp@ipoe.service')
+ call(f'systemctl reload-or-restart {systemd_service}')
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py
index 53df006a4..427cb6911 100755
--- a/src/conf_mode/service_monitoring_telegraf.py
+++ b/src/conf_mode/service_monitoring_telegraf.py
@@ -42,7 +42,7 @@ systemd_override = '/etc/systemd/system/telegraf.service.d/10-override.conf'
def get_nft_filter_chains():
""" Get nft chains for table filter """
- nft = cmd('nft --json list table ip filter')
+ nft = cmd('nft --json list table ip vyos_filter')
nft = json.loads(nft)
chain_list = []
diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py
index 6086ef859..ba0249efd 100755
--- a/src/conf_mode/service_pppoe-server.py
+++ b/src/conf_mode/service_pppoe-server.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2020 VyOS maintainers and contributors
+# Copyright (C) 2018-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -21,14 +21,12 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import get_accel_dict
from vyos.configverify import verify_accel_ppp_base_service
+from vyos.configverify import verify_interface_exists
from vyos.template import render
from vyos.util import call
from vyos.util import dict_search
-from vyos.util import get_interface_config
from vyos import ConfigError
from vyos import airbag
-from vyos.range_regex import range_to_regex
-
airbag.enable()
pppoe_conf = r'/run/accel-pppd/pppoe.conf'
@@ -54,15 +52,14 @@ def verify(pppoe):
verify_accel_ppp_base_service(pppoe)
if 'wins_server' in pppoe and len(pppoe['wins_server']) > 2:
- raise ConfigError('Not more then two IPv4 WINS name-servers can be configured')
+ raise ConfigError('Not more then two WINS name-servers can be configured')
if 'interface' not in pppoe:
raise ConfigError('At least one listen interface must be defined!')
# Check is interface exists in the system
- for iface in pppoe['interface']:
- if not get_interface_config(iface):
- raise ConfigError(f'Interface {iface} does not exist!')
+ for interface in pppoe['interface']:
+ verify_interface_exists(interface)
# local ippool and gateway settings config checks
if not (dict_search('client_ip_pool.subnet', pppoe) or
@@ -81,35 +78,24 @@ def generate(pppoe):
if not pppoe:
return None
- # Generate special regex for dynamic interfaces
- for iface in pppoe['interface']:
- if 'vlan_range' in pppoe['interface'][iface]:
- pppoe['interface'][iface]['regex'] = []
- 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.j2', pppoe)
if dict_search('authentication.mode', pppoe) == 'local':
render(pppoe_chap_secrets, 'accel-ppp/chap-secrets.config_dict.j2',
pppoe, permission=0o640)
- else:
- if os.path.exists(pppoe_chap_secrets):
- os.unlink(pppoe_chap_secrets)
-
return None
def apply(pppoe):
+ systemd_service = 'accel-ppp@pppoe.service'
if not pppoe:
- call('systemctl stop accel-ppp@pppoe.service')
+ call(f'systemctl stop {systemd_service}')
for file in [pppoe_conf, pppoe_chap_secrets]:
if os.path.exists(file):
os.unlink(file)
-
return None
- call('systemctl restart accel-ppp@pppoe.service')
+ call(f'systemctl reload-or-restart {systemd_service}')
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py
index 2bbd7142a..8746cc701 100755
--- a/src/conf_mode/ssh.py
+++ b/src/conf_mode/ssh.py
@@ -73,6 +73,9 @@ def verify(ssh):
if not ssh:
return None
+ if 'rekey' in ssh and 'data' not in ssh['rekey']:
+ raise ConfigError(f'Rekey data is required!')
+
verify_vrf(ssh)
return None
diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py
index fc2723ece..bd9cc3b89 100755
--- a/src/conf_mode/system-login.py
+++ b/src/conf_mode/system-login.py
@@ -40,6 +40,7 @@ from vyos import ConfigError
from vyos import airbag
airbag.enable()
+autologout_file = "/etc/profile.d/autologout.sh"
radius_config_file = "/etc/pam_radius_auth.conf"
def get_local_users():
@@ -203,6 +204,13 @@ def generate(login):
if os.path.isfile(radius_config_file):
os.unlink(radius_config_file)
+ if 'timeout' in login:
+ render(autologout_file, 'login/autologout.j2', login,
+ permission=0o755, user='root', group='root')
+ else:
+ if os.path.isfile(autologout_file):
+ os.unlink(autologout_file)
+
return None
diff --git a/src/conf_mode/system_update_check.py b/src/conf_mode/system_update_check.py
new file mode 100755
index 000000000..08ecfcb81
--- /dev/null
+++ b/src/conf_mode/system_update_check.py
@@ -0,0 +1,93 @@
+#!/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 json
+import jmespath
+
+from pathlib import Path
+from sys import exit
+
+from vyos.config import Config
+from vyos.util import call
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+
+base = ['system', 'update-check']
+service_name = 'vyos-system-update'
+service_conf = Path(f'/run/{service_name}.conf')
+motd_file = Path('/run/motd.d/10-vyos-update')
+
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ if not conf.exists(base):
+ return None
+
+ config = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+
+ return config
+
+
+def verify(config):
+ # bail out early - looks like removal from running config
+ if config is None:
+ return
+
+ if 'url' not in config:
+ raise ConfigError('URL is required!')
+
+
+def generate(config):
+ # bail out early - looks like removal from running config
+ if config is None:
+ # Remove old config and return
+ service_conf.unlink(missing_ok=True)
+ # MOTD used in /run/motd.d/10-update
+ motd_file.unlink(missing_ok=True)
+ return None
+
+ # Write configuration file
+ conf_json = json.dumps(config, indent=4)
+ service_conf.write_text(conf_json)
+
+ return None
+
+
+def apply(config):
+ if config:
+ if 'auto_check' in config:
+ call(f'systemctl restart {service_name}.service')
+ else:
+ call(f'systemctl stop {service_name}.service')
+
+
+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/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index 5ca32d23e..77a425f8b 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.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
@@ -16,6 +16,7 @@
import ipaddress
import os
+import re
from sys import exit
from time import sleep
@@ -264,7 +265,7 @@ def verify(ipsec):
ike = ra_conf['ike_group']
if dict_search(f'ike_group.{ike}.key_exchange', ipsec) != 'ikev2':
- raise ConfigError('IPSec remote-access connections requires IKEv2!')
+ raise ConfigError('IPsec remote-access connections requires IKEv2!')
else:
raise ConfigError(f"Missing ike-group on {name} remote-access config")
@@ -307,10 +308,10 @@ def verify(ipsec):
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!')
+ 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!')
+ 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!')
@@ -348,6 +349,14 @@ def verify(ipsec):
if 'site_to_site' in ipsec and 'peer' in ipsec['site_to_site']:
for peer, peer_conf in ipsec['site_to_site']['peer'].items():
has_default_esp = False
+ # Peer name it is swanctl connection name and shouldn't contain dots or colons, T4118
+ if bool(re.search(':|\.', peer)):
+ raise ConfigError(f'Incorrect peer name "{peer}" '
+ f'Peer name can contain alpha-numeric letters, hyphen and underscore')
+
+ if 'remote_address' not in peer_conf:
+ print(f'You should set correct remote-address "peer {peer} remote-address x.x.x.x"\n')
+
if 'default_esp_group' in peer_conf:
has_default_esp = True
if 'esp_group' not in ipsec or peer_conf['default_esp_group'] not in ipsec['esp_group']:
diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py
index 240546817..c050b796b 100755
--- a/src/conf_mode/vpn_openconnect.py
+++ b/src/conf_mode/vpn_openconnect.py
@@ -58,15 +58,16 @@ def get_config():
default_values = defaults(base)
ocserv = dict_merge(default_values, ocserv)
- # workaround a "know limitation" - https://phabricator.vyos.net/T2665
- del ocserv['authentication']['local_users']['username']['otp']
- if not ocserv["authentication"]["local_users"]["username"]:
- raise ConfigError('openconnect mode local required at least one user')
- default_ocserv_usr_values = default_values['authentication']['local_users']['username']['otp']
- for user, params in ocserv['authentication']['local_users']['username'].items():
- # Not every configuration requires OTP settings
- if ocserv['authentication']['local_users']['username'][user].get('otp'):
- ocserv['authentication']['local_users']['username'][user]['otp'] = dict_merge(default_ocserv_usr_values, ocserv['authentication']['local_users']['username'][user]['otp'])
+ if "local" in ocserv["authentication"]["mode"]:
+ # workaround a "know limitation" - https://phabricator.vyos.net/T2665
+ del ocserv['authentication']['local_users']['username']['otp']
+ if not ocserv["authentication"]["local_users"]["username"]:
+ raise ConfigError('openconnect mode local required at least one user')
+ default_ocserv_usr_values = default_values['authentication']['local_users']['username']['otp']
+ for user, params in ocserv['authentication']['local_users']['username'].items():
+ # Not every configuration requires OTP settings
+ if ocserv['authentication']['local_users']['username'][user].get('otp'):
+ ocserv['authentication']['local_users']['username'][user]['otp'] = dict_merge(default_ocserv_usr_values, ocserv['authentication']['local_users']['username'][user]['otp'])
if ocserv:
ocserv['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'),
@@ -80,9 +81,10 @@ def verify(ocserv):
# Check if listen-ports not binded other services
# It can be only listen by 'ocserv-main'
for proto, port in ocserv.get('listen_ports').items():
- if check_port_availability('0.0.0.0', int(port), proto) is not True and \
+ if check_port_availability(ocserv['listen_address'], int(port), proto) is not True and \
not is_listen_port_bind_service(int(port), 'ocserv-main'):
raise ConfigError(f'"{proto}" port "{port}" is used by another service')
+
# Check authentication
if "authentication" in ocserv:
if "mode" in ocserv["authentication"]:
diff --git a/src/conf_mode/zone_policy.py b/src/conf_mode/zone_policy.py
deleted file mode 100755
index a52c52706..000000000
--- a/src/conf_mode/zone_policy.py
+++ /dev/null
@@ -1,213 +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
-
-from json import loads
-from sys import exit
-
-from vyos.config import Config
-from vyos.configdict import dict_merge
-from vyos.template import render
-from vyos.util import cmd
-from vyos.util import dict_search_args
-from vyos.util import run
-from vyos.xml import defaults
-from vyos import ConfigError
-from vyos import airbag
-airbag.enable()
-
-nftables_conf = '/run/nftables_zone.conf'
-
-def get_config(config=None):
- if config:
- conf = config
- else:
- conf = Config()
- base = ['zone-policy']
- zone_policy = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)
-
- zone_policy['firewall'] = conf.get_config_dict(['firewall'],
- key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)
-
- if 'zone' in zone_policy:
- # 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 + ['zone'])
- for zone in zone_policy['zone']:
- zone_policy['zone'][zone] = dict_merge(default_values,
- zone_policy['zone'][zone])
-
- return zone_policy
-
-def verify(zone_policy):
- # bail out early - looks like removal from running config
- if not zone_policy:
- return None
-
- local_zone = False
- interfaces = []
-
- if 'zone' in zone_policy:
- for zone, zone_conf in zone_policy['zone'].items():
- if 'local_zone' not in zone_conf and 'interface' not in zone_conf:
- raise ConfigError(f'Zone "{zone}" has no interfaces and is not the local zone')
-
- if 'local_zone' in zone_conf:
- if local_zone:
- raise ConfigError('There cannot be multiple local zones')
- if 'interface' in zone_conf:
- raise ConfigError('Local zone cannot have interfaces assigned')
- if 'intra_zone_filtering' in zone_conf:
- raise ConfigError('Local zone cannot use intra-zone-filtering')
- local_zone = True
-
- if 'interface' in zone_conf:
- found_duplicates = [intf for intf in zone_conf['interface'] if intf in interfaces]
-
- if found_duplicates:
- raise ConfigError(f'Interfaces cannot be assigned to multiple zones')
-
- interfaces += zone_conf['interface']
-
- if 'intra_zone_filtering' in zone_conf:
- intra_zone = zone_conf['intra_zone_filtering']
-
- if len(intra_zone) > 1:
- raise ConfigError('Only one intra-zone-filtering action must be specified')
-
- if 'firewall' in intra_zone:
- v4_name = dict_search_args(intra_zone, 'firewall', 'name')
- if v4_name and not dict_search_args(zone_policy, 'firewall', 'name', v4_name):
- raise ConfigError(f'Firewall name "{v4_name}" does not exist')
-
- v6_name = dict_search_args(intra_zone, 'firewall', 'ipv6-name')
- if v6_name and not dict_search_args(zone_policy, 'firewall', 'ipv6-name', v6_name):
- raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist')
-
- if not v4_name and not v6_name:
- raise ConfigError('No firewall names specified for intra-zone-filtering')
-
- if 'from' in zone_conf:
- for from_zone, from_conf in zone_conf['from'].items():
- if from_zone not in zone_policy['zone']:
- raise ConfigError(f'Zone "{zone}" refers to a non-existent or deleted zone "{from_zone}"')
-
- v4_name = dict_search_args(from_conf, 'firewall', 'name')
- if v4_name:
- if 'name' not in zone_policy['firewall']:
- raise ConfigError(f'Firewall name "{v4_name}" does not exist')
-
- if not dict_search_args(zone_policy, 'firewall', 'name', v4_name):
- raise ConfigError(f'Firewall name "{v4_name}" does not exist')
-
- v6_name = dict_search_args(from_conf, 'firewall', 'v6_name')
- if v6_name:
- if 'ipv6_name' not in zone_policy['firewall']:
- raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist')
-
- if not dict_search_args(zone_policy, 'firewall', 'ipv6_name', v6_name):
- raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist')
-
- return None
-
-def has_ipv4_fw(zone_conf):
- if 'from' not in zone_conf:
- return False
- zone_from = zone_conf['from']
- return any([True for fz in zone_from if dict_search_args(zone_from, fz, 'firewall', 'name')])
-
-def has_ipv6_fw(zone_conf):
- if 'from' not in zone_conf:
- return False
- zone_from = zone_conf['from']
- return any([True for fz in zone_from if dict_search_args(zone_from, fz, 'firewall', 'ipv6_name')])
-
-def get_local_from(zone_policy, local_zone_name):
- # Get all zone firewall names from the local zone
- out = {}
- for zone, zone_conf in zone_policy['zone'].items():
- if zone == local_zone_name:
- continue
- if 'from' not in zone_conf:
- continue
- if local_zone_name in zone_conf['from']:
- out[zone] = zone_conf['from'][local_zone_name]
- return out
-
-def cleanup_commands():
- commands = []
- for table in ['ip filter', 'ip6 filter']:
- json_str = cmd(f'nft -t -j list table {table}')
- obj = loads(json_str)
- if 'nftables' not in obj:
- continue
- for item in obj['nftables']:
- if 'rule' in item:
- chain = item['rule']['chain']
- handle = item['rule']['handle']
- if 'expr' not in item['rule']:
- continue
- for expr in item['rule']['expr']:
- target = dict_search_args(expr, 'jump', 'target')
- if not target:
- continue
- if target.startswith("VZONE") or target.startswith("VYOS_STATE_POLICY"):
- commands.append(f'delete rule {table} {chain} handle {handle}')
- for item in obj['nftables']:
- if 'chain' in item:
- if item['chain']['name'].startswith("VZONE"):
- chain = item['chain']['name']
- commands.append(f'delete chain {table} {chain}')
- return commands
-
-def generate(zone_policy):
- data = zone_policy or {}
-
- if os.path.exists(nftables_conf): # Check to see if we've run before
- data['cleanup_commands'] = cleanup_commands()
-
- if 'zone' in data:
- for zone, zone_conf in data['zone'].items():
- zone_conf['ipv4'] = has_ipv4_fw(zone_conf)
- zone_conf['ipv6'] = has_ipv6_fw(zone_conf)
-
- if 'local_zone' in zone_conf:
- zone_conf['from_local'] = get_local_from(data, zone)
-
- render(nftables_conf, 'zone_policy/nftables.j2', data)
- return None
-
-def apply(zone_policy):
- install_result = run(f'nft -f {nftables_conf}')
- if install_result != 0:
- raise ConfigError('Failed to apply zone-policy')
-
- 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/sysctl.d/30-vyos-router.conf b/src/etc/sysctl.d/30-vyos-router.conf
index 4feb7e09a..411429510 100644
--- a/src/etc/sysctl.d/30-vyos-router.conf
+++ b/src/etc/sysctl.d/30-vyos-router.conf
@@ -109,3 +109,7 @@ net.ipv4.neigh.default.gc_thresh3 = 8192
net.ipv6.neigh.default.gc_thresh1 = 1024
net.ipv6.neigh.default.gc_thresh2 = 4096
net.ipv6.neigh.default.gc_thresh3 = 8192
+
+# Enable global RFS (Receive Flow Steering) configuration. RFS is inactive
+# until explicitly configured at the interface level
+net.core.rps_sock_flow_entries = 32768
diff --git a/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py b/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py
index bf4bfd05d..cbc2bfe6b 100755
--- a/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py
+++ b/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py
@@ -11,7 +11,7 @@ def get_nft_filter_chains():
"""
Get list of nft chains for table filter
"""
- nft = cmd('/usr/sbin/nft --json list table ip filter')
+ nft = cmd('/usr/sbin/nft --json list table ip vyos_filter')
nft = json.loads(nft)
chain_list = []
@@ -27,7 +27,7 @@ def get_nftables_details(name):
"""
Get dict, counters packets and bytes for chain
"""
- command = f'/usr/sbin/nft list chain ip filter {name}'
+ command = f'/usr/sbin/nft list chain ip vyos_filter {name}'
try:
results = cmd(command)
except:
@@ -60,7 +60,7 @@ def get_nft_telegraf(name):
Get data for telegraf in influxDB format
"""
for rule, rule_config in get_nftables_details(name).items():
- print(f'nftables,table=filter,chain={name},'
+ print(f'nftables,table=vyos_filter,chain={name},'
f'ruleid={rule} '
f'pkts={rule_config["packets"]}i,'
f'bytes={rule_config["bytes"]}i '
diff --git a/src/migration-scripts/firewall/7-to-8 b/src/migration-scripts/firewall/7-to-8
new file mode 100755
index 000000000..ce527acf5
--- /dev/null
+++ b/src/migration-scripts/firewall/7-to-8
@@ -0,0 +1,98 @@
+#!/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/>.
+
+# T2199: Migrate interface firewall nodes to firewall interfaces <ifname> <direction> name/ipv6-name <name>
+# T2199: Migrate zone-policy to firewall node
+
+import re
+
+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']
+zone_base = ['zone-policy']
+config = ConfigTree(config_file)
+
+if not config.exists(base) and not config.exists(zone_base):
+ # Nothing to do
+ exit(0)
+
+def migrate_interface(config, iftype, ifname, vif=None, vifs=None, vifc=None):
+ if_path = ['interfaces', iftype, ifname]
+ ifname_full = ifname
+
+ if vif:
+ if_path += ['vif', vif]
+ ifname_full = f'{ifname}.{vif}'
+ elif vifs:
+ if_path += ['vif-s', vifs]
+ ifname_full = f'{ifname}.{vifs}'
+ if vifc:
+ if_path += ['vif-c', vifc]
+ ifname_full = f'{ifname}.{vifs}.{vifc}'
+
+ if not config.exists(if_path + ['firewall']):
+ return
+
+ if not config.exists(['firewall', 'interface']):
+ config.set(['firewall', 'interface'])
+ config.set_tag(['firewall', 'interface'])
+
+ config.copy(if_path + ['firewall'], ['firewall', 'interface', ifname_full])
+ config.delete(if_path + ['firewall'])
+
+for iftype in config.list_nodes(['interfaces']):
+ for ifname in config.list_nodes(['interfaces', iftype]):
+ migrate_interface(config, iftype, ifname)
+
+ if config.exists(['interfaces', iftype, ifname, 'vif']):
+ for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']):
+ migrate_interface(config, iftype, ifname, vif=vif)
+
+ if config.exists(['interfaces', iftype, ifname, 'vif-s']):
+ for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']):
+ migrate_interface(config, iftype, ifname, vifs=vifs)
+
+ if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']):
+ for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']):
+ migrate_interface(config, iftype, ifname, vifs=vifs, vifc=vifc)
+
+if config.exists(zone_base + ['zone']):
+ config.set(['firewall', 'zone'])
+ config.set_tag(['firewall', 'zone'])
+
+ for zone in config.list_nodes(zone_base + ['zone']):
+ config.copy(zone_base + ['zone', zone], ['firewall', 'zone', zone])
+ config.delete(zone_base)
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/migration-scripts/ids/0-to-1 b/src/migration-scripts/ids/0-to-1
new file mode 100755
index 000000000..9f08f7dc7
--- /dev/null
+++ b/src/migration-scripts/ids/0-to-1
@@ -0,0 +1,56 @@
+#!/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/>.
+
+from sys import argv
+from sys import exit
+
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['service', 'ids', 'ddos-protection']
+config = ConfigTree(config_file)
+
+if not config.exists(base + ['threshold']):
+ # Nothing to do
+ exit(0)
+else:
+ if config.exists(base + ['threshold', 'fps']):
+ tmp = config.return_value(base + ['threshold', 'fps'])
+ config.delete(base + ['threshold', 'fps'])
+ config.set(base + ['threshold', 'general', 'fps'], value=tmp)
+ if config.exists(base + ['threshold', 'mbps']):
+ tmp = config.return_value(base + ['threshold', 'mbps'])
+ config.delete(base + ['threshold', 'mbps'])
+ config.set(base + ['threshold', 'general', 'mbps'], value=tmp)
+ if config.exists(base + ['threshold', 'pps']):
+ tmp = config.return_value(base + ['threshold', 'pps'])
+ config.delete(base + ['threshold', 'pps'])
+ config.set(base + ['threshold', 'general', 'pps'], value=tmp)
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print(f'Failed to save the modified config: {e}')
+ exit(1)
diff --git a/src/migration-scripts/ipoe-server/0-to-1 b/src/migration-scripts/ipoe-server/0-to-1
index f328ebced..d768758ba 100755
--- a/src/migration-scripts/ipoe-server/0-to-1
+++ b/src/migration-scripts/ipoe-server/0-to-1
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020 VyOS maintainers and contributors
+# 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
@@ -14,8 +14,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-# - remove primary/secondary identifier from nameserver
-# - Unifi RADIUS configuration by placing it all under "authentication radius" node
+# - T4703: merge vlan-id and vlan-range to vlan CLI node
+
+# L2|L3 -> l2|l3
+# mac-address -> mac
+# network-mode -> mode
import os
import sys
@@ -37,97 +40,35 @@ base = ['service', 'ipoe-server']
if not config.exists(base):
# Nothing to do
exit(0)
-else:
-
- # Migrate IPv4 DNS servers
- dns_base = base + ['dns-server']
- if config.exists(dns_base):
- for server in ['server-1', 'server-2']:
- if config.exists(dns_base + [server]):
- dns = config.return_value(dns_base + [server])
- config.set(base + ['name-server'], value=dns, replace=False)
-
- config.delete(dns_base)
-
- # Migrate IPv6 DNS servers
- dns_base = base + ['dnsv6-server']
- if config.exists(dns_base):
- for server in ['server-1', 'server-2', 'server-3']:
- if config.exists(dns_base + [server]):
- dns = config.return_value(dns_base + [server])
- config.set(base + ['name-server'], value=dns, replace=False)
-
- config.delete(dns_base)
-
- # Migrate radius-settings node to RADIUS and use this as base for the
- # later migration of the RADIUS servers - this will save a lot of code
- radius_settings = base + ['authentication', 'radius-settings']
- if config.exists(radius_settings):
- config.rename(radius_settings, 'radius')
-
- # Migrate RADIUS dynamic author / change of authorisation server
- dae_old = base + ['authentication', 'radius', 'dae-server']
- if config.exists(dae_old):
- config.rename(dae_old, 'dynamic-author')
- dae_new = base + ['authentication', 'radius', 'dynamic-author']
-
- if config.exists(dae_new + ['ip-address']):
- config.rename(dae_new + ['ip-address'], 'server')
-
- if config.exists(dae_new + ['secret']):
- config.rename(dae_new + ['secret'], 'key')
- # Migrate RADIUS server
- radius_server = base + ['authentication', 'radius-server']
- if config.exists(radius_server):
- new_base = base + ['authentication', 'radius', 'server']
- config.set(new_base)
- config.set_tag(new_base)
- for server in config.list_nodes(radius_server):
- old_base = radius_server + [server]
- config.copy(old_base, new_base + [server])
-
- # migrate key
- if config.exists(new_base + [server, 'secret']):
- config.rename(new_base + [server, 'secret'], 'key')
-
- # remove old req-limit node
- if config.exists(new_base + [server, 'req-limit']):
- config.delete(new_base + [server, 'req-limit'])
-
- config.delete(radius_server)
-
- # Migrate IPv6 prefixes
- ipv6_base = base + ['client-ipv6-pool']
- if config.exists(ipv6_base + ['prefix']):
- prefix_old = config.return_values(ipv6_base + ['prefix'])
- # delete old prefix CLI nodes
- config.delete(ipv6_base + ['prefix'])
- # create ned prefix tag node
- config.set(ipv6_base + ['prefix'])
- config.set_tag(ipv6_base + ['prefix'])
-
- for p in prefix_old:
- prefix = p.split(',')[0]
- mask = p.split(',')[1]
- config.set(ipv6_base + ['prefix', prefix, 'mask'], value=mask)
-
- if config.exists(ipv6_base + ['delegate-prefix']):
- prefix_old = config.return_values(ipv6_base + ['delegate-prefix'])
- # delete old delegate prefix CLI nodes
- config.delete(ipv6_base + ['delegate-prefix'])
- # create ned delegation tag node
- config.set(ipv6_base + ['delegate'])
- config.set_tag(ipv6_base + ['delegate'])
-
- for p in prefix_old:
- prefix = p.split(',')[0]
- mask = p.split(',')[1]
- config.set(ipv6_base + ['delegate', prefix, 'delegation-prefix'], value=mask)
-
- 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)
+if config.exists(base + ['authentication', 'interface']):
+ for interface in config.list_nodes(base + ['authentication', 'interface']):
+ config.rename(base + ['authentication', 'interface', interface, 'mac-address'], 'mac')
+
+ mac_base = base + ['authentication', 'interface', interface, 'mac']
+ for mac in config.list_nodes(mac_base):
+ vlan_config = mac_base + [mac, 'vlan-id']
+ if config.exists(vlan_config):
+ config.rename(vlan_config, 'vlan')
+
+for interface in config.list_nodes(base + ['interface']):
+ base_path = base + ['interface', interface]
+ for vlan in ['vlan-id', 'vlan-range']:
+ if config.exists(base_path + [vlan]):
+ print(interface, vlan)
+ for tmp in config.return_values(base_path + [vlan]):
+ config.set(base_path + ['vlan'], value=tmp, replace=False)
+ config.delete(base_path + [vlan])
+
+ if config.exists(base_path + ['network-mode']):
+ tmp = config.return_value(base_path + ['network-mode'])
+ config.delete(base_path + ['network-mode'])
+ # Change L2|L3 to lower case l2|l3
+ config.set(base_path + ['mode'], value=tmp.lower())
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/migration-scripts/ipsec/9-to-10 b/src/migration-scripts/ipsec/9-to-10
new file mode 100755
index 000000000..1254104cb
--- /dev/null
+++ b/src/migration-scripts/ipsec/9-to-10
@@ -0,0 +1,134 @@
+#!/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 re
+
+from sys import argv
+from sys import exit
+
+from vyos.configtree import ConfigTree
+from vyos.template import is_ipv4
+from vyos.template import is_ipv6
+
+
+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 = ['vpn', 'ipsec']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+# IKE changes, T4118:
+if config.exists(base + ['ike-group']):
+ for ike_group in config.list_nodes(base + ['ike-group']):
+ # replace 'ipsec ike-group <tag> mobike disable'
+ # => 'ipsec ike-group <tag> disable-mobike'
+ mobike = base + ['ike-group', ike_group, 'mobike']
+ if config.exists(mobike):
+ if config.return_value(mobike) == 'disable':
+ config.set(base + ['ike-group', ike_group, 'disable-mobike'])
+ config.delete(mobike)
+
+ # replace 'ipsec ike-group <tag> ikev2-reauth yes'
+ # => 'ipsec ike-group <tag> ikev2-reauth'
+ reauth = base + ['ike-group', ike_group, 'ikev2-reauth']
+ if config.exists(reauth):
+ if config.return_value(reauth) == 'yes':
+ config.delete(reauth)
+ config.set(reauth)
+ else:
+ config.delete(reauth)
+
+# ESP changes
+# replace 'ipsec esp-group <tag> compression enable'
+# => 'ipsec esp-group <tag> compression'
+if config.exists(base + ['esp-group']):
+ for esp_group in config.list_nodes(base + ['esp-group']):
+ compression = base + ['esp-group', esp_group, 'compression']
+ if config.exists(compression):
+ if config.return_value(compression) == 'enable':
+ config.delete(compression)
+ config.set(compression)
+ else:
+ config.delete(compression)
+
+# PEER changes
+if config.exists(base + ['site-to-site', 'peer']):
+ for peer in config.list_nodes(base + ['site-to-site', 'peer']):
+ peer_base = base + ['site-to-site', 'peer', peer]
+
+ # replace: 'peer <tag> id x'
+ # => 'peer <tag> local-id x'
+ if config.exists(peer_base + ['authentication', 'id']):
+ config.rename(peer_base + ['authentication', 'id'], 'local-id')
+
+ # For the peer '@foo' set remote-id 'foo' if remote-id is not defined
+ if peer.startswith('@'):
+ if not config.exists(peer_base + ['authentication', 'remote-id']):
+ tmp = peer.replace('@', '')
+ config.set(peer_base + ['authentication', 'remote-id'], value=tmp)
+
+ # replace: 'peer <tag> force-encapsulation enable'
+ # => 'peer <tag> force-udp-encapsulation'
+ force_enc = peer_base + ['force-encapsulation']
+ if config.exists(force_enc):
+ if config.return_value(force_enc) == 'enable':
+ config.delete(force_enc)
+ config.set(peer_base + ['force-udp-encapsulation'])
+ else:
+ config.delete(force_enc)
+
+ # add option: 'peer <tag> remote-address x.x.x.x'
+ remote_address = peer
+ if peer.startswith('@'):
+ remote_address = 'any'
+ config.set(peer_base + ['remote-address'], value=remote_address)
+ # Peer name it is swanctl connection name and shouldn't contain dots or colons
+ # rename peer:
+ # peer 192.0.2.1 => peer peer_192-0-2-1
+ # peer 2001:db8::2 => peer peer_2001-db8--2
+ # peer @foo => peer peer_foo
+ re_peer_name = re.sub(':|\.', '-', peer)
+ if re_peer_name.startswith('@'):
+ re_peer_name = re.sub('@', '', re_peer_name)
+ new_peer_name = f'peer_{re_peer_name}'
+
+ config.rename(peer_base, new_peer_name)
+
+# remote-access/road-warrior changes
+if config.exists(base + ['remote-access', 'connection']):
+ for connection in config.list_nodes(base + ['remote-access', 'connection']):
+ ra_base = base + ['remote-access', 'connection', connection]
+ # replace: 'remote-access connection <tag> authentication id x'
+ # => 'remote-access connection <tag> authentication local-id x'
+ if config.exists(ra_base + ['authentication', 'id']):
+ config.rename(ra_base + ['authentication', 'id'], 'local-id')
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print(f'Failed to save the modified config: {e}')
+ exit(1)
diff --git a/src/migration-scripts/isis/1-to-2 b/src/migration-scripts/isis/1-to-2
new file mode 100755
index 000000000..f914ea995
--- /dev/null
+++ b/src/migration-scripts/isis/1-to-2
@@ -0,0 +1,46 @@
+#!/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/>.
+
+# T4739 refactor, and remove "on" from segment routing from the configuration
+
+from sys import argv
+from sys import exit
+
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+# Check if ISIS segment routing is configured. Then check if segment routing "on" exists, then delete the "on" as it is no longer needed. This is for global configuration.
+if config.exists(['protocols', 'isis']):
+ if config.exists(['protocols', 'isis', 'segment-routing']):
+ if config.exists(['protocols', 'isis', 'segment-routing', 'enable']):
+ config.delete(['protocols', 'isis', 'segment-routing', 'enable'])
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print(f'Failed to save the modified config: {e}')
+ exit(1)
diff --git a/src/migration-scripts/policy/3-to-4 b/src/migration-scripts/policy/3-to-4
new file mode 100755
index 000000000..bae30cffc
--- /dev/null
+++ b/src/migration-scripts/policy/3-to-4
@@ -0,0 +1,162 @@
+#!/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/>.
+
+# T4660: change cli
+# from: set policy route-map FOO rule 10 set community 'TEXT'
+# Multiple value
+# to: set policy route-map FOO rule 10 set community replace <community>
+# Multiple value
+# to: set policy route-map FOO rule 10 set community add <community>
+# to: set policy route-map FOO rule 10 set community none
+#
+# from: set policy route-map FOO rule 10 set large-community 'TEXT'
+# Multiple value
+# to: set policy route-map FOO rule 10 set large-community replace <community>
+# Multiple value
+# to: set policy route-map FOO rule 10 set large-community add <community>
+# to: set policy route-map FOO rule 10 set large-community none
+#
+# from: set policy route-map FOO rule 10 set extecommunity [rt|soo] 'TEXT'
+# Multiple value
+# to: set policy route-map FOO rule 10 set extcommunity [rt|soo] <community>
+
+from sys import argv
+from sys import exit
+
+from vyos.configtree import ConfigTree
+
+
+# Migration function for large and regular communities
+def community_migrate(config: ConfigTree, rule: list[str]) -> bool:
+ """
+
+ :param config: configuration object
+ :type config: ConfigTree
+ :param rule: Path to variable
+ :type rule: list[str]
+ :return: True if additive presents in community string
+ :rtype: bool
+ """
+ community_list = list((config.return_value(rule)).split(" "))
+ config.delete(rule)
+ if 'none' in community_list:
+ config.set(rule + ['none'])
+ return False
+ else:
+ community_action: str = 'replace'
+ if 'additive' in community_list:
+ community_action = 'add'
+ community_list.remove('additive')
+ for community in community_list:
+ config.set(rule + [community_action], value=community,
+ replace=False)
+ if community_action == 'replace':
+ return False
+ else:
+ return True
+
+
+# Migration function for extcommunities
+def extcommunity_migrate(config: ConfigTree, rule: list[str]) -> None:
+ """
+
+ :param config: configuration object
+ :type config: ConfigTree
+ :param rule: Path to variable
+ :type rule: list[str]
+ """
+ # if config.exists(rule + ['bandwidth']):
+ # bandwidth: str = config.return_value(rule + ['bandwidth'])
+ # config.delete(rule + ['bandwidth'])
+ # config.set(rule + ['bandwidth'], value=bandwidth)
+
+ if config.exists(rule + ['rt']):
+ community_list = list((config.return_value(rule + ['rt'])).split(" "))
+ config.delete(rule + ['rt'])
+ for community in community_list:
+ config.set(rule + ['rt'], value=community, replace=False)
+
+ if config.exists(rule + ['soo']):
+ community_list = list((config.return_value(rule + ['soo'])).split(" "))
+ config.delete(rule + ['soo'])
+ for community in community_list:
+ config.set(rule + ['soo'], value=community, replace=False)
+
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name: str = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base: list[str] = ['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):
+ if not config.exists(base + [route_map, 'rule']):
+ continue
+ for rule in config.list_nodes(base + [route_map, 'rule']):
+ base_rule: list[str] = base + [route_map, 'rule', rule, 'set']
+
+ # IF additive presents in coummunity then comm-list is redundant
+ isAdditive: bool = True
+ #### Change Set community ########
+ if config.exists(base_rule + ['community']):
+ isAdditive = community_migrate(config,
+ base_rule + ['community'])
+
+ #### Change Set community-list delete migrate ########
+ if config.exists(base_rule + ['comm-list', 'comm-list']):
+ if isAdditive:
+ tmp = config.return_value(
+ base_rule + ['comm-list', 'comm-list'])
+ config.delete(base_rule + ['comm-list'])
+ config.set(base_rule + ['community', 'delete'], value=tmp)
+ else:
+ config.delete(base_rule + ['comm-list'])
+
+ isAdditive = False
+ #### Change Set large-community ########
+ if config.exists(base_rule + ['large-community']):
+ isAdditive = community_migrate(config,
+ base_rule + ['large-community'])
+
+ #### Change Set large-community delete by List ########
+ if config.exists(base_rule + ['large-comm-list-delete']):
+ if isAdditive:
+ tmp = config.return_value(
+ base_rule + ['large-comm-list-delete'])
+ config.delete(base_rule + ['large-comm-list-delete'])
+ config.set(base_rule + ['large-community', 'delete'],
+ value=tmp)
+ else:
+ config.delete(base_rule + ['large-comm-list-delete'])
+
+ #### Change Set extcommunity ########
+ extcommunity_migrate(config, base_rule + ['extcommunity'])
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print(f'Failed to save the modified config: {e}')
+ exit(1)
diff --git a/src/migration-scripts/pppoe-server/5-to-6 b/src/migration-scripts/pppoe-server/5-to-6
new file mode 100755
index 000000000..e4888f4db
--- /dev/null
+++ b/src/migration-scripts/pppoe-server/5-to-6
@@ -0,0 +1,52 @@
+#!/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/>.
+
+# - T4703: merge vlan-id and vlan-range to vlan CLI node
+
+from vyos.configtree import ConfigTree
+from sys import argv
+from sys import exit
+
+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()
+
+config = ConfigTree(config_file)
+base_path = ['service', 'pppoe-server', 'interface']
+if not config.exists(base_path):
+ # Nothing to do
+ exit(0)
+
+for interface in config.list_nodes(base_path):
+ for vlan in ['vlan-id', 'vlan-range']:
+ if config.exists(base_path + [interface, vlan]):
+ print(interface, vlan)
+ for tmp in config.return_values(base_path + [interface, vlan]):
+ config.set(base_path + [interface, 'vlan'], value=tmp, replace=False)
+ config.delete(base_path + [interface, vlan])
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print(f'Failed to save the modified config: {e}')
+ exit(1)
+
diff --git a/src/op_mode/bridge.py b/src/op_mode/bridge.py
index fe8dadd70..5a821a287 100755
--- a/src/op_mode/bridge.py
+++ b/src/op_mode/bridge.py
@@ -22,7 +22,7 @@ import typing
from sys import exit
from tabulate import tabulate
-from vyos.util import cmd
+from vyos.util import cmd, rc_cmd
from vyos.util import dict_search
import vyos.opmode
@@ -57,7 +57,11 @@ def _get_raw_data_fdb(bridge):
"""Get MAC-address for the bridge brX
:returns list
"""
- json_data = cmd(f'sudo bridge --json fdb show br {bridge}')
+ code, json_data = rc_cmd(f'sudo bridge --json fdb show br {bridge}')
+ # From iproute2 fdb.c, fdb_show() will only exit(-1) in case of
+ # non-existent bridge device; raise error.
+ if code == 255:
+ raise vyos.opmode.UnconfiguredSubsystem(f"no such bridge device {bridge}")
data_dict = json.loads(json_data)
return data_dict
diff --git a/src/op_mode/conntrack.py b/src/op_mode/conntrack.py
index b27aa6060..fff537936 100755
--- a/src/op_mode/conntrack.py
+++ b/src/op_mode/conntrack.py
@@ -48,6 +48,14 @@ def _get_raw_data(family):
Return: dictionary
"""
xml = _get_xml_data(family)
+ if len(xml) == 0:
+ output = {'conntrack':
+ {
+ 'error': True,
+ 'reason': 'entries not found'
+ }
+ }
+ return output
return _xml_to_dict(xml)
@@ -72,7 +80,8 @@ def get_formatted_output(dict_data):
:return: formatted output
"""
data_entries = []
- #dict_data = _get_raw_data(family)
+ if 'error' in dict_data['conntrack']:
+ return 'Entries not found'
for entry in dict_data['conntrack']['flow']:
orig_src, orig_dst, orig_sport, orig_dport = {}, {}, {}, {}
reply_src, reply_dst, reply_sport, reply_dport = {}, {}, {}, {}
diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py
index 0aea17b3a..950feb625 100755
--- a/src/op_mode/firewall.py
+++ b/src/op_mode/firewall.py
@@ -24,43 +24,33 @@ from vyos.config import Config
from vyos.util import cmd
from vyos.util import dict_search_args
-def get_firewall_interfaces(conf, firewall, name=None, ipv6=False):
- interfaces = conf.get_config_dict(['interfaces'], key_mangling=('-', '_'),
- get_first_key=True, no_tag_node_value_mangle=True)
-
+def get_firewall_interfaces(firewall, name=None, ipv6=False):
directions = ['in', 'out', 'local']
- def parse_if(ifname, if_conf):
- if 'firewall' in if_conf:
+ if 'interface' in firewall:
+ for ifname, if_conf in firewall['interface'].items():
for direction in directions:
- if direction in if_conf['firewall']:
- fw_conf = if_conf['firewall'][direction]
- name_str = f'({ifname},{direction})'
-
- if 'name' in fw_conf:
- fw_name = fw_conf['name']
+ if direction not in if_conf:
+ continue
- if not name:
- firewall['name'][fw_name]['interface'].append(name_str)
- elif not ipv6 and name == fw_name:
- firewall['interface'].append(name_str)
+ fw_conf = if_conf[direction]
+ name_str = f'({ifname},{direction})'
- if 'ipv6_name' in fw_conf:
- fw_name = fw_conf['ipv6_name']
+ if 'name' in fw_conf:
+ fw_name = fw_conf['name']
- if not name:
- firewall['ipv6_name'][fw_name]['interface'].append(name_str)
- elif ipv6 and name == fw_name:
- firewall['interface'].append(name_str)
+ if not name:
+ firewall['name'][fw_name]['interface'].append(name_str)
+ elif not ipv6 and name == fw_name:
+ firewall['interface'].append(name_str)
- for iftype in ['vif', 'vif_s', 'vif_c']:
- if iftype in if_conf:
- for vifname, vif_conf in if_conf[iftype].items():
- parse_if(f'{ifname}.{vifname}', vif_conf)
+ if 'ipv6_name' in fw_conf:
+ fw_name = fw_conf['ipv6_name']
- for iftype, iftype_conf in interfaces.items():
- for ifname, if_conf in iftype_conf.items():
- parse_if(ifname, if_conf)
+ if not name:
+ firewall['ipv6_name'][fw_name]['interface'].append(name_str)
+ elif ipv6 and name == fw_name:
+ firewall['interface'].append(name_str)
return firewall
@@ -83,13 +73,13 @@ def get_config_firewall(conf, name=None, ipv6=False, interfaces=True):
for fw_name, name_conf in firewall['ipv6_name'].items():
name_conf['interface'] = []
- get_firewall_interfaces(conf, firewall, name, ipv6)
+ get_firewall_interfaces(firewall, name, ipv6)
return firewall
def get_nftables_details(name, ipv6=False):
suffix = '6' if ipv6 else ''
name_prefix = 'NAME6_' if ipv6 else 'NAME_'
- command = f'sudo nft list chain ip{suffix} filter {name_prefix}{name}'
+ command = f'sudo nft list chain ip{suffix} vyos_filter {name_prefix}{name}'
try:
results = cmd(command)
except:
diff --git a/src/op_mode/ikev2_profile_generator.py b/src/op_mode/ikev2_profile_generator.py
index 21561d16f..a22f04c45 100755
--- a/src/op_mode/ikev2_profile_generator.py
+++ b/src/op_mode/ikev2_profile_generator.py
@@ -119,7 +119,7 @@ config_base = ipsec_base + ['remote-access', 'connection']
pki_base = ['pki']
conf = ConfigTreeQuery()
if not conf.exists(config_base):
- exit('IPSec remote-access is not configured!')
+ exit('IPsec remote-access is not configured!')
profile_name = 'VyOS IKEv2 Profile'
if args.profile:
@@ -131,7 +131,7 @@ if args.name:
conn_base = config_base + [args.connection]
if not conf.exists(conn_base):
- exit(f'IPSec remote-access connection "{args.connection}" does not exist!')
+ exit(f'IPsec remote-access connection "{args.connection}" does not exist!')
data = conf.get_config_dict(conn_base, key_mangling=('-', '_'),
get_first_key=True, no_tag_node_value_mangle=True)
@@ -178,7 +178,7 @@ for _, proposal in ike_proposal.items():
proposal['hash'] in set(vyos2client_integrity) and
proposal['dh_group'] in set(supported_dh_groups)):
- # We 're-code' from the VyOS IPSec proposals to the Apple naming scheme
+ # We 're-code' from the VyOS IPsec proposals to the Apple naming scheme
proposal['encryption'] = vyos2client_cipher[ proposal['encryption'] ]
proposal['hash'] = vyos2client_integrity[ proposal['hash'] ]
@@ -191,7 +191,7 @@ count = 1
for _, proposal in esp_proposals.items():
if {'encryption', 'hash'} <= set(proposal):
if proposal['encryption'] in set(vyos2client_cipher) and proposal['hash'] in set(vyos2client_integrity):
- # We 're-code' from the VyOS IPSec proposals to the Apple naming scheme
+ # We 're-code' from the VyOS IPsec proposals to the Apple naming scheme
proposal['encryption'] = vyos2client_cipher[ proposal['encryption'] ]
proposal['hash'] = vyos2client_integrity[ proposal['hash'] ]
diff --git a/src/op_mode/nat.py b/src/op_mode/nat.py
index 1339d5b92..845dbbb2c 100755
--- a/src/op_mode/nat.py
+++ b/src/op_mode/nat.py
@@ -60,7 +60,7 @@ def _get_json_data(direction, family):
if direction == 'destination':
chain = 'PREROUTING'
family = 'ip6' if family == 'inet6' else 'ip'
- return cmd(f'sudo nft --json list chain {family} nat {chain}')
+ return cmd(f'sudo nft --json list chain {family} vyos_nat {chain}')
def _get_raw_data_rules(direction, family):
@@ -109,7 +109,7 @@ def _get_formatted_output_rules(data, direction, family):
if jmespath.search('rule.expr[*].match.left.meta', rule) else 'any'
for index, match in enumerate(jmespath.search('rule.expr[*].match', rule)):
if 'payload' in match['left']:
- if 'prefix' in match['right'] or 'set' in match['right']:
+ if isinstance(match['right'], dict) and ('prefix' in match['right'] or 'set' in match['right']):
# Merge dict src/dst l3_l4 parameters
my_dict = {**match['left']['payload'], **match['right']}
my_dict['op'] = match['op']
@@ -136,10 +136,15 @@ def _get_formatted_output_rules(data, direction, family):
dport = my_dict.get('set')
dport = ','.join(map(str, dport))
else:
- if jmespath.search('left.payload.field', match) == 'saddr':
+ field = jmespath.search('left.payload.field', match)
+ if field == 'saddr':
saddr = match.get('right')
- if jmespath.search('left.payload.field', match) == 'daddr':
+ elif field == 'daddr':
daddr = match.get('right')
+ elif field == 'sport':
+ sport = match.get('right')
+ elif field == 'dport':
+ dport = match.get('right')
else:
saddr = '::/0' if family == 'inet6' else '0.0.0.0/0'
daddr = '::/0' if family == 'inet6' else '0.0.0.0/0'
diff --git a/src/op_mode/route.py b/src/op_mode/route.py
index e1eee5bbf..e1eee5bbf 100644..100755
--- a/src/op_mode/route.py
+++ b/src/op_mode/route.py
diff --git a/src/op_mode/show_nat66_statistics.py b/src/op_mode/show_nat66_statistics.py
index bc81692ae..cb10aed9f 100755
--- a/src/op_mode/show_nat66_statistics.py
+++ b/src/op_mode/show_nat66_statistics.py
@@ -44,7 +44,7 @@ group.add_argument("--destination", help="Show statistics for configured destina
args = parser.parse_args()
if args.source or args.destination:
- tmp = cmd('sudo nft -j list table ip6 nat')
+ tmp = cmd('sudo nft -j list table ip6 vyos_nat')
tmp = json.loads(tmp)
source = r"nftables[?rule.chain=='POSTROUTING'].rule.{chain: chain, handle: handle, comment: comment, counter: expr[].counter | [0], interface: expr[].match.right | [0] }"
diff --git a/src/op_mode/show_nat_statistics.py b/src/op_mode/show_nat_statistics.py
index c568c8305..be41e083b 100755
--- a/src/op_mode/show_nat_statistics.py
+++ b/src/op_mode/show_nat_statistics.py
@@ -44,7 +44,7 @@ group.add_argument("--destination", help="Show statistics for configured destina
args = parser.parse_args()
if args.source or args.destination:
- tmp = cmd('sudo nft -j list table ip nat')
+ tmp = cmd('sudo nft -j list table ip vyos_nat')
tmp = json.loads(tmp)
source = r"nftables[?rule.chain=='POSTROUTING'].rule.{chain: chain, handle: handle, comment: comment, counter: expr[].counter | [0], interface: expr[].match.right | [0] }"
diff --git a/src/op_mode/storage.py b/src/op_mode/storage.py
new file mode 100755
index 000000000..75964c493
--- /dev/null
+++ b/src/op_mode/storage.py
@@ -0,0 +1,60 @@
+#!/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 sys
+
+import vyos.opmode
+from vyos.util import cmd
+
+
+def _get_system_storage(only_persistent=False):
+ if not only_persistent:
+ cmd_str = 'df -h -x squashf'
+ else:
+ cmd_str = 'df -h -t ext4 --output=source,size,used,avail,pcent'
+
+ res = cmd(cmd_str)
+
+ return res
+
+def _get_raw_data():
+ out = _get_system_storage(only_persistent=True)
+ lines = out.splitlines()
+ lists = [l.split() for l in lines]
+ res = {lists[0][i]: lists[1][i] for i in range(len(lists[0]))}
+
+ return res
+
+def _get_formatted_output():
+ return _get_system_storage()
+
+def show(raw: bool):
+ if raw:
+ return _get_raw_data()
+
+ return _get_formatted_output()
+
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
+
diff --git a/src/op_mode/system.py b/src/op_mode/system.py
new file mode 100755
index 000000000..11a3a8730
--- /dev/null
+++ b/src/op_mode/system.py
@@ -0,0 +1,92 @@
+#!/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 jmespath
+import json
+import sys
+import requests
+import typing
+
+from sys import exit
+
+from vyos.configquery import ConfigTreeQuery
+
+import vyos.opmode
+import vyos.version
+
+config = ConfigTreeQuery()
+base = ['system', 'update-check']
+
+
+def _compare_version_raw():
+ url = config.value(base + ['url'])
+ local_data = vyos.version.get_full_version_data()
+ remote_data = vyos.version.get_remote_version(url)
+ if not remote_data:
+ return {"error": True,
+ "reason": "Unable to get remote version"}
+ if local_data.get('version') and remote_data:
+ local_version = local_data.get('version')
+ remote_version = jmespath.search('[0].version', remote_data)
+ image_url = jmespath.search('[0].url', remote_data)
+ if local_data.get('version') != remote_version:
+ return {"error": False,
+ "update_available": True,
+ "local_version": local_version,
+ "remote_version": remote_version,
+ "url": image_url}
+ return {"update_available": False,
+ "local_version": local_version,
+ "remote_version": remote_version}
+
+
+def _formatted_compare_version(data):
+ local_version = data.get('local_version')
+ remote_version = data.get('remote_version')
+ url = data.get('url')
+ if {'update_available','local_version', 'remote_version', 'url'} <= set(data):
+ return f'Current version: {local_version}\n\nUpdate available: {remote_version}\nUpdate URL: {url}'
+ elif local_version == remote_version and remote_version is not None:
+ return f'No available updates for your system \n' \
+ f'current version: {local_version}\nremote version: {remote_version}'
+ else:
+ return 'Update not found'
+
+
+def _verify():
+ if not config.exists(base):
+ return False
+ return True
+
+
+def show_update(raw: bool):
+ if not _verify():
+ raise vyos.opmode.UnconfiguredSubsystem("system update-check not configured")
+ data = _compare_version_raw()
+ if raw:
+ return data
+ else:
+ return _formatted_compare_version(data)
+
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/show_uptime.py b/src/op_mode/uptime.py
index b70c60cf8..2ebe6783b 100755
--- a/src/op_mode/show_uptime.py
+++ b/src/op_mode/uptime.py
@@ -14,7 +14,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-def get_uptime_seconds():
+import sys
+
+import vyos.opmode
+
+def _get_uptime_seconds():
from re import search
from vyos.util import read_file
@@ -23,7 +27,7 @@ def get_uptime_seconds():
return int(float(seconds))
-def get_load_averages():
+def _get_load_averages():
from re import search
from vyos.util import cmd
from vyos.cpu import get_core_count
@@ -40,19 +44,17 @@ def get_load_averages():
return res
-def get_raw_data():
+def _get_raw_data():
from vyos.util import seconds_to_human
res = {}
- res["uptime_seconds"] = get_uptime_seconds()
- res["uptime"] = seconds_to_human(get_uptime_seconds())
- res["load_average"] = get_load_averages()
+ res["uptime_seconds"] = _get_uptime_seconds()
+ res["uptime"] = seconds_to_human(_get_uptime_seconds())
+ res["load_average"] = _get_load_averages()
return res
-def get_formatted_output():
- data = get_raw_data()
-
+def _get_formatted_output(data):
out = "Uptime: {}\n\n".format(data["uptime"])
avgs = data["load_average"]
out += "Load averages:\n"
@@ -62,5 +64,19 @@ def get_formatted_output():
return out
+def show(raw: bool):
+ uptime_data = _get_raw_data()
+
+ if raw:
+ return uptime_data
+ else:
+ return _get_formatted_output(uptime_data)
+
if __name__ == '__main__':
- print(get_formatted_output())
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/vpn_ike_sa.py b/src/op_mode/vpn_ike_sa.py
index 00f34564a..4b44c5c15 100755
--- a/src/op_mode/vpn_ike_sa.py
+++ b/src/op_mode/vpn_ike_sa.py
@@ -71,7 +71,7 @@ if __name__ == '__main__':
args = parser.parse_args()
if not process_named_running('charon'):
- print("IPSec Process NOT Running")
+ print("IPsec Process NOT Running")
sys.exit(0)
ike_sa(args.peer, args.nat)
diff --git a/src/services/api/graphql/graphql/directives.py b/src/services/api/graphql/graphql/directives.py
index d8ceefae6..d75d72582 100644
--- a/src/services/api/graphql/graphql/directives.py
+++ b/src/services/api/graphql/graphql/directives.py
@@ -31,54 +31,21 @@ class VyosDirective(SchemaDirectiveVisitor):
field.resolve = func
return field
-
-class ConfigureDirective(VyosDirective):
- """
- Class providing implementation of 'configure' directive in schema.
- """
- def visit_field_definition(self, field, object_type):
- super().visit_field_definition(field, object_type,
- make_resolver=make_configure_resolver)
-
-class ShowConfigDirective(VyosDirective):
- """
- Class providing implementation of 'show' directive in schema.
- """
- def visit_field_definition(self, field, object_type):
- super().visit_field_definition(field, object_type,
- make_resolver=make_show_config_resolver)
-
-class SystemStatusDirective(VyosDirective):
- """
- Class providing implementation of 'system_status' directive in schema.
- """
- def visit_field_definition(self, field, object_type):
- super().visit_field_definition(field, object_type,
- make_resolver=make_system_status_resolver)
-
-class ConfigFileDirective(VyosDirective):
- """
- Class providing implementation of 'configfile' directive in schema.
- """
- def visit_field_definition(self, field, object_type):
- super().visit_field_definition(field, object_type,
- make_resolver=make_config_file_resolver)
-
-class ShowDirective(VyosDirective):
+class ConfigSessionQueryDirective(VyosDirective):
"""
- Class providing implementation of 'show' directive in schema.
+ Class providing implementation of 'configsessionquery' directive in schema.
"""
def visit_field_definition(self, field, object_type):
super().visit_field_definition(field, object_type,
- make_resolver=make_show_resolver)
+ make_resolver=make_config_session_query_resolver)
-class ImageDirective(VyosDirective):
+class ConfigSessionMutationDirective(VyosDirective):
"""
- Class providing implementation of 'image' directive in schema.
+ Class providing implementation of 'configsessionmutation' directive in schema.
"""
def visit_field_definition(self, field, object_type):
super().visit_field_definition(field, object_type,
- make_resolver=make_image_resolver)
+ make_resolver=make_config_session_mutation_resolver)
class GenOpQueryDirective(VyosDirective):
"""
@@ -96,11 +63,16 @@ class GenOpMutationDirective(VyosDirective):
super().visit_field_definition(field, object_type,
make_resolver=make_gen_op_mutation_resolver)
-directives_dict = {"configure": ConfigureDirective,
- "showconfig": ShowConfigDirective,
- "systemstatus": SystemStatusDirective,
- "configfile": ConfigFileDirective,
- "show": ShowDirective,
- "image": ImageDirective,
+class SystemStatusDirective(VyosDirective):
+ """
+ Class providing implementation of 'system_status' directive in schema.
+ """
+ def visit_field_definition(self, field, object_type):
+ super().visit_field_definition(field, object_type,
+ make_resolver=make_system_status_resolver)
+
+directives_dict = {"configsessionquery": ConfigSessionQueryDirective,
+ "configsessionmutation": ConfigSessionMutationDirective,
"genopquery": GenOpQueryDirective,
- "genopmutation": GenOpMutationDirective}
+ "genopmutation": GenOpMutationDirective,
+ "systemstatus": SystemStatusDirective}
diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py
index 1b77cff87..f7d285a77 100644
--- a/src/services/api/graphql/graphql/mutations.py
+++ b/src/services/api/graphql/graphql/mutations.py
@@ -90,11 +90,12 @@ def make_mutation_resolver(mutation_name, class_name, session_func):
}
except OpModeError as e:
typename = type(e).__name__
+ msg = str(e)
return {
"success": False,
"errore": ['op_mode_error'],
"op_mode_error": {"name": f"{typename}",
- "message": op_mode_err_msg.get(typename, "Unknown"),
+ "message": msg if msg else op_mode_err_msg.get(typename, "Unknown"),
"vyos_code": op_mode_err_code.get(typename, 9999)}
}
except Exception as error:
@@ -105,24 +106,9 @@ def make_mutation_resolver(mutation_name, class_name, session_func):
return func_impl
-def make_prefix_resolver(mutation_name, prefix=[]):
- for pre in prefix:
- Pre = pre.capitalize()
- if Pre in mutation_name:
- class_name = mutation_name.replace(Pre, '', 1)
- return make_mutation_resolver(mutation_name, class_name, pre)
- raise Exception
-
-def make_configure_resolver(mutation_name):
- class_name = mutation_name
- return make_mutation_resolver(mutation_name, class_name, 'configure')
-
-def make_config_file_resolver(mutation_name):
- return make_prefix_resolver(mutation_name, prefix=['save', 'load'])
-
-def make_image_resolver(mutation_name):
- return make_prefix_resolver(mutation_name, prefix=['add', 'delete'])
+def make_config_session_mutation_resolver(mutation_name):
+ return make_mutation_resolver(mutation_name, mutation_name,
+ convert_camel_case_to_snake(mutation_name))
def make_gen_op_mutation_resolver(mutation_name):
- class_name = mutation_name
- return make_mutation_resolver(mutation_name, class_name, 'gen_op_mutation')
+ return make_mutation_resolver(mutation_name, mutation_name, 'gen_op_mutation')
diff --git a/src/services/api/graphql/graphql/queries.py b/src/services/api/graphql/graphql/queries.py
index 8ae61b704..5f3a7d005 100644
--- a/src/services/api/graphql/graphql/queries.py
+++ b/src/services/api/graphql/graphql/queries.py
@@ -90,11 +90,12 @@ def make_query_resolver(query_name, class_name, session_func):
}
except OpModeError as e:
typename = type(e).__name__
+ msg = str(e)
return {
"success": False,
"errors": ['op_mode_error'],
"op_mode_error": {"name": f"{typename}",
- "message": op_mode_err_msg.get(typename, "Unknown"),
+ "message": msg if msg else op_mode_err_msg.get(typename, "Unknown"),
"vyos_code": op_mode_err_code.get(typename, 9999)}
}
except Exception as error:
@@ -105,18 +106,12 @@ def make_query_resolver(query_name, class_name, session_func):
return func_impl
-def make_show_config_resolver(query_name):
- class_name = query_name
- return make_query_resolver(query_name, class_name, 'show_config')
-
-def make_system_status_resolver(query_name):
- class_name = query_name
- return make_query_resolver(query_name, class_name, 'system_status')
-
-def make_show_resolver(query_name):
- class_name = query_name
- return make_query_resolver(query_name, class_name, 'show')
+def make_config_session_query_resolver(query_name):
+ return make_query_resolver(query_name, query_name,
+ convert_camel_case_to_snake(query_name))
def make_gen_op_query_resolver(query_name):
- class_name = query_name
- return make_query_resolver(query_name, class_name, 'gen_op_query')
+ return make_query_resolver(query_name, query_name, 'gen_op_query')
+
+def make_system_status_resolver(query_name):
+ return make_query_resolver(query_name, query_name, 'system_status')
diff --git a/src/services/api/graphql/graphql/schema/config_file.graphql b/src/services/api/graphql/graphql/schema/config_file.graphql
deleted file mode 100644
index a7263114b..000000000
--- a/src/services/api/graphql/graphql/schema/config_file.graphql
+++ /dev/null
@@ -1,29 +0,0 @@
-input SaveConfigFileInput {
- key: String!
- fileName: String
-}
-
-type SaveConfigFile {
- fileName: String
-}
-
-type SaveConfigFileResult {
- data: SaveConfigFile
- success: Boolean!
- errors: [String]
-}
-
-input LoadConfigFileInput {
- key: String!
- fileName: String!
-}
-
-type LoadConfigFile {
- fileName: String!
-}
-
-type LoadConfigFileResult {
- data: LoadConfigFile
- success: Boolean!
- errors: [String]
-}
diff --git a/src/services/api/graphql/graphql/schema/configsession.graphql b/src/services/api/graphql/graphql/schema/configsession.graphql
new file mode 100644
index 000000000..b1deac4b3
--- /dev/null
+++ b/src/services/api/graphql/graphql/schema/configsession.graphql
@@ -0,0 +1,115 @@
+
+input ShowConfigInput {
+ key: String!
+ path: [String!]!
+ configFormat: String = null
+}
+
+type ShowConfig {
+ result: Generic
+}
+
+type ShowConfigResult {
+ data: ShowConfig
+ success: Boolean!
+ errors: [String]
+}
+
+extend type Query {
+ ShowConfig(data: ShowConfigInput) : ShowConfigResult @configsessionquery
+}
+
+input ShowInput {
+ key: String!
+ path: [String!]!
+}
+
+type Show {
+ result: Generic
+}
+
+type ShowResult {
+ data: Show
+ success: Boolean!
+ errors: [String]
+}
+
+extend type Query {
+ Show(data: ShowInput) : ShowResult @configsessionquery
+}
+
+input SaveConfigFileInput {
+ key: String!
+ fileName: String = null
+}
+
+type SaveConfigFile {
+ result: Generic
+}
+
+type SaveConfigFileResult {
+ data: SaveConfigFile
+ success: Boolean!
+ errors: [String]
+}
+
+extend type Mutation {
+ SaveConfigFile(data: SaveConfigFileInput) : SaveConfigFileResult @configsessionmutation
+}
+
+input LoadConfigFileInput {
+ key: String!
+ fileName: String!
+}
+
+type LoadConfigFile {
+ result: Generic
+}
+
+type LoadConfigFileResult {
+ data: LoadConfigFile
+ success: Boolean!
+ errors: [String]
+}
+
+extend type Mutation {
+ LoadConfigFile(data: LoadConfigFileInput) : LoadConfigFileResult @configsessionmutation
+}
+
+input AddSystemImageInput {
+ key: String!
+ location: String!
+}
+
+type AddSystemImage {
+ result: Generic
+}
+
+type AddSystemImageResult {
+ data: AddSystemImage
+ success: Boolean!
+ errors: [String]
+}
+
+extend type Mutation {
+ AddSystemImage(data: AddSystemImageInput) : AddSystemImageResult @configsessionmutation
+}
+
+input DeleteSystemImageInput {
+ key: String!
+ name: String!
+}
+
+type DeleteSystemImage {
+ result: Generic
+}
+
+type DeleteSystemImageResult {
+ data: DeleteSystemImage
+ success: Boolean!
+ errors: [String]
+}
+
+extend type Mutation {
+ DeleteSystemImage(data: DeleteSystemImageInput) : DeleteSystemImageResult @configsessionmutation
+} \ No newline at end of file
diff --git a/src/services/api/graphql/graphql/schema/dhcp_server.graphql b/src/services/api/graphql/graphql/schema/dhcp_server.graphql
deleted file mode 100644
index 345c349ac..000000000
--- a/src/services/api/graphql/graphql/schema/dhcp_server.graphql
+++ /dev/null
@@ -1,36 +0,0 @@
-input DhcpServerConfigInput {
- key: String!
- sharedNetworkName: String
- subnet: String
- defaultRouter: String
- nameServer: String
- domainName: String
- lease: Int
- range: Int
- start: String
- stop: String
- dnsForwardingAllowFrom: String
- dnsForwardingCacheSize: Int
- dnsForwardingListenAddress: String
-}
-
-type DhcpServerConfig {
- sharedNetworkName: String
- subnet: String
- defaultRouter: String
- nameServer: String
- domainName: String
- lease: Int
- range: Int
- start: String
- stop: String
- dnsForwardingAllowFrom: String
- dnsForwardingCacheSize: Int
- dnsForwardingListenAddress: String
-}
-
-type CreateDhcpServerResult {
- data: DhcpServerConfig
- success: Boolean!
- errors: [String]
-}
diff --git a/src/services/api/graphql/graphql/schema/firewall_group.graphql b/src/services/api/graphql/graphql/schema/firewall_group.graphql
deleted file mode 100644
index 9454d2997..000000000
--- a/src/services/api/graphql/graphql/schema/firewall_group.graphql
+++ /dev/null
@@ -1,101 +0,0 @@
-input CreateFirewallAddressGroupInput {
- key: String!
- name: String!
- address: [String]
-}
-
-type CreateFirewallAddressGroup {
- name: String!
- address: [String]
-}
-
-type CreateFirewallAddressGroupResult {
- data: CreateFirewallAddressGroup
- success: Boolean!
- errors: [String]
-}
-
-input UpdateFirewallAddressGroupMembersInput {
- key: String!
- name: String!
- address: [String!]!
-}
-
-type UpdateFirewallAddressGroupMembers {
- name: String!
- address: [String!]!
-}
-
-type UpdateFirewallAddressGroupMembersResult {
- data: UpdateFirewallAddressGroupMembers
- success: Boolean!
- errors: [String]
-}
-
-input RemoveFirewallAddressGroupMembersInput {
- key: String!
- name: String!
- address: [String!]!
-}
-
-type RemoveFirewallAddressGroupMembers {
- name: String!
- address: [String!]!
-}
-
-type RemoveFirewallAddressGroupMembersResult {
- data: RemoveFirewallAddressGroupMembers
- success: Boolean!
- errors: [String]
-}
-
-input CreateFirewallAddressIpv6GroupInput {
- key: String!
- name: String!
- address: [String]
-}
-
-type CreateFirewallAddressIpv6Group {
- name: String!
- address: [String]
-}
-
-type CreateFirewallAddressIpv6GroupResult {
- data: CreateFirewallAddressIpv6Group
- success: Boolean!
- errors: [String]
-}
-
-input UpdateFirewallAddressIpv6GroupMembersInput {
- key: String!
- name: String!
- address: [String!]!
-}
-
-type UpdateFirewallAddressIpv6GroupMembers {
- name: String!
- address: [String!]!
-}
-
-type UpdateFirewallAddressIpv6GroupMembersResult {
- data: UpdateFirewallAddressIpv6GroupMembers
- success: Boolean!
- errors: [String]
-}
-
-input RemoveFirewallAddressIpv6GroupMembersInput {
- key: String!
- name: String!
- address: [String!]!
-}
-
-type RemoveFirewallAddressIpv6GroupMembers {
- name: String!
- address: [String!]!
-}
-
-type RemoveFirewallAddressIpv6GroupMembersResult {
- data: RemoveFirewallAddressIpv6GroupMembers
- success: Boolean!
- errors: [String]
-}
diff --git a/src/services/api/graphql/graphql/schema/image.graphql b/src/services/api/graphql/graphql/schema/image.graphql
deleted file mode 100644
index 485033875..000000000
--- a/src/services/api/graphql/graphql/schema/image.graphql
+++ /dev/null
@@ -1,31 +0,0 @@
-input AddSystemImageInput {
- key: String!
- location: String!
-}
-
-type AddSystemImage {
- location: String
- result: String
-}
-
-type AddSystemImageResult {
- data: AddSystemImage
- success: Boolean!
- errors: [String]
-}
-
-input DeleteSystemImageInput {
- key: String!
- name: String!
-}
-
-type DeleteSystemImage {
- name: String
- result: String
-}
-
-type DeleteSystemImageResult {
- data: DeleteSystemImage
- success: Boolean!
- errors: [String]
-}
diff --git a/src/services/api/graphql/graphql/schema/interface_ethernet.graphql b/src/services/api/graphql/graphql/schema/interface_ethernet.graphql
deleted file mode 100644
index 8a17d919f..000000000
--- a/src/services/api/graphql/graphql/schema/interface_ethernet.graphql
+++ /dev/null
@@ -1,19 +0,0 @@
-input InterfaceEthernetConfigInput {
- key: String!
- interface: String
- address: String
- replace: Boolean = true
- description: String
-}
-
-type InterfaceEthernetConfig {
- interface: String
- address: String
- description: String
-}
-
-type CreateInterfaceEthernetResult {
- data: InterfaceEthernetConfig
- success: Boolean!
- errors: [String]
-}
diff --git a/src/services/api/graphql/graphql/schema/schema.graphql b/src/services/api/graphql/graphql/schema/schema.graphql
index 624be2620..2acecade4 100644
--- a/src/services/api/graphql/graphql/schema/schema.graphql
+++ b/src/services/api/graphql/graphql/schema/schema.graphql
@@ -3,34 +3,16 @@ schema {
mutation: Mutation
}
-directive @configure on FIELD_DEFINITION
-directive @configfile on FIELD_DEFINITION
-directive @show on FIELD_DEFINITION
-directive @showconfig on FIELD_DEFINITION
directive @systemstatus on FIELD_DEFINITION
-directive @image on FIELD_DEFINITION
+directive @configsessionquery on FIELD_DEFINITION
+directive @configsessionmutation on FIELD_DEFINITION
directive @genopquery on FIELD_DEFINITION
directive @genopmutation on FIELD_DEFINITION
scalar Generic
type Query {
- Show(data: ShowInput) : ShowResult @show
- ShowConfig(data: ShowConfigInput) : ShowConfigResult @showconfig
SystemStatus(data: SystemStatusInput) : SystemStatusResult @systemstatus
}
-type Mutation {
- CreateDhcpServer(data: DhcpServerConfigInput) : CreateDhcpServerResult @configure
- CreateInterfaceEthernet(data: InterfaceEthernetConfigInput) : CreateInterfaceEthernetResult @configure
- CreateFirewallAddressGroup(data: CreateFirewallAddressGroupInput) : CreateFirewallAddressGroupResult @configure
- UpdateFirewallAddressGroupMembers(data: UpdateFirewallAddressGroupMembersInput) : UpdateFirewallAddressGroupMembersResult @configure
- RemoveFirewallAddressGroupMembers(data: RemoveFirewallAddressGroupMembersInput) : RemoveFirewallAddressGroupMembersResult @configure
- CreateFirewallAddressIpv6Group(data: CreateFirewallAddressIpv6GroupInput) : CreateFirewallAddressIpv6GroupResult @configure
- UpdateFirewallAddressIpv6GroupMembers(data: UpdateFirewallAddressIpv6GroupMembersInput) : UpdateFirewallAddressIpv6GroupMembersResult @configure
- RemoveFirewallAddressIpv6GroupMembers(data: RemoveFirewallAddressIpv6GroupMembersInput) : RemoveFirewallAddressIpv6GroupMembersResult @configure
- SaveConfigFile(data: SaveConfigFileInput) : SaveConfigFileResult @configfile
- LoadConfigFile(data: LoadConfigFileInput) : LoadConfigFileResult @configfile
- AddSystemImage(data: AddSystemImageInput) : AddSystemImageResult @image
- DeleteSystemImage(data: DeleteSystemImageInput) : DeleteSystemImageResult @image
-}
+type Mutation
diff --git a/src/services/api/graphql/graphql/schema/show.graphql b/src/services/api/graphql/graphql/schema/show.graphql
deleted file mode 100644
index 278ed536b..000000000
--- a/src/services/api/graphql/graphql/schema/show.graphql
+++ /dev/null
@@ -1,15 +0,0 @@
-input ShowInput {
- key: String!
- path: [String!]!
-}
-
-type Show {
- path: [String]
- result: String
-}
-
-type ShowResult {
- data: Show
- success: Boolean!
- errors: [String]
-}
diff --git a/src/services/api/graphql/graphql/schema/show_config.graphql b/src/services/api/graphql/graphql/schema/show_config.graphql
deleted file mode 100644
index 5a1fe43da..000000000
--- a/src/services/api/graphql/graphql/schema/show_config.graphql
+++ /dev/null
@@ -1,21 +0,0 @@
-"""
-Use 'scalar Generic' for show config output, to avoid attempts to
-JSON-serialize in case of JSON output.
-"""
-
-input ShowConfigInput {
- key: String!
- path: [String!]!
- configFormat: String
-}
-
-type ShowConfig {
- path: [String]
- result: Generic
-}
-
-type ShowConfigResult {
- data: ShowConfig
- success: Boolean!
- errors: [String]
-}
diff --git a/src/services/api/graphql/session/composite/system_status.py b/src/services/api/graphql/session/composite/system_status.py
index 8dadcc9f3..3c1a3d45b 100755
--- a/src/services/api/graphql/session/composite/system_status.py
+++ b/src/services/api/graphql/session/composite/system_status.py
@@ -30,8 +30,8 @@ def get_system_version() -> dict:
return show_version.show(raw=True, funny=False)
def get_system_uptime() -> dict:
- show_uptime = load_op_mode_as_module('show_uptime.py')
- return show_uptime.get_raw_data()
+ show_uptime = load_op_mode_as_module('uptime.py')
+ return show_uptime._get_raw_data()
def get_system_ram_usage() -> dict:
show_ram = load_op_mode_as_module('memory.py')
diff --git a/src/services/api/graphql/session/session.py b/src/services/api/graphql/session/session.py
index 93e1c328e..f990e63d0 100644
--- a/src/services/api/graphql/session/session.py
+++ b/src/services/api/graphql/session/session.py
@@ -45,40 +45,6 @@ class Session:
except Exception:
self._op_mode_list = None
- def configure(self):
- session = self._session
- data = self._data
- func_base_name = self._name
-
- tmpl_file = f'{func_base_name}.tmpl'
- cmd_file = f'/tmp/{func_base_name}.cmds'
- tmpl_dir = directories['api_templates']
-
- try:
- render(cmd_file, tmpl_file, data, location=tmpl_dir)
- commands = []
- with open(cmd_file) as f:
- lines = f.readlines()
- for line in lines:
- commands.append(line.split())
- for cmd in commands:
- if cmd[0] == 'set':
- session.set(cmd[1:])
- elif cmd[0] == 'delete':
- session.delete(cmd[1:])
- else:
- raise ValueError('Operation must be "set" or "delete"')
- session.commit()
- except Exception as error:
- raise error
-
- def delete_path_if_childless(self, path):
- session = self._session
- config = Config(session.get_session_env())
- if not config.list_nodes(path):
- session.delete(path)
- session.commit()
-
def show_config(self):
session = self._session
data = self._data
@@ -87,14 +53,14 @@ class Session:
try:
out = session.show_config(data['path'])
if data.get('config_format', '') == 'json':
- config_tree = vyos.configtree.ConfigTree(out)
+ config_tree = ConfigTree(out)
out = json.loads(config_tree.to_json())
except Exception as error:
raise error
return out
- def save(self):
+ def save_config_file(self):
session = self._session
data = self._data
if 'file_name' not in data or not data['file_name']:
@@ -105,7 +71,7 @@ class Session:
except Exception as error:
raise error
- def load(self):
+ def load_config_file(self):
session = self._session
data = self._data
@@ -127,7 +93,7 @@ class Session:
return out
- def add(self):
+ def add_system_image(self):
session = self._session
data = self._data
@@ -138,7 +104,7 @@ class Session:
return res
- def delete(self):
+ def delete_system_image(self):
session = self._session
data = self._data
diff --git a/src/services/api/graphql/utils/config_session_function.py b/src/services/api/graphql/utils/config_session_function.py
new file mode 100644
index 000000000..fc0dd7a87
--- /dev/null
+++ b/src/services/api/graphql/utils/config_session_function.py
@@ -0,0 +1,28 @@
+# typing information for native configsession functions; used to generate
+# schema definition files
+import typing
+
+def show_config(path: list[str], configFormat: typing.Optional[str]):
+ pass
+
+def show(path: list[str]):
+ pass
+
+queries = {'show_config': show_config,
+ 'show': show}
+
+def save_config_file(fileName: typing.Optional[str]):
+ pass
+def load_config_file(fileName: str):
+ pass
+def add_system_image(location: str):
+ pass
+def delete_system_image(name: str):
+ pass
+
+mutations = {'save_config_file': save_config_file,
+ 'load_config_file': load_config_file,
+ 'add_system_image': add_system_image,
+ 'delete_system_image': delete_system_image}
+
+
diff --git a/src/services/api/graphql/utils/schema_from_config_session.py b/src/services/api/graphql/utils/schema_from_config_session.py
new file mode 100755
index 000000000..ea78aaf88
--- /dev/null
+++ b/src/services/api/graphql/utils/schema_from_config_session.py
@@ -0,0 +1,119 @@
+#!/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/>.
+#
+#
+# A utility to generate GraphQL schema defintions from typing information of
+# (wrappers of) native configsession functions.
+
+import os
+import json
+from inspect import signature, getmembers, isfunction, isclass, getmro
+from jinja2 import Template
+
+if __package__ is None or __package__ == '':
+ from util import snake_to_pascal_case, map_type_name
+else:
+ from . util import snake_to_pascal_case, map_type_name
+
+# this will be run locally before the build
+SCHEMA_PATH = '../graphql/schema'
+
+schema_data: dict = {'schema_name': '',
+ 'schema_fields': []}
+
+query_template = """
+input {{ schema_name }}Input {
+ key: String!
+ {%- for field_entry in schema_fields %}
+ {{ field_entry }}
+ {%- endfor %}
+}
+
+type {{ schema_name }} {
+ result: Generic
+}
+
+type {{ schema_name }}Result {
+ data: {{ schema_name }}
+ success: Boolean!
+ errors: [String]
+}
+
+extend type Query {
+ {{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @configsessionquery
+}
+"""
+
+mutation_template = """
+input {{ schema_name }}Input {
+ key: String!
+ {%- for field_entry in schema_fields %}
+ {{ field_entry }}
+ {%- endfor %}
+}
+
+type {{ schema_name }} {
+ result: Generic
+}
+
+type {{ schema_name }}Result {
+ data: {{ schema_name }}
+ success: Boolean!
+ errors: [String]
+}
+
+extend type Mutation {
+ {{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @configsessionmutation
+}
+"""
+
+def create_schema(func_name: str, func: callable, template: str) -> str:
+ sig = signature(func)
+
+ field_dict = {}
+ for k in sig.parameters:
+ field_dict[sig.parameters[k].name] = map_type_name(sig.parameters[k].annotation)
+
+ schema_fields = []
+ for k,v in field_dict.items():
+ schema_fields.append(k+': '+v)
+
+ schema_data['schema_name'] = snake_to_pascal_case(func_name)
+ schema_data['schema_fields'] = schema_fields
+
+ j2_template = Template(template)
+ res = j2_template.render(schema_data)
+
+ return res
+
+def generate_config_session_definitions():
+ from config_session_function import queries, mutations
+
+ results = []
+ for name,func in queries.items():
+ res = create_schema(name, func, query_template)
+ results.append(res)
+
+ for name,func in mutations.items():
+ res = create_schema(name, func, mutation_template)
+ results.append(res)
+
+ out = '\n'.join(results)
+ with open(f'{SCHEMA_PATH}/configsession.graphql', 'w') as f:
+ f.write(out)
+
+if __name__ == '__main__':
+ generate_config_session_definitions()
diff --git a/src/services/api/graphql/utils/schema_from_op_mode.py b/src/services/api/graphql/utils/schema_from_op_mode.py
index 379d15250..57d63628b 100755
--- a/src/services/api/graphql/utils/schema_from_op_mode.py
+++ b/src/services/api/graphql/utils/schema_from_op_mode.py
@@ -20,15 +20,16 @@
import os
import json
-import typing
from inspect import signature, getmembers, isfunction, isclass, getmro
from jinja2 import Template
from vyos.defaults import directories
if __package__ is None or __package__ == '':
from util import load_as_module, is_op_mode_function_name, is_show_function_name
+ from util import snake_to_pascal_case, map_type_name
else:
from . util import load_as_module, is_op_mode_function_name, is_show_function_name
+ from . util import snake_to_pascal_case, map_type_name
OP_MODE_PATH = directories['op_mode']
SCHEMA_PATH = directories['api_schema']
@@ -103,35 +104,12 @@ type {{ name }} implements OpModeError {
{%- endfor %}
"""
-def _snake_to_pascal_case(name: str) -> str:
- res = ''.join(map(str.title, name.split('_')))
- return res
-
-def _map_type_name(type_name: type, optional: bool = False) -> str:
- if type_name == str:
- return 'String!' if not optional else 'String = null'
- if type_name == int:
- return 'Int!' if not optional else 'Int = null'
- if type_name == bool:
- return 'Boolean!' if not optional else 'Boolean = false'
- if typing.get_origin(type_name) == list:
- if not optional:
- return f'[{_map_type_name(typing.get_args(type_name)[0])}]!'
- return f'[{_map_type_name(typing.get_args(type_name)[0])}]'
- # typing.Optional is typing.Union[_, NoneType]
- if (typing.get_origin(type_name) is typing.Union and
- typing.get_args(type_name)[1] == type(None)):
- return f'{_map_type_name(typing.get_args(type_name)[0], optional=True)}'
-
- # scalar 'Generic' is defined in schema.graphql
- return 'Generic'
-
def create_schema(func_name: str, base_name: str, func: callable) -> str:
sig = signature(func)
field_dict = {}
for k in sig.parameters:
- field_dict[sig.parameters[k].name] = _map_type_name(sig.parameters[k].annotation)
+ field_dict[sig.parameters[k].name] = map_type_name(sig.parameters[k].annotation)
# It is assumed that if one is generating a schema for a 'show_*'
# function, that 'get_raw_data' is present and 'raw' is desired.
@@ -142,7 +120,7 @@ def create_schema(func_name: str, base_name: str, func: callable) -> str:
for k,v in field_dict.items():
schema_fields.append(k+': '+v)
- schema_data['schema_name'] = _snake_to_pascal_case(func_name + '_' + base_name)
+ schema_data['schema_name'] = snake_to_pascal_case(func_name + '_' + base_name)
schema_data['schema_fields'] = schema_fields
if is_show_function_name(func_name):
diff --git a/src/services/api/graphql/utils/util.py b/src/services/api/graphql/utils/util.py
index 073126853..da2bcdb5b 100644
--- a/src/services/api/graphql/utils/util.py
+++ b/src/services/api/graphql/utils/util.py
@@ -15,6 +15,7 @@
import os
import re
+import typing
import importlib.util
from vyos.defaults import directories
@@ -74,3 +75,26 @@ def split_compound_op_mode_name(name: str, files: list):
pair = (pair[0], f[0])
return pair
return (name, '')
+
+def snake_to_pascal_case(name: str) -> str:
+ res = ''.join(map(str.title, name.split('_')))
+ return res
+
+def map_type_name(type_name: type, optional: bool = False) -> str:
+ if type_name == str:
+ return 'String!' if not optional else 'String = null'
+ if type_name == int:
+ return 'Int!' if not optional else 'Int = null'
+ if type_name == bool:
+ return 'Boolean!' if not optional else 'Boolean = false'
+ if typing.get_origin(type_name) == list:
+ if not optional:
+ return f'[{map_type_name(typing.get_args(type_name)[0])}]!'
+ return f'[{map_type_name(typing.get_args(type_name)[0])}]'
+ # typing.Optional is typing.Union[_, NoneType]
+ if (typing.get_origin(type_name) is typing.Union and
+ typing.get_args(type_name)[1] == type(None)):
+ return f'{map_type_name(typing.get_args(type_name)[0], optional=True)}'
+
+ # scalar 'Generic' is defined in schema.graphql
+ return 'Generic'
diff --git a/src/system/vyos-system-update-check.py b/src/system/vyos-system-update-check.py
new file mode 100755
index 000000000..c9597721b
--- /dev/null
+++ b/src/system/vyos-system-update-check.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import argparse
+import json
+import jmespath
+
+from pathlib import Path
+from sys import exit
+from time import sleep
+
+from vyos.util import call
+
+import vyos.version
+
+motd_file = Path('/run/motd.d/10-vyos-update')
+
+
+if __name__ == '__main__':
+ # Parse command arguments and get config
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-c',
+ '--config',
+ action='store',
+ help='Path to system-update-check configuration',
+ required=True,
+ type=Path)
+
+ args = parser.parse_args()
+ try:
+ config_path = Path(args.config)
+ config = json.loads(config_path.read_text())
+ except Exception as err:
+ print(
+ f'Configuration file "{config_path}" does not exist or malformed: {err}'
+ )
+ exit(1)
+
+ url_json = config.get('url')
+ local_data = vyos.version.get_full_version_data()
+ local_version = local_data.get('version')
+
+ while True:
+ remote_data = vyos.version.get_remote_version(url_json)
+ if remote_data:
+ url = jmespath.search('[0].url', remote_data)
+ remote_version = jmespath.search('[0].version', remote_data)
+ if local_version != remote_version and remote_version:
+ call(f'wall -n "Update available: {remote_version} \nUpdate URL: {url}"')
+ # MOTD used in /run/motd.d/10-update
+ motd_file.parent.mkdir(exist_ok=True)
+ motd_file.write_text(f'---\n'
+ f'Current version: {local_version}\n'
+ f'Update available: \033[1;34m{remote_version}\033[0m\n'
+ f'---\n')
+ # Check every 12 hours
+ sleep(43200)
diff --git a/src/systemd/vyos-system-update.service b/src/systemd/vyos-system-update.service
new file mode 100644
index 000000000..032e5a14c
--- /dev/null
+++ b/src/systemd/vyos-system-update.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=VyOS system udpate-check service
+After=network.target vyos-router.service
+
+[Service]
+Type=simple
+Restart=always
+ExecStart=/usr/bin/python3 /usr/libexec/vyos/system/vyos-system-update-check.py --config /run/vyos-system-update.conf
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src/validators/accel-radius-dictionary b/src/validators/accel-radius-dictionary
new file mode 100755
index 000000000..05287e770
--- /dev/null
+++ b/src/validators/accel-radius-dictionary
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+DICT_PATH=/usr/share/accel-ppp/radius
+NAME=$1
+
+if [ -n "$NAME" -a -e $DICT_PATH/dictionary.$NAME ]; then
+ exit 0
+else
+ echo "$NAME is not a valid RADIUS dictionary name"
+ echo "Please make sure that $DICT_PATH/dictionary.$NAME file exists"
+ exit 1
+fi
+
diff --git a/src/validators/bgp-extended-community b/src/validators/bgp-extended-community
new file mode 100755
index 000000000..b69ae3449
--- /dev/null
+++ b/src/validators/bgp-extended-community
@@ -0,0 +1,55 @@
+#!/usr/bin/env python3
+
+# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+from argparse import ArgumentParser
+from sys import exit
+
+from vyos.template import is_ipv4
+
+COMM_MAX_2_OCTET: int = 65535
+COMM_MAX_4_OCTET: int = 4294967295
+
+if __name__ == '__main__':
+ # add an argument with community
+ parser: ArgumentParser = ArgumentParser()
+ parser.add_argument('community', type=str)
+ args = parser.parse_args()
+ community: str = args.community
+ if community.count(':') != 1:
+ print("Invalid community format")
+ exit(1)
+ try:
+ # try to extract community parts from an argument
+ comm_left: str = community.split(':')[0]
+ comm_right: int = int(community.split(':')[1])
+
+ # check if left part is an IPv4 address
+ if is_ipv4(comm_left) and 0 <= comm_right <= COMM_MAX_2_OCTET:
+ exit()
+ # check if a left part is a number
+ if 0 <= int(comm_left) <= COMM_MAX_2_OCTET \
+ and 0 <= comm_right <= COMM_MAX_4_OCTET:
+ exit()
+
+ except Exception:
+ # fail if something was wrong
+ print("Invalid community format")
+ exit(1)
+
+ # fail if none of validators catched the value
+ print("Invalid community format")
+ exit(1) \ No newline at end of file
diff --git a/src/validators/bgp-large-community b/src/validators/bgp-large-community
new file mode 100755
index 000000000..386398308
--- /dev/null
+++ b/src/validators/bgp-large-community
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+
+# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+from argparse import ArgumentParser
+from sys import exit
+
+from vyos.template import is_ipv4
+
+COMM_MAX_4_OCTET: int = 4294967295
+
+if __name__ == '__main__':
+ # add an argument with community
+ parser: ArgumentParser = ArgumentParser()
+ parser.add_argument('community', type=str)
+ args = parser.parse_args()
+ community: str = args.community
+ if community.count(':') != 2:
+ print("Invalid community format")
+ exit(1)
+ try:
+ # try to extract community parts from an argument
+ comm_part1: int = int(community.split(':')[0])
+ comm_part2: int = int(community.split(':')[1])
+ comm_part3: int = int(community.split(':')[2])
+
+ # check compatibilities of left and right parts
+ if 0 <= comm_part1 <= COMM_MAX_4_OCTET \
+ and 0 <= comm_part2 <= COMM_MAX_4_OCTET \
+ and 0 <= comm_part3 <= COMM_MAX_4_OCTET:
+ exit(0)
+
+ except Exception:
+ # fail if something was wrong
+ print("Invalid community format")
+ exit(1)
+
+ # fail if none of validators catched the value
+ print("Invalid community format")
+ exit(1) \ No newline at end of file
diff --git a/src/validators/bgp-regular-community b/src/validators/bgp-regular-community
new file mode 100755
index 000000000..d43a71eae
--- /dev/null
+++ b/src/validators/bgp-regular-community
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+
+# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+from argparse import ArgumentParser
+from sys import exit
+
+from vyos.template import is_ipv4
+
+COMM_MAX_2_OCTET: int = 65535
+
+if __name__ == '__main__':
+ # add an argument with community
+ parser: ArgumentParser = ArgumentParser()
+ parser.add_argument('community', type=str)
+ args = parser.parse_args()
+ community: str = args.community
+ if community.count(':') != 1:
+ print("Invalid community format")
+ exit(1)
+ try:
+ # try to extract community parts from an argument
+ comm_left: int = int(community.split(':')[0])
+ comm_right: int = int(community.split(':')[1])
+
+ # check compatibilities of left and right parts
+ if 0 <= comm_left <= COMM_MAX_2_OCTET \
+ and 0 <= comm_right <= COMM_MAX_2_OCTET:
+ exit(0)
+ except Exception:
+ # fail if something was wrong
+ print("Invalid community format")
+ exit(1)
+
+ # fail if none of validators catched the value
+ print("Invalid community format")
+ exit(1) \ No newline at end of file
diff --git a/src/validators/range b/src/validators/range
deleted file mode 100755
index d4c25f3c4..000000000
--- a/src/validators/range
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2021 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import re
-import sys
-import argparse
-
-class MalformedRange(Exception):
- pass
-
-def validate_range(value, min=None, max=None):
- try:
- lower, upper = re.match(r'^(\d+)-(\d+)$', value).groups()
-
- lower, upper = int(lower), int(upper)
-
- if int(lower) > int(upper):
- raise MalformedRange("the lower bound exceeds the upper bound".format(value))
-
- if min is not None:
- if lower < min:
- raise MalformedRange("the lower bound must not be less than {}".format(min))
-
- if max is not None:
- if upper > max:
- raise MalformedRange("the upper bound must not be greater than {}".format(max))
-
- except (AttributeError, ValueError):
- raise MalformedRange("range syntax error")
-
-parser = argparse.ArgumentParser(description='Range validator.')
-parser.add_argument('--min', type=int, action='store')
-parser.add_argument('--max', type=int, action='store')
-parser.add_argument('value', action='store')
-
-if __name__ == '__main__':
- args = parser.parse_args()
-
- try:
- validate_range(args.value, min=args.min, max=args.max)
- except MalformedRange as e:
- print("Incorrect range '{}': {}".format(args.value, e))
- sys.exit(1)