From 201257fe60afc40d101d162cc08e2878dfa3467b Mon Sep 17 00:00:00 2001 From: zsdc Date: Tue, 4 Jan 2022 15:38:29 +0200 Subject: ipsec: T1925: Fixed `show vpn ipsec sa` output After the a1aaf4fb9c0e4111670ef3dd491796fa35a2311f commit, only single (latest) CHILD_SA for each connection can be displayed in the `show vpn ipsec sa` output. This commit backs the proper behavior for the command and adds a little optimization to the formatter to make it easier. --- src/op_mode/show_ipsec_sa.py | 186 +++++++++++++++++++++---------------------- 1 file changed, 92 insertions(+), 94 deletions(-) (limited to 'src') diff --git a/src/op_mode/show_ipsec_sa.py b/src/op_mode/show_ipsec_sa.py index e72f0f965..5b8f00dba 100755 --- a/src/op_mode/show_ipsec_sa.py +++ b/src/op_mode/show_ipsec_sa.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019 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,119 +14,117 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import re -import sys +from re import split as re_split +from sys import exit -import vici -import tabulate -import hurry.filesize +from hurry import filesize +from tabulate import tabulate +from vici import Session as vici_session + +from vyos.util import seconds_to_human -import vyos.util def convert(text): return int(text) if text.isdigit() else text.lower() + def alphanum_key(key): - return [convert(c) for c in re.split('([0-9]+)', str(key))] + return [convert(c) for c in re_split('([0-9]+)', str(key))] -def format_output(conns, sas): + +def format_output(sas): sa_data = [] - for peer, parent_conn in conns.items(): - if peer not in sas: - continue - - parent_sa = sas[peer] - child_sas = parent_sa['child-sas'] - installed_sas = {v['name'].decode(): v for k, v in child_sas.items() if v["state"] == b"INSTALLED"} - - # parent_sa["state"] = IKE state, child_sas["state"] = ESP state - state = 'down' - uptime = 'N/A' - - if parent_sa["state"] == b"ESTABLISHED" and installed_sas: - state = "up" - - remote_host = parent_sa["remote-host"].decode() - remote_id = parent_sa["remote-id"].decode() - - if remote_host == remote_id: - remote_id = "N/A" - - # The counters can only be obtained from the child SAs - for child_conn in parent_conn['children']: - if child_conn not in installed_sas: - data = [child_conn, "down", "N/A", "N/A", "N/A", "N/A", "N/A", "N/A"] - sa_data.append(data) - continue - - isa = installed_sas[child_conn] - csa_name = isa['name'] - csa_name = csa_name.decode() - - bytes_in = hurry.filesize.size(int(isa["bytes-in"].decode())) - bytes_out = hurry.filesize.size(int(isa["bytes-out"].decode())) - bytes_str = "{0}/{1}".format(bytes_in, bytes_out) - - pkts_in = hurry.filesize.size(int(isa["packets-in"].decode()), system=hurry.filesize.si) - pkts_out = hurry.filesize.size(int(isa["packets-out"].decode()), system=hurry.filesize.si) - pkts_str = "{0}/{1}".format(pkts_in, pkts_out) - # Remove B from <1K values - pkts_str = re.sub(r'B', r'', pkts_str) - - uptime = vyos.util.seconds_to_human(isa['install-time'].decode()) - - enc = isa["encr-alg"].decode() - if "encr-keysize" in isa: - key_size = isa["encr-keysize"].decode() - else: - key_size = "" - if "integ-alg" in isa: - hash = isa["integ-alg"].decode() - else: - hash = "" - if "dh-group" in isa: - dh_group = isa["dh-group"].decode() - else: - dh_group = "" - - proposal = enc - if key_size: - proposal = "{0}_{1}".format(proposal, key_size) - if hash: - proposal = "{0}/{1}".format(proposal, hash) - if dh_group: - proposal = "{0}/{1}".format(proposal, dh_group) - - data = [csa_name, state, uptime, bytes_str, pkts_str, remote_host, remote_id, proposal] - sa_data.append(data) + for sa in sas: + for parent_sa in sa.values(): + # create an item for each child-sa + for child_sa in parent_sa.get('child-sas', {}).values(): + # prepare a list for output data + sa_out_name = sa_out_state = sa_out_uptime = sa_out_bytes = sa_out_packets = sa_out_remote_addr = sa_out_remote_id = sa_out_proposal = 'N/A' + + # collect raw data + sa_name = child_sa.get('name') + sa_state = child_sa.get('state') + sa_uptime = child_sa.get('install-time') + sa_bytes_in = child_sa.get('bytes-in') + sa_bytes_out = child_sa.get('bytes-out') + sa_packets_in = child_sa.get('packets-in') + sa_packets_out = child_sa.get('packets-out') + sa_remote_addr = parent_sa.get('remote-host') + sa_remote_id = parent_sa.get('remote-id') + sa_proposal_encr_alg = child_sa.get('encr-alg') + sa_proposal_integ_alg = child_sa.get('integ-alg') + sa_proposal_encr_keysize = child_sa.get('encr-keysize') + sa_proposal_dh_group = child_sa.get('dh-group') + + # format data to display + if sa_name: + sa_out_name = sa_name.decode() + if sa_state: + if sa_state == b'INSTALLED': + sa_out_state = 'up' + else: + sa_out_state = 'down' + if sa_uptime: + sa_out_uptime = seconds_to_human(sa_uptime.decode()) + if sa_bytes_in and sa_bytes_out: + bytes_in = filesize.size(int(sa_bytes_in.decode())) + bytes_out = filesize.size(int(sa_bytes_out.decode())) + sa_out_bytes = f'{bytes_in}/{bytes_out}' + if sa_packets_in and sa_packets_out: + packets_in = filesize.size(int(sa_packets_in.decode()), + system=filesize.si) + packets_out = filesize.size(int(sa_packets_out.decode()), + system=filesize.si) + sa_out_packets = f'{packets_in}/{packets_out}' + if sa_remote_addr: + sa_out_remote_addr = sa_remote_addr.decode() + if sa_remote_id: + sa_out_remote_id = sa_remote_id.decode() + # format proposal + if sa_proposal_encr_alg: + sa_out_proposal = sa_proposal_encr_alg.decode() + if sa_proposal_encr_keysize: + sa_proposal_encr_keysize_str = sa_proposal_encr_keysize.decode() + sa_out_proposal = f'{sa_out_proposal}_{sa_proposal_encr_keysize_str}' + if sa_proposal_integ_alg: + sa_proposal_integ_alg_str = sa_proposal_integ_alg.decode() + sa_out_proposal = f'{sa_out_proposal}/{sa_proposal_integ_alg_str}' + if sa_proposal_dh_group: + sa_proposal_dh_group_str = sa_proposal_dh_group.decode() + sa_out_proposal = f'{sa_out_proposal}/{sa_proposal_dh_group_str}' + + # add a new item to output data + sa_data.append([ + sa_out_name, sa_out_state, sa_out_uptime, sa_out_bytes, + sa_out_packets, sa_out_remote_addr, sa_out_remote_id, + sa_out_proposal + ]) + + # return output data return sa_data + if __name__ == '__main__': try: - session = vici.Session() - conns = {} - sas = {} + session = vici_session() + sas = list(session.list_sas()) - for conn in session.list_conns(): - for key in conn: - conns[key] = conn[key] - - for sa in session.list_sas(): - for key in sa: - sas[key] = sa[key] - - headers = ["Connection", "State", "Uptime", "Bytes In/Out", "Packets In/Out", "Remote address", "Remote ID", "Proposal"] - sa_data = format_output(conns, sas) + sa_data = format_output(sas) sa_data = sorted(sa_data, key=alphanum_key) - output = tabulate.tabulate(sa_data, headers) + + headers = [ + "Connection", "State", "Uptime", "Bytes In/Out", "Packets In/Out", + "Remote address", "Remote ID", "Proposal" + ] + output = tabulate(sa_data, headers) print(output) except PermissionError: print("You do not have a permission to connect to the IPsec daemon") - sys.exit(1) + exit(1) except ConnectionRefusedError: print("IPsec is not runing") - sys.exit(1) + exit(1) except Exception as e: print("An error occured: {0}".format(e)) - sys.exit(1) + exit(1) -- cgit v1.2.3 From ed67750b94e8bc779ec0e2cf6d568a3f7292de13 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Sat, 29 Jan 2022 13:18:28 +0100 Subject: firewall: T4218: Adds a prefix to all user defined chains --- data/templates/firewall/nftables.tmpl | 4 ++-- data/templates/zone_policy/nftables.tmpl | 12 ++++++------ python/vyos/template.py | 3 ++- smoketest/scripts/cli/test_firewall.py | 6 +++--- smoketest/scripts/cli/test_zone_policy.py | 4 ++-- src/conf_mode/firewall-interface.py | 11 +++++++---- src/conf_mode/firewall.py | 7 +++++-- src/op_mode/firewall.py | 3 ++- 8 files changed, 29 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/data/templates/firewall/nftables.tmpl b/data/templates/firewall/nftables.tmpl index 33c821e84..468a5a32f 100644 --- a/data/templates/firewall/nftables.tmpl +++ b/data/templates/firewall/nftables.tmpl @@ -32,7 +32,7 @@ table ip filter { {% endif %} {% if name is defined %} {% for name_text, conf in name.items() %} - chain {{ name_text }} { + chain NAME_{{ name_text }} { {% if conf.rule is defined %} {% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not defined %} {{ rule_conf | nft_rule(name_text, rule_id) }} @@ -82,7 +82,7 @@ table ip6 filter { {% endif %} {% if ipv6_name is defined %} {% for name_text, conf in ipv6_name.items() %} - chain {{ name_text }} { + chain NAME6_{{ name_text }} { {% if conf.rule is defined %} {% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not defined %} {{ rule_conf | nft_rule(name_text, rule_id, 'ip6') }} diff --git a/data/templates/zone_policy/nftables.tmpl b/data/templates/zone_policy/nftables.tmpl index e59208a0d..093da6bd8 100644 --- a/data/templates/zone_policy/nftables.tmpl +++ b/data/templates/zone_policy/nftables.tmpl @@ -13,7 +13,7 @@ table ip filter { chain VZONE_{{ zone_name }}_IN { iifname lo counter return {% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.name is defined %} - iifname { {{ zone[from_zone].interface | join(",") }} } counter jump {{ from_conf.firewall.name }} + iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME_{{ from_conf.firewall.name }} iifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endfor %} counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }} @@ -21,7 +21,7 @@ table ip filter { chain VZONE_{{ zone_name }}_OUT { oifname lo counter return {% for from_zone, from_conf in zone_conf.from_local.items() if from_conf.firewall.name is defined %} - oifname { {{ zone[from_zone].interface | join(",") }} } counter jump {{ from_conf.firewall.name }} + oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME_{{ from_conf.firewall.name }} oifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endfor %} counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }} @@ -34,7 +34,7 @@ table ip filter { {% endif %} {% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.name is defined %} {% if zone[from_zone].local_zone is not defined %} - iifname { {{ zone[from_zone].interface | join(",") }} } counter jump {{ from_conf.firewall.name }} + iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME_{{ from_conf.firewall.name }} iifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endif %} {% endfor %} @@ -50,7 +50,7 @@ table ip6 filter { chain VZONE6_{{ zone_name }}_IN { iifname lo counter return {% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.ipv6_name is defined %} - iifname { {{ zone[from_zone].interface | join(",") }} } counter jump {{ from_conf.firewall.ipv6_name }} + iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }} iifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endfor %} counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }} @@ -58,7 +58,7 @@ table ip6 filter { chain VZONE6_{{ zone_name }}_OUT { oifname lo counter return {% for from_zone, from_conf in zone_conf.from_local.items() if from_conf.firewall.ipv6_name is defined %} - oifname { {{ zone[from_zone].interface | join(",") }} } counter jump {{ from_conf.firewall.ipv6_name }} + oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }} oifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endfor %} counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }} @@ -71,7 +71,7 @@ table ip6 filter { {% endif %} {% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.ipv6_name is defined %} {% if zone[from_zone].local_zone is not defined %} - iifname { {{ zone[from_zone].interface | join(",") }} } counter jump {{ from_conf.firewall.ipv6_name }} + iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }} iifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endif %} {% endfor %} diff --git a/python/vyos/template.py b/python/vyos/template.py index 633b28ade..3675aef5d 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -548,6 +548,7 @@ def nft_intra_zone_action(zone_conf, ipv6=False): if 'intra_zone_filtering' in zone_conf: intra_zone = zone_conf['intra_zone_filtering'] fw_name = 'ipv6_name' if ipv6 else 'name' + name_prefix = 'NAME6_' if ipv6 else 'NAME_' if 'action' in intra_zone: if intra_zone['action'] == 'accept': @@ -555,5 +556,5 @@ def nft_intra_zone_action(zone_conf, ipv6=False): return intra_zone['action'] elif dict_search_args(intra_zone, 'firewall', fw_name): name = dict_search_args(intra_zone, 'firewall', fw_name) - return f'jump {name}' + return f'jump {name_prefix}{name}' return 'return' diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index 6b74e6c92..ecc0c29a0 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -63,7 +63,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_commit() nftables_search = [ - ['iifname "eth0"', 'jump smoketest'], + ['iifname "eth0"', 'jump NAME_smoketest'], ['ip saddr { 172.16.99.0/24 }', 'ip daddr 172.16.10.10', 'th dport { 53, 123 }', 'return'], ['ether saddr { 00:01:02:03:04:05 }', 'return'] ] @@ -94,7 +94,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_commit() nftables_search = [ - ['iifname "eth0"', 'jump smoketest'], + ['iifname "eth0"', 'jump NAME_smoketest'], ['saddr 172.16.20.10', 'daddr 172.16.10.10', 'return'], ['tcp flags & (syn | ack) == syn', 'tcp dport { 8888 }', 'reject'], ['smoketest default-action', 'drop'] @@ -124,7 +124,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_commit() nftables_search = [ - ['iifname "eth0"', 'jump v6-smoketest'], + ['iifname "eth0"', 'jump NAME6_v6-smoketest'], ['saddr 2002::1', 'daddr 2002::1:1', 'return'], ['meta l4proto { tcp, udp }', 'th dport { 8888 }', 'reject'], ['smoketest default-action', 'drop'] diff --git a/smoketest/scripts/cli/test_zone_policy.py b/smoketest/scripts/cli/test_zone_policy.py index c0af6164b..00dfe0182 100755 --- a/smoketest/scripts/cli/test_zone_policy.py +++ b/smoketest/scripts/cli/test_zone_policy.py @@ -44,8 +44,8 @@ class TestZonePolicy(VyOSUnitTestSHIM.TestCase): ['oifname { "eth0" }', 'jump VZONE_smoketest-eth0'], ['jump VZONE_smoketest-local_IN'], ['jump VZONE_smoketest-local_OUT'], - ['iifname { "eth0" }', 'jump smoketest'], - ['oifname { "eth0" }', 'jump smoketest'] + ['iifname { "eth0" }', 'jump NAME_smoketest'], + ['oifname { "eth0" }', 'jump NAME_smoketest'] ] nftables_output = cmd('sudo nft list table ip filter') diff --git a/src/conf_mode/firewall-interface.py b/src/conf_mode/firewall-interface.py index a7442ecbd..9a5d278e9 100755 --- a/src/conf_mode/firewall-interface.py +++ b/src/conf_mode/firewall-interface.py @@ -31,6 +31,9 @@ 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', @@ -127,7 +130,7 @@ def apply(if_firewall): name = dict_search_args(if_firewall, direction, 'name') if name: - rule_exists = cleanup_rule('ip filter', chain, if_prefix, ifname, name) + rule_exists = cleanup_rule('ip filter', chain, if_prefix, ifname, f'{NAME_PREFIX}{name}') if not rule_exists: rule_action = 'insert' @@ -138,13 +141,13 @@ def apply(if_firewall): 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}') + 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, 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' @@ -155,7 +158,7 @@ def apply(if_firewall): 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 {ipv6_name}') + 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) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 358b938e3..5b6c57d04 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -54,6 +54,9 @@ sysfs_config = { 'twa_hazards_protection': {'sysfs': '/proc/sys/net/ipv4/tcp_rfc1337'} } +NAME_PREFIX = 'NAME_' +NAME6_PREFIX = 'NAME6_' + preserve_chains = [ 'INPUT', 'FORWARD', @@ -281,9 +284,9 @@ def cleanup_commands(firewall): else: commands.append(f'flush chain {table} {chain}') elif chain not in preserve_chains and not chain.startswith("VZONE"): - if table == 'ip filter' and dict_search_args(firewall, 'name', chain): + if table == 'ip filter' and dict_search_args(firewall, 'name', chain.replace(NAME_PREFIX, "", 1)): commands.append(f'flush chain {table} {chain}') - elif table == 'ip6 filter' and dict_search_args(firewall, 'ipv6_name', chain): + elif table == 'ip6 filter' and dict_search_args(firewall, 'ipv6_name', chain.replace(NAME6_PREFIX, "", 1)): commands.append(f'flush chain {table} {chain}') else: commands += cleanup_rule(table, chain) diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py index b6bb5b802..3146fc357 100755 --- a/src/op_mode/firewall.py +++ b/src/op_mode/firewall.py @@ -88,7 +88,8 @@ def get_config_firewall(conf, name=None, ipv6=False, interfaces=True): def get_nftables_details(name, ipv6=False): suffix = '6' if ipv6 else '' - command = f'sudo nft list chain ip{suffix} filter {name}' + name_prefix = 'NAME6_' if ipv6 else 'NAME_' + command = f'sudo nft list chain ip{suffix} filter {name_prefix}{name}' try: results = cmd(command) except: -- cgit v1.2.3 From 985a9e8536cb7f049e82dd1c7333ecced34563fa Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:34:05 +0100 Subject: firewall: T4216: Add support for negated firewall groups --- python/vyos/firewall.py | 25 +++++++++++++++++++++---- src/conf_mode/firewall.py | 4 ++++ 2 files changed, 25 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index a2e133217..a74fd922a 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -104,13 +104,25 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): group = side_conf['group'] if 'address_group' in group: group_name = group['address_group'] - output.append(f'{ip_name} {prefix}addr $A{def_suffix}_{group_name}') + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + output.append(f'{ip_name} {prefix}addr {operator} $A{def_suffix}_{group_name}') elif 'network_group' in group: group_name = group['network_group'] - output.append(f'{ip_name} {prefix}addr $N{def_suffix}_{group_name}') + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + output.append(f'{ip_name} {prefix}addr {operator} $N{def_suffix}_{group_name}') if 'mac_group' in group: group_name = group['mac_group'] - output.append(f'ether {prefix}addr $M_{group_name}') + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + output.append(f'ether {prefix}addr {operator} $M_{group_name}') if 'port_group' in group: proto = rule_conf['protocol'] group_name = group['port_group'] @@ -118,7 +130,12 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): if proto == 'tcp_udp': proto = 'th' - output.append(f'{proto} {prefix}port $P_{group_name}') + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + + output.append(f'{proto} {prefix}port {operator} $P_{group_name}') if 'log' in rule_conf and rule_conf['log'] == 'enable': action = rule_conf['action'] if 'action' in rule_conf else 'accept' diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 5b6c57d04..064b2d5a3 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -204,6 +204,10 @@ def verify_rule(firewall, rule_conf, ipv6): for group in valid_groups: if group in side_conf['group']: group_name = side_conf['group'][group] + + if group_name and group_name[0] == '!': + group_name = group_name[1:] + fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group error_group = fw_group.replace("_", "-") group_obj = dict_search_args(firewall, 'group', fw_group, group_name) -- cgit v1.2.3 From 8532f2c391e895d7cd4c10b6d83d1e26973202a3 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Sun, 30 Jan 2022 00:07:35 +0100 Subject: policy: T4213: Fix duplicate commands from multiple rules with single table --- src/conf_mode/policy-route.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py index 7dcab4b58..82f668acf 100755 --- a/src/conf_mode/policy-route.py +++ b/src/conf_mode/policy-route.py @@ -206,6 +206,7 @@ def apply_table_marks(policy): for route in ['route', 'route6']: if route in policy: cmd_str = 'ip' if route == 'route' else 'ip -6' + tables = [] for name, pol_conf in policy[route].items(): if 'rule' in pol_conf: for rule_id, rule_conf in pol_conf['rule'].items(): @@ -213,6 +214,9 @@ def apply_table_marks(policy): if set_table: if set_table == 'main': set_table = '254' + if set_table in tables: + continue + tables.append(set_table) table_mark = mark_offset - int(set_table) cmd(f'{cmd_str} rule add pref {set_table} fwmark {table_mark} table {set_table}') -- cgit v1.2.3 From c6c562eca6ff469f603697f7f1d9319b2a5504a3 Mon Sep 17 00:00:00 2001 From: Henning Surmeier Date: Fri, 28 Jan 2022 23:55:06 +0100 Subject: policy: T4219: add local-route(6) incoming-interface --- .../include/interface/inbound-interface.xml.i | 10 ++++ interface-definitions/policy-local-route.xml.in | 2 + smoketest/scripts/cli/test_policy.py | 53 +++++++++++++++++++++- src/conf_mode/policy-local-route.py | 34 ++++++++++++-- 4 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 interface-definitions/include/interface/inbound-interface.xml.i (limited to 'src') diff --git a/interface-definitions/include/interface/inbound-interface.xml.i b/interface-definitions/include/interface/inbound-interface.xml.i new file mode 100644 index 000000000..5a8d47280 --- /dev/null +++ b/interface-definitions/include/interface/inbound-interface.xml.i @@ -0,0 +1,10 @@ + + + + Inbound Interface + + + + + + diff --git a/interface-definitions/policy-local-route.xml.in b/interface-definitions/policy-local-route.xml.in index 11b1e04d9..573a7963f 100644 --- a/interface-definitions/policy-local-route.xml.in +++ b/interface-definitions/policy-local-route.xml.in @@ -88,6 +88,7 @@ + #include @@ -177,6 +178,7 @@ + #include diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py index 73d93c986..491f1766d 100755 --- a/smoketest/scripts/cli/test_policy.py +++ b/smoketest/scripts/cli/test_policy.py @@ -1206,6 +1206,32 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): self.assertEqual(sort_ip(tmp), sort_ip(original)) + # Test set table for sources with iif + def test_iif_sources_table_id(self): + path = base_path + ['local-route'] + + sources = ['203.0.113.11', '203.0.113.12'] + iif = 'lo' + rule = '100' + table = '150' + + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'inbound-interface', iif]) + for src in sources: + self.cli_set(path + ['rule', rule, 'source', src]) + + self.cli_commit() + + # Check generated configuration + # Expected values + original = """ + 100: from 203.0.113.11 iif lo lookup 150 + 100: from 203.0.113.12 iif lo lookup 150 + """ + tmp = cmd('ip rule show prio 100') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + # Test set table for sources and destinations with fwmark def test_fwmark_sources_destination_table_id(self): path = base_path + ['local-route'] @@ -1318,6 +1344,31 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): self.assertEqual(sort_ip(tmp), sort_ip(original)) + # Test set table for sources with iif ipv6 + def test_iif_sources_ipv6_table_id(self): + path = base_path + ['local-route6'] + + sources = ['2001:db8:1338::/126', '2001:db8:1339::/126'] + iif = 'lo' + rule = '102' + table = '150' + for src in sources: + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'source', src]) + self.cli_set(path + ['rule', rule, 'inbound-interface', iif]) + + self.cli_commit() + + # Check generated configuration + # Expected values + original = """ + 102: from 2001:db8:1338::/126 iif lo lookup 150 + 102: from 2001:db8:1339::/126 iif lo lookup 150 + """ + tmp = cmd('ip -6 rule show prio 102') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + # Test set table for sources and destinations with fwmark ipv6 def test_fwmark_sources_destination_ipv6_table_id(self): path = base_path + ['local-route6'] @@ -1384,7 +1435,7 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): 103: from 2001:db8:1338::/126 to 2001:db8:16::/48 fwmark 0x17 lookup 150 103: from 2001:db8:1339::/56 to 2001:db8:13::/48 fwmark 0x17 lookup 150 103: from 2001:db8:1339::/56 to 2001:db8:16::/48 fwmark 0x17 lookup 150 - 103: from 2001:db8:1338::/126 to 2001:db8:13::/48 fwmark 0x17 lookup 150 + 103: from 2001:db8:1338::/126 to 2001:db8:13::/48 fwmark 0x17 lookup 150 """ tmp = cmd('ip rule show prio 103') tmp_v6 = cmd('ip -6 rule show prio 103') diff --git a/src/conf_mode/policy-local-route.py b/src/conf_mode/policy-local-route.py index 71183c6ba..0990039c1 100755 --- a/src/conf_mode/policy-local-route.py +++ b/src/conf_mode/policy-local-route.py @@ -18,6 +18,7 @@ import os from sys import exit +from netifaces import interfaces from vyos.config import Config from vyos.configdict import dict_merge from vyos.configdict import node_changed @@ -51,12 +52,15 @@ def get_config(config=None): for rule in (tmp or []): src = leaf_node_changed(conf, base_rule + [rule, 'source']) fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark']) + iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface']) dst = leaf_node_changed(conf, base_rule + [rule, 'destination']) rule_def = {} if src: rule_def = dict_merge({'source' : src}, rule_def) if fwmk: rule_def = dict_merge({'fwmark' : fwmk}, rule_def) + if iif: + rule_def = dict_merge({'inbound_interface' : iif}, rule_def) if dst: rule_def = dict_merge({'destination' : dst}, rule_def) dict = dict_merge({dict_id : {rule : rule_def}}, dict) @@ -72,6 +76,7 @@ def get_config(config=None): for rule, rule_config in pbr[route]['rule'].items(): src = leaf_node_changed(conf, base_rule + [rule, 'source']) fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark']) + iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface']) dst = leaf_node_changed(conf, base_rule + [rule, 'destination']) # keep track of changes in configuration # otherwise we might remove an existing node although nothing else has changed @@ -100,6 +105,13 @@ def get_config(config=None): changed = True if len(fwmk) > 0: rule_def = dict_merge({'fwmark' : fwmk}, rule_def) + if iif is None: + if 'inbound_interface' in rule_config: + rule_def = dict_merge({'inbound_interface': rule_config['inbound_interface']}, rule_def) + else: + changed = True + if len(iif) > 0: + rule_def = dict_merge({'inbound_interface' : iif}, rule_def) if dst is None: if 'destination' in rule_config: rule_def = dict_merge({'destination': rule_config['destination']}, rule_def) @@ -125,11 +137,18 @@ def verify(pbr): pbr_route = pbr[route] if 'rule' in pbr_route: for rule in pbr_route['rule']: - if 'source' not in pbr_route['rule'][rule] and 'destination' not in pbr_route['rule'][rule] and 'fwmark' not in pbr_route['rule'][rule]: - raise ConfigError('Source or destination address or fwmark is required!') + if 'source' not in pbr_route['rule'][rule] \ + and 'destination' not in pbr_route['rule'][rule] \ + and 'fwmark' not in pbr_route['rule'][rule] \ + and 'inbound_interface' not in pbr_route['rule'][rule]: + raise ConfigError('Source or destination address or fwmark or inbound-interface is required!') else: if 'set' not in pbr_route['rule'][rule] or 'table' not in pbr_route['rule'][rule]['set']: raise ConfigError('Table set is required!') + if 'inbound_interface' in pbr_route['rule'][rule]: + interface = pbr_route['rule'][rule]['inbound_interface'] + if interface not in interfaces(): + raise ConfigError(f'Interface "{interface}" does not exist') return None @@ -159,7 +178,10 @@ def apply(pbr): rule_config['fwmark'] = rule_config['fwmark'] if 'fwmark' in rule_config else [''] for fwmk in rule_config['fwmark']: f_fwmk = '' if fwmk == '' else f' fwmark {fwmk} ' - call(f'ip{v6} rule del prio {rule} {f_src}{f_dst}{f_fwmk}') + rule_config['inbound_interface'] = rule_config['inbound_interface'] if 'inbound_interface' in rule_config else [''] + for iif in rule_config['inbound_interface']: + f_iif = '' if iif == '' else f' iif {iif} ' + call(f'ip{v6} rule del prio {rule} {f_src}{f_dst}{f_fwmk}{f_iif}') # Generate new config for route in ['local_route', 'local_route6']: @@ -183,7 +205,11 @@ def apply(pbr): if 'fwmark' in rule_config: fwmk = rule_config['fwmark'] f_fwmk = f' fwmark {fwmk} ' - call(f'ip{v6} rule add prio {rule} {f_src}{f_dst}{f_fwmk} lookup {table}') + f_iif = '' + if 'inbound_interface' in rule_config: + iif = rule_config['inbound_interface'] + f_iif = f' iif {iif} ' + call(f'ip{v6} rule add prio {rule} {f_src}{f_dst}{f_fwmk}{f_iif} lookup {table}') return None -- cgit v1.2.3 From ff2cc45f8ba6d7ad1bc75ef384643692a54f31cc Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Sun, 30 Jan 2022 22:52:49 +0100 Subject: firewall: T2199: Fix errors when referencing an empty chain --- src/conf_mode/firewall.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 064b2d5a3..9dec2143e 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -73,6 +73,9 @@ preserve_chains = [ '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', 'network_group', @@ -248,27 +251,29 @@ def verify(firewall): name = dict_search_args(if_firewall, direction, 'name') ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name') - if name and not dict_search_args(firewall, 'name', name): + if name and dict_search_args(firewall, 'name', name) == None: raise ConfigError(f'Firewall name "{name}" is still referenced on interface {ifname}') - if ipv6_name and not dict_search_args(firewall, 'ipv6_name', 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}') for fw_name, used_names in firewall['zone_policy'].items(): for name in used_names: - if not dict_search_args(firewall, fw_name, name): + if dict_search_args(firewall, fw_name, name) == None: raise ConfigError(f'Firewall {fw_name.replace("_", "-")} "{name}" is still referenced in zone-policy') return None def cleanup_rule(table, jump_chain): commands = [] - results = cmd(f'nft -a list table {table}').split("\n") - for line in results: - if f'jump {jump_chain}' in line: - handle_search = re.search('handle (\d+)', line) - if handle_search: - commands.append(f'delete rule {table} {chain} handle {handle_search[1]}') + chains = nft_iface_chains if table == 'ip filter' else nft6_iface_chains + for chain in chains: + results = cmd(f'nft -a list chain {table} {chain}').split("\n") + for line in results: + if f'jump {jump_chain}' in line: + handle_search = re.search('handle (\d+)', line) + if handle_search: + commands.append(f'delete rule {table} {chain} handle {handle_search[1]}') return commands def cleanup_commands(firewall): @@ -288,9 +293,9 @@ def cleanup_commands(firewall): else: commands.append(f'flush chain {table} {chain}') elif chain not in preserve_chains and not chain.startswith("VZONE"): - if table == 'ip filter' and dict_search_args(firewall, 'name', chain.replace(NAME_PREFIX, "", 1)): + if table == 'ip filter' and dict_search_args(firewall, 'name', chain.replace(NAME_PREFIX, "", 1)) != None: commands.append(f'flush chain {table} {chain}') - elif table == 'ip6 filter' and dict_search_args(firewall, 'ipv6_name', chain.replace(NAME6_PREFIX, "", 1)): + elif table == 'ip6 filter' and dict_search_args(firewall, 'ipv6_name', chain.replace(NAME6_PREFIX, "", 1)) != None: commands.append(f'flush chain {table} {chain}') else: commands += cleanup_rule(table, chain) -- cgit v1.2.3 From 494ca8ffa043fd90c83cd052ff7da170646cec05 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 31 Jan 2022 21:57:24 +0100 Subject: upnpd: T3420: code cleanup --- src/conf_mode/service_upnp.py | 44 ++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/conf_mode/service_upnp.py b/src/conf_mode/service_upnp.py index 638296f45..d21b31990 100755 --- a/src/conf_mode/service_upnp.py +++ b/src/conf_mode/service_upnp.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 @@ -24,7 +24,6 @@ from ipaddress import IPv6Network from vyos.config import Config from vyos.configdict import dict_merge -from vyos.configdict import dict_search from vyos.configdict import get_interface_dict from vyos.configverify import verify_vrf from vyos.util import call @@ -43,17 +42,18 @@ def get_config(config=None): conf = config else: conf = Config() + base = ['service', 'upnp'] upnpd = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - + if not upnpd: return None - - if dict_search('rule', upnpd): + + if 'rule' in upnpd: default_member_values = defaults(base + ['rule']) for rule,rule_config in upnpd['rule'].items(): upnpd['rule'][rule] = dict_merge(default_member_values, upnpd['rule'][rule]) - + uuidgen = uuid.uuid1() upnpd.update({'uuid': uuidgen}) @@ -62,7 +62,7 @@ def get_config(config=None): def get_all_interface_addr(prefix, filter_dev, filter_family): list_addr = [] interfaces = netifaces.interfaces() - + for interface in interfaces: if filter_dev and interface in filter_dev: continue @@ -87,27 +87,28 @@ def get_all_interface_addr(prefix, filter_dev, filter_family): list_addr.append(addr['addr'] + prefix) else: list_addr.append(addr['addr']) - + return list_addr def verify(upnpd): if not upnpd: return None - + if 'wan_interface' not in upnpd: raise ConfigError('To enable UPNP, you must have the "wan-interface" option!') - - if dict_search('rules', upnpd): - for rule,rule_config in upnpd['rule'].items(): + + if 'rule' in upnpd: + for rule, rule_config in upnpd['rule'].items(): for option in ['external_port_range', 'internal_port_range', 'ip', 'action']: if option not in rule_config: - raise ConfigError(f'A UPNP rule must have an "{option}" option!') - - if dict_search('stun', upnpd): + tmp = option.replace('_', '-') + raise ConfigError(f'Every UPNP rule requires "{tmp}" to be set!') + + if 'stun' in upnpd: for option in ['host', 'port']: if option not in upnpd['stun']: raise ConfigError(f'A UPNP stun support must have an "{option}" option!') - + # Check the validity of the IP address listen_dev = [] system_addrs_cidr = get_all_interface_addr(True, [], [netifaces.AF_INET, netifaces.AF_INET6]) @@ -120,7 +121,7 @@ def verify(upnpd): raise ConfigError(f'The address "{listen_if_or_addr}" is an address that is not allowed to listen on. It is not an interface address nor a multicast address!') if is_ipv6(listen_if_or_addr) and IPv6Network(listen_if_or_addr).is_multicast: raise ConfigError(f'The address "{listen_if_or_addr}" is an address that is not allowed to listen on. It is not an interface address nor a multicast address!') - + system_listening_dev_addrs_cidr = get_all_interface_addr(True, listen_dev, [netifaces.AF_INET6]) system_listening_dev_addrs = get_all_interface_addr(False, listen_dev, [netifaces.AF_INET6]) for listen_if_or_addr in upnpd['listen']: @@ -130,19 +131,20 @@ def verify(upnpd): def generate(upnpd): if not upnpd: return None - + if os.path.isfile(config_file): os.unlink(config_file) - + render(config_file, 'firewall/upnpd.conf.tmpl', upnpd) def apply(upnpd): + systemd_service_name = 'miniupnpd.service' if not upnpd: # Stop the UPNP service - call('systemctl stop miniupnpd.service') + call(f'systemctl stop {systemd_service_name}') else: # Start the UPNP service - call('systemctl restart miniupnpd.service') + call(f'systemctl restart {systemd_service_name}') if __name__ == '__main__': try: -- cgit v1.2.3 From bf549b34e7daab7e843176bc1c8a8d03148f3840 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 1 Feb 2022 08:00:58 +0100 Subject: Revert "dhclient: T3392: remove /usr/sbin prefix from iproute2 ip command" This reverts commit 78b247b724f74bdabab0706aaa7f5b00e5809bc1. --- src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper | 16 ++++++++-------- src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper index 9d5505758..74a7e83bf 100644 --- a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper +++ b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper @@ -4,7 +4,7 @@ IF_METRIC=${IF_METRIC:-210} # Check if interface is inside a VRF -VRF_OPTION=$(ip -j -d link show ${interface} | awk '{if(match($0, /.*"master":"(\w+)".*"info_slave_kind":"vrf"/, IFACE_DETAILS)) printf("vrf %s", IFACE_DETAILS[1])}') +VRF_OPTION=$(/usr/sbin/ip -j -d link show ${interface} | awk '{if(match($0, /.*"master":"(\w+)".*"info_slave_kind":"vrf"/, IFACE_DETAILS)) printf("vrf %s", IFACE_DETAILS[1])}') # get status of FRR function frr_alive () { @@ -66,9 +66,9 @@ function iptovtysh () { # delete the same route from kernel before adding new one function delroute () { logmsg info "Checking if the route presented in kernel: $@ $VRF_OPTION" - if ip route show $@ $VRF_OPTION | grep -qx "$1 " ; then - logmsg info "Deleting IP route: \"ip route del $@ $VRF_OPTION\"" - ip route del $@ $VRF_OPTION + if /usr/sbin/ip route show $@ $VRF_OPTION | grep -qx "$1 " ; then + logmsg info "Deleting IP route: \"/usr/sbin/ip route del $@ $VRF_OPTION\"" + /usr/sbin/ip route del $@ $VRF_OPTION fi } @@ -76,8 +76,8 @@ function delroute () { function ip () { # pass comand to system `ip` if this is not related to routes change if [ "$2" != "route" ] ; then - logmsg info "Passing command to iproute2: \"$@\"" - ip $@ + logmsg info "Passing command to /usr/sbin/ip: \"$@\"" + /usr/sbin/ip $@ else # if we want to work with routes, try to use FRR first if frr_alive ; then @@ -87,8 +87,8 @@ function ip () { vtysh -c "conf t" -c "$VTYSH_CMD" else # add ip route to kernel - logmsg info "Modifying routes in kernel: \"ip $@\"" - ip $@ $VRF_OPTION + logmsg info "Modifying routes in kernel: \"/usr/sbin/ip $@\"" + /usr/sbin/ip $@ $VRF_OPTION fi fi } diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup b/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup index a6989441b..ad6a1d5eb 100644 --- a/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup +++ b/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup @@ -1,7 +1,7 @@ ## ## VyOS cleanup ## -# NOTE: here we use 'ip' wrapper, therefore a route will be actually deleted via ip or vtysh, according to the system state +# NOTE: here we use 'ip' wrapper, therefore a route will be actually deleted via /usr/sbin/ip or vtysh, according to the system state hostsd_client="/usr/bin/vyos-hostsd-client" hostsd_changes= # check vyos-hostsd status -- cgit v1.2.3 From 22f0794a9f195e69e277d48f031fe934febe9408 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Thu, 27 Jan 2022 16:58:36 +0100 Subject: firewall: T4209: Fix support for rule `recent` matches --- data/templates/firewall/nftables.tmpl | 22 ++++++++++++++++++++++ .../include/firewall/common-rule.xml.i | 19 +++++++++++++++---- python/vyos/firewall.py | 4 +--- src/conf_mode/firewall.py | 6 +++++- src/migration-scripts/firewall/6-to-7 | 20 ++++++++++++++++++++ 5 files changed, 63 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/data/templates/firewall/nftables.tmpl b/data/templates/firewall/nftables.tmpl index 468a5a32f..0cc977cf9 100644 --- a/data/templates/firewall/nftables.tmpl +++ b/data/templates/firewall/nftables.tmpl @@ -31,16 +31,27 @@ table ip filter { } {% endif %} {% if name is defined %} +{% set ns = namespace(sets=[]) %} {% for name_text, conf in name.items() %} chain NAME_{{ name_text }} { {% if conf.rule is defined %} {% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not defined %} {{ rule_conf | nft_rule(name_text, rule_id) }} +{% if rule_conf.recent is defined %} +{% set ns.sets = ns.sets + [name_text + '_' + rule_id] %} +{% endif %} {% endfor %} {% endif %} {{ conf | nft_default_rule(name_text) }} } {% endfor %} +{% for set_name in ns.sets %} + set RECENT_{{ set_name }} { + type ipv4_addr + size 65535 + flags dynamic + } +{% endfor %} {% endif %} {% if state_policy is defined %} chain VYOS_STATE_POLICY { @@ -81,16 +92,27 @@ table ip6 filter { } {% endif %} {% if ipv6_name is defined %} +{% set ns = namespace(sets=[]) %} {% for name_text, conf in ipv6_name.items() %} chain NAME6_{{ name_text }} { {% if conf.rule is defined %} {% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not defined %} {{ rule_conf | nft_rule(name_text, rule_id, 'ip6') }} +{% if rule_conf.recent is defined %} +{% set ns.sets = ns.sets + [name_text + '_' + rule_id] %} +{% endif %} {% endfor %} {% endif %} {{ conf | nft_default_rule(name_text) }} } {% endfor %} +{% for set_name in ns.sets %} + set RECENT6_{{ set_name }} { + type ipv6_addr + size 65535 + flags dynamic + } +{% endfor %} {% endif %} {% if state_policy is defined %} chain VYOS_STATE_POLICY6 { diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i index 521fe54f2..353804990 100644 --- a/interface-definitions/include/firewall/common-rule.xml.i +++ b/interface-definitions/include/firewall/common-rule.xml.i @@ -146,13 +146,24 @@ - Source addresses seen in the last N seconds + Source addresses seen in the last second/minute/hour + + second minute hour + - u32:0-4294967295 - Source addresses seen in the last N seconds + second + Source addresses seen COUNT times in the last second + + + minute + Source addresses seen COUNT times in the last minute + + + hour + Source addresses seen COUNT times in the last hour - + ^(second|minute|hour)$ diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index c1217b420..55ce318e7 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -181,9 +181,7 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): if 'recent' in rule_conf: count = rule_conf['recent']['count'] time = rule_conf['recent']['time'] - # output.append(f'meter {fw_name}_{rule_id} {{ ip saddr and 255.255.255.255 limit rate over {count}/{time} burst {count} packets }}') - # Waiting on input from nftables developers due to - # bug with above line and atomic chain flushing. + output.append(f'add @RECENT{def_suffix}_{fw_name}_{rule_id} {{ {ip_name} saddr limit rate over {count}/{time} burst {count} packets }}') if 'time' in rule_conf: output.append(parse_time(rule_conf['time'])) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 9dec2143e..41df1b84a 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -278,6 +278,7 @@ def cleanup_rule(table, jump_chain): def cleanup_commands(firewall): commands = [] + commands_end = [] for table in ['ip filter', 'ip6 filter']: state_chain = 'VYOS_STATE_POLICY' if table == 'ip filter' else 'VYOS_STATE_POLICY6' json_str = cmd(f'nft -j list table {table}') @@ -308,7 +309,10 @@ def cleanup_commands(firewall): chain = rule['chain'] handle = rule['handle'] commands.append(f'delete rule {table} {chain} handle {handle}') - return commands + elif 'set' in item: + set_name = item['set']['name'] + commands_end.append(f'delete set {table} {set_name}') + return commands + commands_end def generate(firewall): if not os.path.exists(nftables_conf): diff --git a/src/migration-scripts/firewall/6-to-7 b/src/migration-scripts/firewall/6-to-7 index efc901530..5f4cff90d 100755 --- a/src/migration-scripts/firewall/6-to-7 +++ b/src/migration-scripts/firewall/6-to-7 @@ -104,6 +104,7 @@ if config.exists(base + ['name']): continue for rule in config.list_nodes(base + ['name', name, 'rule']): + rule_recent = base + ['name', name, 'rule', rule, 'recent'] rule_time = base + ['name', name, 'rule', rule, 'time'] rule_tcp_flags = base + ['name', name, 'rule', rule, 'tcp', 'flags'] rule_icmp = base + ['name', name, 'rule', rule, 'icmp'] @@ -114,6 +115,15 @@ if config.exists(base + ['name']): if config.exists(rule_time + ['utc']): config.delete(rule_time + ['utc']) + if config.exists(rule_recent + ['time']): + tmp = int(config.return_value(rule_recent + ['time'])) + unit = 'minute' + if tmp > 600: + unit = 'hour' + elif tmp < 10: + unit = 'second' + config.set(rule_recent + ['time'], value=unit) + if config.exists(rule_tcp_flags): tmp = config.return_value(rule_tcp_flags) config.delete(rule_tcp_flags) @@ -148,6 +158,7 @@ if config.exists(base + ['ipv6-name']): continue for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']): + rule_recent = base + ['ipv6-name', name, 'rule', rule, 'recent'] rule_time = base + ['ipv6-name', name, 'rule', rule, 'time'] rule_tcp_flags = base + ['ipv6-name', name, 'rule', rule, 'tcp', 'flags'] rule_icmp = base + ['ipv6-name', name, 'rule', rule, 'icmpv6'] @@ -158,6 +169,15 @@ if config.exists(base + ['ipv6-name']): if config.exists(rule_time + ['utc']): config.delete(rule_time + ['utc']) + if config.exists(rule_recent + ['time']): + tmp = int(config.return_value(rule_recent + ['time'])) + unit = 'minute' + if tmp > 600: + unit = 'hour' + elif tmp < 10: + unit = 'second' + config.set(rule_recent + ['time'], value=unit) + if config.exists(rule_tcp_flags): tmp = config.return_value(rule_tcp_flags) config.delete(rule_tcp_flags) -- cgit v1.2.3 From 5444eeda0fab496da5bef7b233c443ba79b100ee Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Fri, 4 Feb 2022 08:45:18 +0000 Subject: policy: T4151: Delete unexpected print added in commit c501ae0f --- src/conf_mode/policy-local-route.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'src') diff --git a/src/conf_mode/policy-local-route.py b/src/conf_mode/policy-local-route.py index 0990039c1..3f834f55c 100755 --- a/src/conf_mode/policy-local-route.py +++ b/src/conf_mode/policy-local-route.py @@ -162,8 +162,6 @@ def apply(pbr): if not pbr: return None - print(pbr) - # Delete old rule if needed for rule_rm in ['rule_remove', 'rule6_remove']: if rule_rm in pbr: -- cgit v1.2.3 From 11a900e706db59459314622050ced7d4117f090b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 5 Feb 2022 20:13:04 +0100 Subject: vrrp: T4226: transition-script does not work for groups containing a hypen (-) --- src/system/keepalived-fifo.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/system/keepalived-fifo.py b/src/system/keepalived-fifo.py index b1fe7e43f..a8df232ae 100755 --- a/src/system/keepalived-fifo.py +++ b/src/system/keepalived-fifo.py @@ -71,7 +71,8 @@ class KeepalivedFifo: # Read VRRP configuration directly from CLI self.vrrp_config_dict = conf.get_config_dict(base, - key_mangling=('-', '_'), get_first_key=True) + key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) logger.debug(f'Loaded configuration: {self.vrrp_config_dict}') except Exception as err: -- cgit v1.2.3 From 4ddfe9b7e72e4f1e1fc8e70c5239bf09644e6d9b Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Tue, 8 Feb 2022 08:38:12 +0000 Subject: monitoring: T3872: Add input filter for firewall InfluxDB2 Input filter for firewall allows to get bytes/counters from nftables in format, required for InfluxDB2 --- data/templates/monitoring/telegraf.tmpl | 3 +- .../custom_scripts/show_firewall_input_filter.py | 73 ++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100755 src/etc/telegraf/custom_scripts/show_firewall_input_filter.py (limited to 'src') diff --git a/data/templates/monitoring/telegraf.tmpl b/data/templates/monitoring/telegraf.tmpl index 465b58c80..d3145a500 100644 --- a/data/templates/monitoring/telegraf.tmpl +++ b/data/templates/monitoring/telegraf.tmpl @@ -17,7 +17,7 @@ [[outputs.influxdb_v2]] urls = ["{{ url }}:{{ port }}"] insecure_skip_verify = true - token = "{{ authentication.token }}" + token = "$INFLUX_TOKEN" organization = "{{ authentication.organization }}" bucket = "{{ bucket }}" [[inputs.cpu]] @@ -52,6 +52,7 @@ syslog_standard = "RFC3164" [[inputs.exec]] commands = [ + "{{ custom_scripts_dir }}/show_firewall_input_filter.py", "{{ custom_scripts_dir }}/show_interfaces_input_filter.py", "{{ custom_scripts_dir }}/vyos_services_input_filter.py" ] diff --git a/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py b/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py new file mode 100755 index 000000000..bf4bfd05d --- /dev/null +++ b/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 + +import json +import re +import time + +from vyos.util import cmd + + +def get_nft_filter_chains(): + """ + Get list of nft chains for table filter + """ + nft = cmd('/usr/sbin/nft --json list table ip filter') + nft = json.loads(nft) + chain_list = [] + + for output in nft['nftables']: + if 'chain' in output: + chain = output['chain']['name'] + chain_list.append(chain) + + return chain_list + + +def get_nftables_details(name): + """ + Get dict, counters packets and bytes for chain + """ + command = f'/usr/sbin/nft list chain ip filter {name}' + try: + results = cmd(command) + except: + return {} + + # Trick to remove 'NAME_' from chain name in the comment + # It was added to any chain T4218 + # counter packets 0 bytes 0 return comment "FOO default-action accept" + comment_name = name.replace("NAME_", "") + out = {} + for line in results.split('\n'): + comment_search = re.search(rf'{comment_name}[\- ](\d+|default-action)', line) + if not comment_search: + continue + + rule = {} + rule_id = comment_search[1] + counter_search = re.search(r'counter packets (\d+) bytes (\d+)', line) + if counter_search: + rule['packets'] = counter_search[1] + rule['bytes'] = counter_search[2] + + rule['conditions'] = re.sub(r'(\b(counter packets \d+ bytes \d+|drop|reject|return|log)\b|comment "[\w\-]+")', '', line).strip() + out[rule_id] = rule + return out + + +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},' + f'ruleid={rule} ' + f'pkts={rule_config["packets"]}i,' + f'bytes={rule_config["bytes"]}i ' + f'{str(int(time.time()))}000000000') + + +chains = get_nft_filter_chains() + +for chain in chains: + get_nft_telegraf(chain) -- cgit v1.2.3 From 4ecfd5d87c33aea770878a012f3b4956deafd762 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Tue, 8 Feb 2022 10:58:20 +0000 Subject: openvpn: T4230: Delete checks if local-host address assigned OpenVPN can't start if it depends on VRRP virtual-address as virtual-address is not yet assigned by HA (openvpn and ha in one commit) as we have checks "if address assigned" It depends on commit priorities: 460 interfaces/openvpn 800 high-availability Replace check if local-host address assigned from raise ConfigError to print (just notification) Allow to bind OpenVPN service to nonlocal address --- src/conf_mode/interfaces-openvpn.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 3b8fae710..0f6114b4a 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -47,6 +47,7 @@ from vyos.template import is_ipv4 from vyos.template import is_ipv6 from vyos.util import call 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 makedir @@ -423,8 +424,8 @@ def verify(openvpn): # verify specified IP address is present on any interface on this system if 'local_host' in openvpn: if not is_addr_assigned(openvpn['local_host']): - raise ConfigError('local-host IP address "{local_host}" not assigned' \ - ' to any interface'.format(**openvpn)) + print('local-host IP address "{local_host}" not assigned' \ + ' to any interface'.format(**openvpn)) # TCP active if openvpn['protocol'] == 'tcp-active': @@ -647,6 +648,13 @@ def apply(openvpn): return None + # verify specified IP address is present on any interface on this system + # Allow to bind service to nonlocal address, if it virtaual-vrrp address + # or if address will be assign later + if 'local_host' in openvpn: + if not is_addr_assigned(openvpn['local_host']): + cmd('sysctl -w net.ipv4.ip_nonlocal_bind=1') + # No matching OpenVPN process running - maybe it got killed or none # existed - nevertheless, spawn new OpenVPN process call(f'systemctl reload-or-restart openvpn@{interface}.service') -- cgit v1.2.3 From 230ac0a202acd7ae9ad9bccb9e777ee5a0e0b7b7 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Wed, 9 Feb 2022 16:07:55 +0000 Subject: openvpn: T3686: Fix for check local-address in script and tmpl Local-address should be checked/executed only if it exists in the openvpn configuration, dictionary, jinja2 template --- data/templates/openvpn/server.conf.tmpl | 10 ++++++---- src/conf_mode/interfaces-openvpn.py | 13 +++++++------ 2 files changed, 13 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/data/templates/openvpn/server.conf.tmpl b/data/templates/openvpn/server.conf.tmpl index 7a0470d0e..fb7ad9e16 100644 --- a/data/templates/openvpn/server.conf.tmpl +++ b/data/templates/openvpn/server.conf.tmpl @@ -141,11 +141,13 @@ ping {{ keep_alive.interval }} ping-restart {{ keep_alive.failure_count }} {% if device_type == 'tap' %} -{% for laddr, laddr_conf in local_address.items() if laddr | is_ipv4 %} -{% if laddr_conf is defined and laddr_conf.subnet_mask is defined and laddr_conf.subnet_mask is not none %} +{% if local_address is defined and local_address is not none %} +{% for laddr, laddr_conf in local_address.items() if laddr | is_ipv4 %} +{% if laddr_conf is defined and laddr_conf.subnet_mask is defined and laddr_conf.subnet_mask is not none %} ifconfig {{ laddr }} {{ laddr_conf.subnet_mask }} -{% endif %} -{% endfor %} +{% endif %} +{% endfor %} +{% endif %} {% else %} {% for laddr in local_address if laddr | is_ipv4 %} {% for raddr in remote_address if raddr | is_ipv4 %} diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 3b8fae710..242fae9fb 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2021 VyOS maintainers and contributors +# Copyright (C) 2019-2022 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -225,11 +225,12 @@ def verify(openvpn): if 'local_address' not in openvpn and 'is_bridge_member' not in openvpn: raise ConfigError('Must specify "local-address" or add interface to bridge') - if len([addr for addr in openvpn['local_address'] if is_ipv4(addr)]) > 1: - raise ConfigError('Only one IPv4 local-address can be specified') + if 'local_address' in openvpn: + if len([addr for addr in openvpn['local_address'] if is_ipv4(addr)]) > 1: + raise ConfigError('Only one IPv4 local-address can be specified') - if len([addr for addr in openvpn['local_address'] if is_ipv6(addr)]) > 1: - raise ConfigError('Only one IPv6 local-address can be specified') + if len([addr for addr in openvpn['local_address'] if is_ipv6(addr)]) > 1: + raise ConfigError('Only one IPv6 local-address can be specified') if openvpn['device_type'] == 'tun': if 'remote_address' not in openvpn: @@ -268,7 +269,7 @@ def verify(openvpn): if dict_search('remote_host', openvpn) in dict_search('remote_address', openvpn): raise ConfigError('"remote-address" and "remote-host" can not be the same') - if openvpn['device_type'] == 'tap': + if openvpn['device_type'] == 'tap' and 'local_address' in openvpn: # we can only have one local_address, this is ensured above v4addr = None for laddr in openvpn['local_address']: -- cgit v1.2.3 From 7f7be911b749b6c65ac3c6e57192e9e4ce2dcd24 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Thu, 10 Feb 2022 22:05:19 +0000 Subject: openvpn: T4236: Add generator for ovpn configurations in op-mode This generator generates client .ovpn files with required initial configuration It gets information from interface vtun, pki ca and certificates --- .../generate-openvpn-config-client.xml.in | 58 +++++++++ src/op_mode/generate_ovpn_client_file.py | 145 +++++++++++++++++++++ 2 files changed, 203 insertions(+) create mode 100644 op-mode-definitions/generate-openvpn-config-client.xml.in create mode 100755 src/op_mode/generate_ovpn_client_file.py (limited to 'src') diff --git a/op-mode-definitions/generate-openvpn-config-client.xml.in b/op-mode-definitions/generate-openvpn-config-client.xml.in new file mode 100644 index 000000000..4f9f31bfe --- /dev/null +++ b/op-mode-definitions/generate-openvpn-config-client.xml.in @@ -0,0 +1,58 @@ + + + + + + + Generate OpenVPN client configuration ovpn file + + + + + Generate Client config + + + + + Local interface used for connection + + + + + + + + CA certificate + + pki ca + + + + + + Cerificate used by client + + pki certificate + + + + + + Certificate key used by client + + sudo ${vyos_op_scripts_dir}/generate_ovpn_client_file.py --interface "$5" --ca "$7" --cert "$9" --key "${11}" + + + sudo ${vyos_op_scripts_dir}/generate_ovpn_client_file.py --interface "$5" --ca "$7" --cert "$9" + + + + + + + + + + + + diff --git a/src/op_mode/generate_ovpn_client_file.py b/src/op_mode/generate_ovpn_client_file.py new file mode 100755 index 000000000..29db41e37 --- /dev/null +++ b/src/op_mode/generate_ovpn_client_file.py @@ -0,0 +1,145 @@ +#!/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 . + +import argparse +import os + +from jinja2 import Template + +from vyos.configquery import ConfigTreeQuery +from vyos.ifconfig import Section +from vyos.util import cmd + + +client_config = """ + +client +nobind +remote {{ remote_host }} {{ port }} +remote-cert-tls server +proto {{ 'tcp-client' if protocol == 'tcp-active' else 'udp' }} +dev {{ device }} +dev-type {{ device }} +persist-key +persist-tun +verb 3 + +# Encryption options +{% if encryption is defined and encryption is not none %} +{% if encryption.cipher is defined and encryption.cipher is not none %} +cipher {{ encryption.cipher }} +{% if encryption.cipher == 'bf128' %} +keysize 128 +{% elif encryption.cipher == 'bf256' %} +keysize 256 +{% endif %} +{% endif %} +{% if encryption.ncp_ciphers is defined and encryption.ncp_ciphers is not none %} +data-ciphers {{ encryption.ncp_ciphers }} +{% endif %} +{% endif %} + +{% if hash is defined and hash is not none %} +auth {{ hash }} +{% endif %} +keysize 256 +comp-lzo {{ '' if use_lzo_compression is defined else 'no' }} + + +-----BEGIN CERTIFICATE----- +{{ ca }} +-----END CERTIFICATE----- + + + + +-----BEGIN CERTIFICATE----- +{{ cert }} +-----END CERTIFICATE----- + + + + +-----BEGIN PRIVATE KEY----- +{{ key }} +-----END PRIVATE KEY----- + + + +""" + +config = ConfigTreeQuery() +base = ['interfaces', 'openvpn'] + +if not config.exists(base): + print('OpenVPN not configured') + exit(0) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument("-i", "--interface", type=str, help='OpenVPN interface the client is connecting to', required=True) + parser.add_argument("-a", "--ca", type=str, help='OpenVPN CA cerificate', required=True) + parser.add_argument("-c", "--cert", type=str, help='OpenVPN client cerificate', required=True) + parser.add_argument("-k", "--key", type=str, help='OpenVPN client cerificate key', action="store") + args = parser.parse_args() + + interface = args.interface + ca = args.ca + cert = args.cert + key = args.key + if not key: + key = args.cert + + if interface not in Section.interfaces('openvpn'): + exit(f'OpenVPN interface "{interface}" does not exist!') + + if not config.exists(['pki', 'ca', ca, 'certificate']): + exit(f'OpenVPN CA certificate "{ca}" does not exist!') + + if not config.exists(['pki', 'certificate', cert, 'certificate']): + exit(f'OpenVPN certificate "{cert}" does not exist!') + + if not config.exists(['pki', 'certificate', cert, 'private', 'key']): + exit(f'OpenVPN certificate key "{key}" does not exist!') + + ca = config.value(['pki', 'ca', ca, 'certificate']) + cert = config.value(['pki', 'certificate', cert, 'certificate']) + key = config.value(['pki', 'certificate', key, 'private', 'key']) + remote_host = config.value(base + [interface, 'local-host']) + + ovpn_conf = config.get_config_dict(base + [interface], key_mangling=('-', '_'), get_first_key=True) + + port = '1194' if 'local_port' not in ovpn_conf else ovpn_conf['local_port'] + proto = 'udp' if 'protocol' not in ovpn_conf else ovpn_conf['protocol'] + device = 'tun' if 'device_type' not in ovpn_conf else ovpn_conf['device_type'] + + config = { + 'interface' : interface, + 'ca' : ca, + 'cert' : cert, + 'key' : key, + 'device' : device, + 'port' : port, + 'proto' : proto, + 'remote_host' : remote_host, + 'address' : [], + } + +# Clear out terminal first +print('\x1b[2J\x1b[H') +client = Template(client_config, trim_blocks=True).render(config) +print(client) -- cgit v1.2.3 From 27daf4a6cd4928be41ed08330ccc1b7f04ad2638 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 12 Feb 2022 08:44:43 +0100 Subject: policy: T2199: bugfix verify_rule() on negated groups Related to #1215 --- src/conf_mode/policy-route.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py index 82f668acf..3d1d7d8c5 100755 --- a/src/conf_mode/policy-route.py +++ b/src/conf_mode/policy-route.py @@ -123,6 +123,10 @@ def verify_rule(policy, name, rule_conf, ipv6): for group in valid_groups: if group in side_conf['group']: group_name = side_conf['group'][group] + + if group_name.startswith('!'): + group_name = group_name[1:] + fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group error_group = fw_group.replace("_", "-") group_obj = dict_search_args(policy['firewall_group'], fw_group, group_name) -- cgit v1.2.3 From 2cec431e5caf9df85640f707cd6dc3077c17c238 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 13 Feb 2022 20:29:25 +0100 Subject: vrf: T4191: bugfix for "ip rule" when VRFs are created We always mangled and worked on the "ip rule" singleton even when nothing needed to be changed. This resulted in a VRF hickup when the same VRF was added and removed multiple times. set interfaces ethernet eth1 vrf foo set vrf name foo table '1000' commit delete interfaces ethernet eth1 vrf delete vrf commit set interfaces ethernet eth1 vrf foo set vrf name foo table '1000' commit broke reachability on eth1 - a reboot was required. This change will now only alter the ip rule tables once when VRF instances are created for the first time and will not touch the Kernel "ip rule" representation afterwards. --- src/conf_mode/vrf.py | 108 ++++++++++++++++++++++++++------------------------- 1 file changed, 56 insertions(+), 52 deletions(-) (limited to 'src') diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index 38c0c4463..cfe0f4d8e 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -29,6 +29,7 @@ from vyos.util import dict_search from vyos.util import get_interface_config from vyos.util import popen from vyos.util import run +from vyos.util import sysctl from vyos import ConfigError from vyos import frr from vyos import airbag @@ -37,10 +38,16 @@ airbag.enable() config_file = '/etc/iproute2/rt_tables.d/vyos-vrf.conf' nft_vrf_config = '/tmp/nftables-vrf-zones' -def list_rules(): - command = 'ip -j -4 rule show' - answer = loads(cmd(command)) - return [_ for _ in answer if _] +def has_rule(af : str, priority : int, table : str): + """ Check if a given ip rule exists """ + if af not in ['-4', '-6']: + raise ValueError() + command = f'ip -j {af} rule show' + for tmp in loads(cmd(command)): + if {'priority', 'table'} <= set(tmp): + if tmp['priority'] == priority and tmp['table'] == table: + return True + return False def vrf_interfaces(c, match): matched = [] @@ -69,7 +76,6 @@ def vrf_routing(c, match): c.set_level(old_level) return matched - def get_config(config=None): if config: conf = config @@ -148,13 +154,11 @@ def apply(vrf): bind_all = '0' if 'bind-to-all' in vrf: bind_all = '1' - call(f'sysctl -wq net.ipv4.tcp_l3mdev_accept={bind_all}') - call(f'sysctl -wq net.ipv4.udp_l3mdev_accept={bind_all}') + sysctl('net.ipv4.tcp_l3mdev_accept', bind_all) + sysctl('net.ipv4.udp_l3mdev_accept', bind_all) for tmp in (dict_search('vrf_remove', vrf) or []): if os.path.isdir(f'/sys/class/net/{tmp}'): - call(f'ip -4 route del vrf {tmp} unreachable default metric 4278198272') - call(f'ip -6 route del vrf {tmp} unreachable default metric 4278198272') call(f'ip link delete dev {tmp}') # Remove nftables conntrack zone map item nft_del_element = f'delete element inet vrf_zones ct_iface_map {{ "{tmp}" }}' @@ -165,31 +169,59 @@ def apply(vrf): # check if table already exists _, err = popen('nft list table inet vrf_zones') # If not, create a table - if err: - if os.path.exists(nft_vrf_config): - cmd(f'nft -f {nft_vrf_config}') - os.unlink(nft_vrf_config) + if err and os.path.exists(nft_vrf_config): + cmd(f'nft -f {nft_vrf_config}') + os.unlink(nft_vrf_config) + + # Linux routing uses rules to find tables - routing targets are then + # looked up in those tables. If the lookup got a matching route, the + # process ends. + # + # TL;DR; first table with a matching entry wins! + # + # You can see your routing table lookup rules using "ip rule", sadly the + # local lookup is hit before any VRF lookup. Pinging an addresses from the + # VRF will usually find a hit in the local table, and never reach the VRF + # routing table - this is usually not what you want. Thus we will + # re-arrange the tables and move the local lookup further down once VRFs + # are enabled. + # + # Thanks to https://stbuehler.de/blog/article/2020/02/29/using_vrf__virtual_routing_and_forwarding__on_linux.html + + for afi in ['-4', '-6']: + # move lookup local to pref 32765 (from 0) + if not has_rule(afi, 32765, 'local'): + call(f'ip {afi} rule add pref 32765 table local') + if has_rule(afi, 0, 'local'): + call(f'ip {afi} rule del pref 0') + # make sure that in VRFs after failed lookup in the VRF specific table + # nothing else is reached + if not has_rule(afi, 1000, 'l3mdev'): + # this should be added by the kernel when a VRF is created + # add it here for completeness + call(f'ip {afi} rule add pref 1000 l3mdev protocol kernel') + + # add another rule with an unreachable target which only triggers in VRF context + # if a route could not be reached + if not has_rule(afi, 2000, 'l3mdev'): + call(f'ip {afi} rule add pref 2000 l3mdev unreachable') for name, config in vrf['name'].items(): table = config['table'] - if not os.path.isdir(f'/sys/class/net/{name}'): # For each VRF apart from your default context create a VRF # interface with a separate routing table call(f'ip link add {name} type vrf table {table}') - # The kernel Documentation/networking/vrf.txt also recommends - # adding unreachable routes to the VRF routing tables so that routes - # afterwards are taken. - call(f'ip -4 route add vrf {name} unreachable default metric 4278198272') - call(f'ip -6 route add vrf {name} unreachable default metric 4278198272') - # We also should add proper loopback IP addresses to the newly - # created VRFs for services bound to the loopback address (SNMP, NTP) - call(f'ip -4 addr add 127.0.0.1/8 dev {name}') - call(f'ip -6 addr add ::1/128 dev {name}') # set VRF description for e.g. SNMP monitoring vrf_if = Interface(name) + # We also should add proper loopback IP addresses to the newly + # created VRFs for services bound to the loopback address (SNMP, NTP) + vrf_if.add_addr('127.0.0.1/8') + vrf_if.add_addr('::1/128') + # add VRF description if available vrf_if.set_alias(config.get('description', '')) + # Enable/Disable of an interface must always be done at the end of the # derived class to make use of the ref-counting set_admin_state() # function. We will only enable the interface if 'up' was called as @@ -203,37 +235,9 @@ def apply(vrf): nft_add_element = f'add element inet vrf_zones ct_iface_map {{ "{name}" : {table} }}' cmd(f'nft {nft_add_element}') - # Linux routing uses rules to find tables - routing targets are then - # looked up in those tables. If the lookup got a matching route, the - # process ends. - # - # TL;DR; first table with a matching entry wins! - # - # You can see your routing table lookup rules using "ip rule", sadly the - # local lookup is hit before any VRF lookup. Pinging an addresses from the - # VRF will usually find a hit in the local table, and never reach the VRF - # routing table - this is usually not what you want. Thus we will - # re-arrange the tables and move the local lookup furhter down once VRFs - # are enabled. - - # get current preference on local table - local_pref = [r.get('priority') for r in list_rules() if r.get('table') == 'local'][0] - - # change preference when VRFs are enabled and local lookup table is default - if not local_pref and 'name' in vrf: - for af in ['-4', '-6']: - call(f'ip {af} rule add pref 32765 table local') - call(f'ip {af} rule del pref 0') # return to default lookup preference when no VRF is configured if 'name' not in vrf: - for af in ['-4', '-6']: - call(f'ip {af} rule add pref 0 table local') - call(f'ip {af} rule del pref 32765') - - # clean out l3mdev-table rule if present - if 1000 in [r.get('priority') for r in list_rules() if r.get('priority') == 1000]: - call(f'ip {af} rule del pref 1000') # Remove VRF zones table from nftables tmp = run('nft list table inet vrf_zones') if tmp == 0: -- cgit v1.2.3 From 6f1326d6b68f6dcb83843374c876407ef2922bd1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 14 Feb 2022 19:07:29 +0100 Subject: tunnel: T4154: verify() no more then one GRE tunnel is used w/o "ip key" per interface It is impossible for the OS kernel to distinguish multiple GRE tunnels when no "gre key" is configured when sourcing tunnels from the same interface. --- src/conf_mode/interfaces-tunnel.py | 39 +++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 30f57ec0c..c9267e749 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -103,19 +103,22 @@ def verify(tunnel): raise ConfigError('Tunnel parameters ip key must be set!') if tunnel['encapsulation'] in ['gre', 'gretap']: - if dict_search('parameters.ip.key', tunnel) != None: - # Check pairs tunnel source-address/encapsulation/key with exists tunnels. - # Prevent the same key for 2 tunnels with same source-address/encap. T2920 - for tunnel_if in Section.interfaces('tunnel'): - # It makes no sense to run the test for re-used GRE keys on our - # own interface we are currently working on - if tunnel['ifname'] == tunnel_if: - continue - tunnel_cfg = get_interface_config(tunnel_if) - # no match on encapsulation - bail out - if dict_search('linkinfo.info_kind', tunnel_cfg) != tunnel['encapsulation']: - continue - new_source_address = dict_search('source_address', tunnel) + # Check pairs tunnel source-address/encapsulation/key with exists tunnels. + # Prevent the same key for 2 tunnels with same source-address/encap. T2920 + for tunnel_if in Section.interfaces('tunnel'): + # It makes no sense to run the test against our own interface we + # are currently configuring + if tunnel['ifname'] == tunnel_if: + continue + + tunnel_cfg = get_interface_config(tunnel_if) + # no match on encapsulation - bail out + if dict_search('linkinfo.info_kind', tunnel_cfg) != tunnel['encapsulation']: + continue + + new_source_address = dict_search('source_address', tunnel) + new_source_interface = dict_search('source_interface', tunnel) + if dict_search('parameters.ip.key', tunnel) != None: # Convert tunnel key to ip key, format "ip -j link show" # 1 => 0.0.0.1, 999 => 0.0.3.231 orig_new_key = dict_search('parameters.ip.key', tunnel) @@ -125,6 +128,16 @@ def verify(tunnel): dict_search('linkinfo.info_data.ikey', tunnel_cfg) == new_key: raise ConfigError(f'Key "{orig_new_key}" for source-address "{new_source_address}" ' \ f'is already used for tunnel "{tunnel_if}"!') + else: + # If no IP GRE key is used we can not have more then one GRE tunnel + # bound to any one interface/IP address. This will result in a OS + # PermissionError: add tunnel "gre0" failed: File exists + if (dict_search('address', tunnel_cfg) == new_source_address or + (dict_search('address', tunnel_cfg) == '0.0.0.0' and + dict_search('link', tunnel_cfg) == new_source_interface)): + raise ConfigError(f'Missing required "ip key" parameter when \ + running more then one GRE based tunnel on the \ + same source-interface/source-address') # Keys are not allowed with ipip and sit tunnels if tunnel['encapsulation'] in ['ipip', 'sit']: -- cgit v1.2.3 From 122c7a53575f67759f157e02eca776f799658dc1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 14 Feb 2022 19:09:09 +0100 Subject: tunnel: T4154: import cleanup --- src/conf_mode/interfaces-tunnel.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'src') diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index c9267e749..4c1204b4e 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2021 VyOS maintainers and contributors +# Copyright (C) 2018-2022 yOS 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,9 +21,7 @@ from netifaces import interfaces from ipaddress import IPv4Address 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.configverify import verify_address from vyos.configverify import verify_bridge_delete @@ -34,8 +32,6 @@ from vyos.configverify import verify_tunnel from vyos.ifconfig import Interface from vyos.ifconfig import Section from vyos.ifconfig import TunnelIf -from vyos.template import is_ipv4 -from vyos.template import is_ipv6 from vyos.util import get_interface_config from vyos.util import dict_search from vyos import ConfigError -- cgit v1.2.3 From e00edb0072ceb07b92be826984154afeb6c567d3 Mon Sep 17 00:00:00 2001 From: Andrew Gunnerson Date: Mon, 14 Feb 2022 17:02:13 -0500 Subject: pki: eapol: T4244: Fix KeyError when CA cert name differs from client cert name This commit fixes a small typo where the client cert name was being used to index the CA configuration dict. Signed-off-by: Andrew Gunnerson --- python/vyos/configverify.py | 2 +- src/conf_mode/interfaces-ethernet.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 365a28feb..18fb7f9f7 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -173,7 +173,7 @@ def verify_eapol(config): if ca_cert_name not in config['pki']['ca']: raise ConfigError('Invalid CA certificate specified for EAPoL') - ca_cert = config['pki']['ca'][cert_name] + ca_cert = config['pki']['ca'][ca_cert_name] if 'certificate' not in ca_cert: raise ConfigError('Invalid CA certificate specified for EAPoL') diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index e7250fb49..ab8d58f81 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -165,7 +165,7 @@ def generate(ethernet): if 'ca_certificate' in ethernet['eapol']: ca_cert_file_path = os.path.join(cfg_dir, f'{ifname}_ca.pem') ca_cert_name = ethernet['eapol']['ca_certificate'] - pki_ca_cert = ethernet['pki']['ca'][cert_name] + pki_ca_cert = ethernet['pki']['ca'][ca_cert_name] write_file(ca_cert_file_path, wrap_certificate(pki_ca_cert['certificate'])) -- cgit v1.2.3 From 283688fe52bd762d1fadff635de58a87df3da629 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Fri, 11 Feb 2022 13:17:05 +0000 Subject: conntrack-sync: T4237: Fix checks for listen-address list to str Verify section conntrack_sync.py funciton 'is_addr_assigned' should checks address as string not as list (cherry picked from commit c41c51e4ed7ceb293161014a73bdd350162c3300) --- src/conf_mode/conntrack_sync.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/conf_mode/conntrack_sync.py b/src/conf_mode/conntrack_sync.py index 8f9837c2b..34d1f7398 100755 --- a/src/conf_mode/conntrack_sync.py +++ b/src/conf_mode/conntrack_sync.py @@ -93,9 +93,9 @@ def verify(conntrack): raise ConfigError('Can not configure expect-sync "all" with other protocols!') if 'listen_address' in conntrack: - address = conntrack['listen_address'] - if not is_addr_assigned(address): - raise ConfigError(f'Specified listen-address {address} not assigned to any interface!') + for address in conntrack['listen_address']: + if not is_addr_assigned(address): + raise ConfigError(f'Specified listen-address {address} not assigned to any interface!') vrrp_group = dict_search('failover_mechanism.vrrp.sync_group', conntrack) if vrrp_group == None: -- cgit v1.2.3 From 425f8f16caa7e9e5101be3dc8ab32e60db274d7f Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 16 Feb 2022 20:11:31 -0600 Subject: xml: T3474: get component version dictionary from xml cache, not legacy --- python/vyos/migrator.py | 2 +- src/helpers/system-versions-foot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/python/vyos/migrator.py b/python/vyos/migrator.py index 4574bb6d1..a2e0daabd 100644 --- a/python/vyos/migrator.py +++ b/python/vyos/migrator.py @@ -195,7 +195,7 @@ class Migrator(object): # This will force calling all migration scripts: cfg_versions = {} - sys_versions = systemversions.get_system_versions() + sys_versions = systemversions.get_system_component_version() # save system component versions in json file for easy reference self.save_json_record(sys_versions) diff --git a/src/helpers/system-versions-foot.py b/src/helpers/system-versions-foot.py index c33e41d79..2aa687221 100755 --- a/src/helpers/system-versions-foot.py +++ b/src/helpers/system-versions-foot.py @@ -21,7 +21,7 @@ import vyos.systemversions as systemversions import vyos.defaults import vyos.version -sys_versions = systemversions.get_system_versions() +sys_versions = systemversions.get_system_component_version() component_string = formatversions.format_versions_string(sys_versions) -- cgit v1.2.3 From 1cbcbf40b7721849f9696c05fac65db010a66b7c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 17 Feb 2022 20:58:02 +0100 Subject: openvpn: T4230: globally enable ip_nonlocal_bind --- src/conf_mode/interfaces-openvpn.py | 7 ------- src/etc/sysctl.d/33-vyos-nonlocal-bind.conf | 8 ++++++++ 2 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 src/etc/sysctl.d/33-vyos-nonlocal-bind.conf (limited to 'src') diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 329399274..29a25eedc 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -649,13 +649,6 @@ def apply(openvpn): return None - # verify specified IP address is present on any interface on this system - # Allow to bind service to nonlocal address, if it virtaual-vrrp address - # or if address will be assign later - if 'local_host' in openvpn: - if not is_addr_assigned(openvpn['local_host']): - cmd('sysctl -w net.ipv4.ip_nonlocal_bind=1') - # No matching OpenVPN process running - maybe it got killed or none # existed - nevertheless, spawn new OpenVPN process call(f'systemctl reload-or-restart openvpn@{interface}.service') diff --git a/src/etc/sysctl.d/33-vyos-nonlocal-bind.conf b/src/etc/sysctl.d/33-vyos-nonlocal-bind.conf new file mode 100644 index 000000000..aa81b5336 --- /dev/null +++ b/src/etc/sysctl.d/33-vyos-nonlocal-bind.conf @@ -0,0 +1,8 @@ +### Added by vyos-1x ### +# +# ip_nonlocal_bind - BOOLEAN +# If set, allows processes to bind() to non-local IP addresses, +# which can be quite useful - but may break some applications. +# Default: 0 +net.ipv4.ip_nonlocal_bind = 1 +net.ipv6.ip_nonlocal_bind = 1 -- cgit v1.2.3 From 3d1b34bf715e594aa4a013d409bfcc5a4c4ad99c Mon Sep 17 00:00:00 2001 From: Andrew Gunnerson Date: Wed, 16 Feb 2022 17:46:06 -0500 Subject: pki: eapol: T4245: Add full CA and client cert chains to wpa_supplicant PEM files This commit updates the eapol code so that it writes the full certificate chains for both the specified CA and the client certificate to `_ca.pem` and `_cert.pem`, respectively. The full CA chain is necessary for validating the incoming server certificate when it is signed by an intermediate CA and the intermediate CA cert is not included in the EAP-TLS ServerHello. In this scenario, wpa_supplicant needs to have both the intermediate CA and the root CA in its `ca_file`. Similarly, the full client certificate chain is needed when the ISP expects/requires that the client (wpa_supplicant) sends the client cert + the intermediate CA (or even + the root CA) as part of the EAP-TLS ClientHello. Signed-off-by: Andrew Gunnerson --- python/vyos/pki.py | 26 ++++++ smoketest/scripts/cli/test_interfaces_ethernet.py | 108 +++++++++++++++++----- src/conf_mode/interfaces-ethernet.py | 18 +++- 3 files changed, 125 insertions(+), 27 deletions(-) (limited to 'src') diff --git a/python/vyos/pki.py b/python/vyos/pki.py index 68ad73bf2..0b916eaae 100644 --- a/python/vyos/pki.py +++ b/python/vyos/pki.py @@ -331,3 +331,29 @@ def verify_certificate(cert, ca_cert): return True except InvalidSignature: return False + +# Certificate chain + +def find_parent(cert, ca_certs): + for ca_cert in ca_certs: + if verify_certificate(cert, ca_cert): + return ca_cert + return None + +def find_chain(cert, ca_certs): + remaining = ca_certs.copy() + chain = [cert] + + while remaining: + parent = find_parent(chain[-1], remaining) + if parent is None: + # No parent in the list of remaining certificates or there's a circular dependency + break + elif parent == chain[-1]: + # Self-signed: must be root CA (end of chain) + break + else: + remaining.remove(parent) + chain.append(parent) + + return chain diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py index 6d80e4c96..ae3d5ed96 100755 --- a/smoketest/scripts/cli/test_interfaces_ethernet.py +++ b/smoketest/scripts/cli/test_interfaces_ethernet.py @@ -21,29 +21,73 @@ import unittest from base_interfaces_test import BasicInterfaceTest from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section +from vyos.pki import CERT_BEGIN from vyos.util import cmd from vyos.util import process_named_running from vyos.util import read_file -cert_data = """ -MIICFDCCAbugAwIBAgIUfMbIsB/ozMXijYgUYG80T1ry+mcwCgYIKoZIzj0EAwIw -WTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv -bWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MB4XDTIx -MDcyMDEyNDUxMloXDTI2MDcxOTEyNDUxMlowWTELMAkGA1UEBhMCR0IxEzARBgNV -BAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlP -UzESMBAGA1UEAwwJVnlPUyBUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE -01HrLcNttqq4/PtoMua8rMWEkOdBu7vP94xzDO7A8C92ls1v86eePy4QllKCzIw3 -QxBIoCuH2peGRfWgPRdFsKNhMF8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E -BAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0GA1UdDgQWBBSu -+JnU5ZC4mkuEpqg2+Mk4K79oeDAKBggqhkjOPQQDAgNHADBEAiBEFdzQ/Bc3Lftz -ngrY605UhA6UprHhAogKgROv7iR4QgIgEFUxTtW3xXJcnUPWhhUFhyZoqfn8dE93 -+dm/LDnp7C0= +server_ca_root_cert_data = """ +MIIBcTCCARagAwIBAgIUDcAf1oIQV+6WRaW7NPcSnECQ/lUwCgYIKoZIzj0EAwIw +HjEcMBoGA1UEAwwTVnlPUyBzZXJ2ZXIgcm9vdCBDQTAeFw0yMjAyMTcxOTQxMjBa +Fw0zMjAyMTUxOTQxMjBaMB4xHDAaBgNVBAMME1Z5T1Mgc2VydmVyIHJvb3QgQ0Ew +WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQ0y24GzKQf4aM2Ir12tI9yITOIzAUj +ZXyJeCmYI6uAnyAMqc4Q4NKyfq3nBi4XP87cs1jlC1P2BZ8MsjL5MdGWozIwMDAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRwC/YaieMEnjhYa7K3Flw/o0SFuzAK +BggqhkjOPQQDAgNJADBGAiEAh3qEj8vScsjAdBy5shXzXDVVOKWCPTdGrPKnu8UW +a2cCIQDlDgkzWmn5ujc5ATKz1fj+Se/aeqwh4QyoWCVTFLIxhQ== """ -key_data = """ -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPLpD0Ohhoq0g4nhx -2KMIuze7ucKUt/lBEB2wc03IxXyhRANCAATTUestw222qrj8+2gy5rysxYSQ50G7 -u8/3jHMM7sDwL3aWzW/zp54/LhCWUoLMjDdDEEigK4fal4ZF9aA9F0Ww +server_ca_intermediate_cert_data = """ +MIIBmTCCAT+gAwIBAgIUNzrtHzLmi3QpPK57tUgCnJZhXXQwCgYIKoZIzj0EAwIw +HjEcMBoGA1UEAwwTVnlPUyBzZXJ2ZXIgcm9vdCBDQTAeFw0yMjAyMTcxOTQxMjFa +Fw0zMjAyMTUxOTQxMjFaMCYxJDAiBgNVBAMMG1Z5T1Mgc2VydmVyIGludGVybWVk +aWF0ZSBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEl2nJ1CzoqPV6hWII2m +eGN/uieU6wDMECTk/LgG8CCCSYb488dibUiFN/1UFsmoLIdIhkx/6MUCYh62m8U2 +WNujUzBRMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMV3YwH88I5gFsFUibbQ +kMR0ECPsMB8GA1UdIwQYMBaAFHAL9hqJ4wSeOFhrsrcWXD+jRIW7MAoGCCqGSM49 +BAMCA0gAMEUCIQC/ahujD9dp5pMMCd3SZddqGC9cXtOwMN0JR3e5CxP13AIgIMQm +jMYrinFoInxmX64HfshYqnUY8608nK9D2BNPOHo= +""" + +client_ca_root_cert_data = """ +MIIBcDCCARagAwIBAgIUZmoW2xVdwkZSvglnkCq0AHKa6zIwCgYIKoZIzj0EAwIw +HjEcMBoGA1UEAwwTVnlPUyBjbGllbnQgcm9vdCBDQTAeFw0yMjAyMTcxOTQxMjFa +Fw0zMjAyMTUxOTQxMjFaMB4xHDAaBgNVBAMME1Z5T1MgY2xpZW50IHJvb3QgQ0Ew +WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATUpKXzQk2NOVKDN4VULk2yw4mOKPvn +mg947+VY7lbpfOfAUD0QRg95qZWCw899eKnXp/U4TkAVrmEKhUb6OJTFozIwMDAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTXu6xGWUl25X3sBtrhm3BJSICIATAK +BggqhkjOPQQDAgNIADBFAiEAnTzEwuTI9bz2Oae3LZbjP6f/f50KFJtjLZFDbQz7 +DpYCIDNRHV8zBUibC+zg5PqMpQBKd/oPfNU76nEv6xkp/ijO +""" + +client_ca_intermediate_cert_data = """ +MIIBmDCCAT+gAwIBAgIUJEMdotgqA7wU4XXJvEzDulUAGqgwCgYIKoZIzj0EAwIw +HjEcMBoGA1UEAwwTVnlPUyBjbGllbnQgcm9vdCBDQTAeFw0yMjAyMTcxOTQxMjJa +Fw0zMjAyMTUxOTQxMjJaMCYxJDAiBgNVBAMMG1Z5T1MgY2xpZW50IGludGVybWVk +aWF0ZSBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGyIVIi217s9j3O+WQ2b +6R65/Z0ZjQpELxPjBRc0CA0GFCo+pI5EvwI+jNFArvTAJ5+ZdEWUJ1DQhBKDDQdI +avCjUzBRMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOUS8oNJjChB1Rb9Blcl +ETvziHJ9MB8GA1UdIwQYMBaAFNe7rEZZSXblfewG2uGbcElIgIgBMAoGCCqGSM49 +BAMCA0cAMEQCIArhaxWgRsAUbEeNHD/ULtstLHxw/P97qPUSROLQld53AiBjgiiz +9pDfISmpekZYz6bIDWRIR0cXUToZEMFNzNMrQg== +""" + +client_cert_data = """ +MIIBmTCCAUCgAwIBAgIUV5T77XdE/tV82Tk4Vzhp5BIFFm0wCgYIKoZIzj0EAwIw +JjEkMCIGA1UEAwwbVnlPUyBjbGllbnQgaW50ZXJtZWRpYXRlIENBMB4XDTIyMDIx +NzE5NDEyMloXDTMyMDIxNTE5NDEyMlowIjEgMB4GA1UEAwwXVnlPUyBjbGllbnQg +Y2VydGlmaWNhdGUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARuyynqfc/qJj5e +KJ03oOH8X4Z8spDeAPO9WYckMM0ldPj+9kU607szFzPwjaPWzPdgyIWz3hcN8yAh +CIhytmJao1AwTjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTIFKrxZ+PqOhYSUqnl +TGCUmM7wTjAfBgNVHSMEGDAWgBTlEvKDSYwoQdUW/QZXJRE784hyfTAKBggqhkjO +PQQDAgNHADBEAiAvO8/jvz05xqmP3OXD53XhfxDLMIxzN4KPoCkFqvjlhQIgIHq2 +/geVx3rAOtSps56q/jiDouN/aw01TdpmGKVAa9U= +""" + +client_key_data = """ +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgxaxAQsJwjoOCByQE ++qSYKtKtJzbdbOnTsKNSrfgkFH6hRANCAARuyynqfc/qJj5eKJ03oOH8X4Z8spDe +APO9WYckMM0ldPj+9kU607szFzPwjaPWzPdgyIWz3hcN8yAhCIhytmJa """ def get_wpa_supplicant_value(interface, key): @@ -51,6 +95,10 @@ def get_wpa_supplicant_value(interface, key): tmp = re.findall(r'\n?{}=(.*)'.format(key), tmp) return tmp[0] +def get_certificate_count(interface, cert_type): + tmp = read_file(f'/run/wpa_supplicant/{interface}_{cert_type}.pem') + return tmp.count(CERT_BEGIN) + class EthernetInterfaceTest(BasicInterfaceTest.TestCase): @classmethod def setUpClass(cls): @@ -165,16 +213,23 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase): self.cli_commit() def test_eapol_support(self): - ca_name = 'eapol' - cert_name = 'eapol' + ca_certs = { + 'eapol-server-ca-root': server_ca_root_cert_data, + 'eapol-server-ca-intermediate': server_ca_intermediate_cert_data, + 'eapol-client-ca-root': client_ca_root_cert_data, + 'eapol-client-ca-intermediate': client_ca_intermediate_cert_data, + } + cert_name = 'eapol-client' - self.cli_set(['pki', 'ca', ca_name, 'certificate', cert_data.replace('\n','')]) - self.cli_set(['pki', 'certificate', cert_name, 'certificate', cert_data.replace('\n','')]) - self.cli_set(['pki', 'certificate', cert_name, 'private', 'key', key_data.replace('\n','')]) + for name, data in ca_certs.items(): + self.cli_set(['pki', 'ca', name, 'certificate', data.replace('\n','')]) + + self.cli_set(['pki', 'certificate', cert_name, 'certificate', client_cert_data.replace('\n','')]) + self.cli_set(['pki', 'certificate', cert_name, 'private', 'key', client_key_data.replace('\n','')]) for interface in self._interfaces: # Enable EAPoL - self.cli_set(self._base_path + [interface, 'eapol', 'ca-certificate', ca_name]) + self.cli_set(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol-server-ca-intermediate']) self.cli_set(self._base_path + [interface, 'eapol', 'certificate', cert_name]) self.cli_commit() @@ -206,7 +261,12 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase): tmp = get_wpa_supplicant_value(interface, 'identity') self.assertEqual(f'"{mac}"', tmp) - self.cli_delete(['pki', 'ca', ca_name]) + # Check certificate files have the full chain + self.assertEqual(get_certificate_count(interface, 'ca'), 2) + self.assertEqual(get_certificate_count(interface, 'cert'), 3) + + for name in ca_certs: + self.cli_delete(['pki', 'ca', name]) self.cli_delete(['pki', 'certificate', cert_name]) if __name__ == '__main__': diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index ab8d58f81..2a8a126f2 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -32,7 +32,9 @@ from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf from vyos.ethtool import Ethtool from vyos.ifconfig import EthernetIf -from vyos.pki import wrap_certificate +from vyos.pki import find_chain +from vyos.pki import encode_certificate +from vyos.pki import load_certificate from vyos.pki import wrap_private_key from vyos.template import render from vyos.util import call @@ -159,7 +161,14 @@ def generate(ethernet): cert_name = ethernet['eapol']['certificate'] pki_cert = ethernet['pki']['certificate'][cert_name] - write_file(cert_file_path, wrap_certificate(pki_cert['certificate'])) + loaded_pki_cert = load_certificate(pki_cert['certificate']) + loaded_ca_certs = {load_certificate(c['certificate']) + for c in ethernet['pki']['ca'].values()} + + cert_full_chain = find_chain(loaded_pki_cert, loaded_ca_certs) + + write_file(cert_file_path, + '\n'.join(encode_certificate(c) for c in cert_full_chain)) write_file(cert_key_path, wrap_private_key(pki_cert['private']['key'])) if 'ca_certificate' in ethernet['eapol']: @@ -167,8 +176,11 @@ def generate(ethernet): ca_cert_name = ethernet['eapol']['ca_certificate'] pki_ca_cert = ethernet['pki']['ca'][ca_cert_name] + loaded_ca_cert = load_certificate(pki_ca_cert['certificate']) + ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs) + write_file(ca_cert_file_path, - wrap_certificate(pki_ca_cert['certificate'])) + '\n'.join(encode_certificate(c) for c in ca_full_chain)) else: # delete configuration on interface removal if os.path.isfile(wpa_suppl_conf.format(**ethernet)): -- cgit v1.2.3 From cf36ced75094a519682875e0e73571824f34b6ec Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Sat, 19 Feb 2022 18:06:27 +0000 Subject: containers: T4249: Allow to connect host device to the container Ability to attach host devices to the container It can be disk, USB device or any device from the directory /dev set container name alp01 device disk source '/dev/vdb1' set container name alp01 device disk destination '/dev/mydisk' --- interface-definitions/containers.xml.in | 25 +++++++++++++++++++++++++ src/conf_mode/containers.py | 22 +++++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/interface-definitions/containers.xml.in b/interface-definitions/containers.xml.in index 30c7110b8..07686b16e 100644 --- a/interface-definitions/containers.xml.in +++ b/interface-definitions/containers.xml.in @@ -58,6 +58,31 @@ #include + + + Add a host device to the container + + + + + Source device (Example: "/dev/x") + + txt + Source device + + + + + + Destination container device (Example: "/dev/x") + + txt + Destination container device + + + + + #include diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py index 26c50cab6..516671844 100755 --- a/src/conf_mode/containers.py +++ b/src/conf_mode/containers.py @@ -122,6 +122,18 @@ def verify(container): raise ConfigError(f'IP address "{address}" can not be used for a container, '\ 'reserved for the container engine!') + if 'device' in container_config: + for dev, dev_config in container_config['device'].items(): + if 'source' not in dev_config: + raise ConfigError(f'Device "{dev}" has no source path configured!') + + if 'destination' not in dev_config: + raise ConfigError(f'Device "{dev}" has no destination path configured!') + + source = dev_config['source'] + if not os.path.exists(source): + raise ConfigError(f'Device "{dev}" source path "{source}" does not exist!') + if 'environment' in container_config: for var, cfg in container_config['environment'].items(): if 'value' not in cfg: @@ -266,6 +278,14 @@ def apply(container): c = c.replace('-', '_') cap_add += f' --cap-add={c}' + # Add a host device to the container /dev/x:/dev/x + device = '' + if 'device' in container_config: + for dev, dev_config in container_config['device'].items(): + source_dev = dev_config['source'] + dest_dev = dev_config['destination'] + device += f' --device={source_dev}:{dest_dev}' + # Check/set environment options "-e foo=bar" env_opt = '' if 'environment' in container_config: @@ -296,7 +316,7 @@ def apply(container): container_base_cmd = f'podman run --detach --interactive --tty --replace {cap_add} ' \ f'--memory {memory}m --memory-swap 0 --restart {restart} ' \ - f'--name {name} {port} {volume} {env_opt}' + f'--name {name} {device} {port} {volume} {env_opt}' if 'allow_host_networks' in container_config: run(f'{container_base_cmd} --net host {image}') else: -- cgit v1.2.3 From f23040a0f7d425550350f91410272196f842308e Mon Sep 17 00:00:00 2001 From: srividya0208 Date: Sun, 20 Feb 2022 15:06:21 -0500 Subject: T4115:Reboot:Options "in" and "at" are not working When reboot is executed with "in" option it only accepts minutes till 99 value and does not accept greater values and "at" is also working same like in option where as it should work with exact timings. --- op-mode-definitions/reboot.xml.in | 4 ++-- src/op_mode/powerctrl.py | 25 +++++++++++++++++++++---- 2 files changed, 23 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/op-mode-definitions/reboot.xml.in b/op-mode-definitions/reboot.xml.in index 2c8daec5d..6414742d9 100644 --- a/op-mode-definitions/reboot.xml.in +++ b/op-mode-definitions/reboot.xml.in @@ -25,7 +25,7 @@ <Minutes> - sudo ${vyos_op_scripts_dir}/powerctrl.py --yes --reboot $3 $4 + sudo ${vyos_op_scripts_dir}/powerctrl.py --yes --reboot_in $3 $4 @@ -40,7 +40,7 @@ Reboot at a specific date - <DDMMYYYY> <DD/MM/YYYY> <DD.MM.YYYY> <DD:MM:YYYY> + <DD/MM/YYYY> <DD.MM.YYYY> <DD:MM:YYYY> sudo ${vyos_op_scripts_dir}/powerctrl.py --yes --reboot $3 $5 diff --git a/src/op_mode/powerctrl.py b/src/op_mode/powerctrl.py index 679b03c0b..fd4f86d88 100755 --- a/src/op_mode/powerctrl.py +++ b/src/op_mode/powerctrl.py @@ -33,10 +33,12 @@ def utc2local(datetime): def parse_time(s): try: - if re.match(r'^\d{1,2}$', s): - if (int(s) > 59): + if re.match(r'^\d{1,9999}$', s): + if (int(s) > 59) and (int(s) < 1440): s = str(int(s)//60) + ":" + str(int(s)%60) return datetime.strptime(s, "%H:%M").time() + if (int(s) >= 1440): + return s.split() else: return datetime.strptime(s, "%M").time() else: @@ -141,7 +143,7 @@ def execute_shutdown(time, reboot=True, ask=True): cmd(f'/usr/bin/wall "{wall_msg}"') else: if not ts: - exit(f'Invalid time "{time[0]}". The valid format is HH:MM') + exit(f'Invalid time "{time[0]}". Uses 24 Hour Clock format') else: exit(f'Invalid date "{time[1]}". A valid format is YYYY-MM-DD [HH:MM]') else: @@ -172,7 +174,12 @@ def main(): action.add_argument("--reboot", "-r", help="Reboot the system", nargs="*", - metavar="Minutes|HH:MM") + metavar="HH:MM") + + action.add_argument("--reboot_in", "-i", + help="Reboot the system", + nargs="*", + metavar="Minutes") action.add_argument("--poweroff", "-p", help="Poweroff the system", @@ -190,7 +197,17 @@ def main(): try: if args.reboot is not None: + for r in args.reboot: + if ':' not in r and '/' not in r and '.' not in r: + print("Incorrect format! Use HH:MM") + exit(1) execute_shutdown(args.reboot, reboot=True, ask=args.yes) + if args.reboot_in is not None: + for i in args.reboot_in: + if ':' in i: + print("Incorrect format! Use Minutes") + exit(1) + execute_shutdown(args.reboot_in, reboot=True, ask=args.yes) if args.poweroff is not None: execute_shutdown(args.poweroff, reboot=False, ask=args.yes) if args.cancel: -- cgit v1.2.3 From 0ecddff7cffa8900d351d5c15e32420f9d780c0b Mon Sep 17 00:00:00 2001 From: Andreas Date: Wed, 29 Dec 2021 18:02:06 +0100 Subject: vxlan: T4120: add ability to set multiple remotes (PR #1127) VXLAN does support using multiple remotes but VyOS does not. Add the ability to set multiple remotes and add their flood lists using "bridge" command. --- .../include/interface/tunnel-remote.xml.i | 2 +- .../include/interface/tunnel-remotes.xml.i | 19 ++++++++++++ interface-definitions/interfaces-vxlan.xml.in | 2 +- python/vyos/ifconfig/vxlan.py | 7 +++++ smoketest/scripts/cli/test_interfaces_vxlan.py | 2 ++ src/conf_mode/interfaces-vxlan.py | 34 ++++++++++++++++++++++ 6 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 interface-definitions/include/interface/tunnel-remotes.xml.i (limited to 'src') diff --git a/interface-definitions/include/interface/tunnel-remote.xml.i b/interface-definitions/include/interface/tunnel-remote.xml.i index 1ba9b0382..2a8891b85 100644 --- a/interface-definitions/include/interface/tunnel-remote.xml.i +++ b/interface-definitions/include/interface/tunnel-remote.xml.i @@ -1,4 +1,4 @@ - + Tunnel remote address diff --git a/interface-definitions/include/interface/tunnel-remotes.xml.i b/interface-definitions/include/interface/tunnel-remotes.xml.i new file mode 100644 index 000000000..ae8481898 --- /dev/null +++ b/interface-definitions/include/interface/tunnel-remotes.xml.i @@ -0,0 +1,19 @@ + + + + Tunnel remote address + + ipv4 + Tunnel remote IPv4 address + + + ipv6 + Tunnel remote IPv6 address + + + + + + + + diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index 4c3c3ac71..559067ea5 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -98,7 +98,7 @@ #include #include - #include + #include #include #include diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py index 0c5282db4..87b5e40b8 100644 --- a/python/vyos/ifconfig/vxlan.py +++ b/python/vyos/ifconfig/vxlan.py @@ -82,3 +82,10 @@ class VXLANIf(Interface): self._cmd(cmd.format(**self.config)) # interface is always A/D down. It needs to be enabled explicitly self.set_admin_state('down') + + other_remotes = self.config.get('other_remotes') + if other_remotes: + for rem in other_remotes: + self.config['rem'] = rem + cmd2 = 'bridge fdb append to 00:00:00:00:00:00 dst {rem} port {port} dev {ifname}' + self._cmd(cmd2.format(**self.config)) diff --git a/smoketest/scripts/cli/test_interfaces_vxlan.py b/smoketest/scripts/cli/test_interfaces_vxlan.py index 9278adadd..12fc463ba 100755 --- a/smoketest/scripts/cli/test_interfaces_vxlan.py +++ b/smoketest/scripts/cli/test_interfaces_vxlan.py @@ -33,6 +33,8 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase): 'vxlan10': ['vni 10', 'remote 127.0.0.2'], 'vxlan20': ['vni 20', 'group 239.1.1.1', 'source-interface eth0'], 'vxlan30': ['vni 30', 'remote 2001:db8:2000::1', 'source-address 2001:db8:1000::1', 'parameters ipv6 flowlabel 0x1000'], + 'vxlan40': ['vni 40', 'remote 127.0.0.2', 'remote 127.0.0.3'], + 'vxlan50': ['vni 50', 'remote 2001:db8:2000::1', 'remote 2001:db8:2000::2', 'parameters ipv6 flowlabel 0x1000'], } cls._interfaces = list(cls._options) # call base-classes classmethod diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 1f097c4e3..092f249df 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -58,6 +58,13 @@ def get_config(config=None): if len(vxlan['other_tunnels']) == 0: del vxlan['other_tunnels'] + # leave first remote in dict and put the other ones (if they exists) to "other_remotes" + remotes = vxlan.get('remote') + if remotes: + vxlan['remote'] = remotes[0] + if len(remotes) > 1: + del remotes[0] + vxlan['other_remotes'] = remotes return vxlan def verify(vxlan): @@ -108,6 +115,33 @@ def verify(vxlan): raise ConfigError(f'Underlaying device MTU is to small ({lower_mtu} '\ f'bytes) for VXLAN overhead ({vxlan_overhead} bytes!)') + # Check for mixed IPv4 and IPv6 addresses + protocol = None + if 'source_address' in vxlan: + if is_ipv6(vxlan['source_address']): + protocol = 'ipv6' + else: + protocol = 'ipv4' + if 'remote' in vxlan: + if is_ipv6(vxlan['remote']): + if protocol == 'ipv4': + raise ConfigError('IPv4 and IPV6 cannot be mixed') + protocol = 'ipv6' + else: + if protocol == 'ipv6': + raise ConfigError('IPv4 and IPV6 cannot be mixed') + protocol = 'ipv4' + if 'other_remotes' in vxlan: + for rem in vxlan['other_remotes']: + if is_ipv6(rem): + if protocol == 'ipv4': + raise ConfigError('IPv4 and IPV6 cannot be mixed') + protocol = 'ipv6' + else: + if protocol == 'ipv6': + raise ConfigError('IPv4 and IPV6 cannot be mixed') + protocol = 'ipv4' + verify_mtu_ipv6(vxlan) verify_address(vxlan) return None -- cgit v1.2.3 From 25b2f2a8057260ad0d2c59823618d7c9f0fba707 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 20 Feb 2022 22:10:37 +0100 Subject: bridge: remove unreferenced import -> leaf_node_changed --- src/conf_mode/interfaces-bridge.py | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index 4d3ebc587..f4dba9d4a 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -22,7 +22,6 @@ from netifaces import interfaces from vyos.config import Config 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_member from vyos.configdict import is_source_interface from vyos.configdict import has_vlan_subinterface_configured -- cgit v1.2.3 From a3b7e985911eeaccac4fa229563b78c5a64e7e90 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Mon, 21 Feb 2022 08:06:40 -0500 Subject: T2719: initial batch of standardized structure op mode scripts --- src/op_mode/cpu_summary.py | 36 +++++++++++++++++--------- src/op_mode/show_cpu.py | 63 ++++++++++++++++++++++++++------------------- src/op_mode/show_ram.py | 19 +++++++++----- src/op_mode/show_uptime.py | 27 ++++++++++++++----- src/op_mode/show_version.py | 22 +++++++++++----- 5 files changed, 109 insertions(+), 58 deletions(-) (limited to 'src') diff --git a/src/op_mode/cpu_summary.py b/src/op_mode/cpu_summary.py index cfd321522..3bdf5a718 100755 --- a/src/op_mode/cpu_summary.py +++ b/src/op_mode/cpu_summary.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2022 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -19,18 +19,30 @@ from vyos.util import colon_separated_to_dict FILE_NAME = '/proc/cpuinfo' -with open(FILE_NAME, 'r') as f: - data_raw = f.read() +def get_raw_data(): + with open(FILE_NAME, 'r') as f: + data_raw = f.read() -data = colon_separated_to_dict(data_raw) + data = colon_separated_to_dict(data_raw) -# Accumulate all data in a dict for future support for machine-readable output -cpu_data = {} -cpu_data['cpu_number'] = len(data['processor']) -cpu_data['models'] = list(set(data['model name'])) + # Accumulate all data in a dict for future support for machine-readable output + cpu_data = {} + cpu_data['cpu_number'] = len(data['processor']) + cpu_data['models'] = list(set(data['model name'])) -# Strip extra whitespace from CPU model names, /proc/cpuinfo is prone to that -cpu_data['models'] = map(lambda s: re.sub(r'\s+', ' ', s), cpu_data['models']) + # Strip extra whitespace from CPU model names, /proc/cpuinfo is prone to that + cpu_data['models'] = list(map(lambda s: re.sub(r'\s+', ' ', s), cpu_data['models'])) + + return cpu_data + +def get_formatted_output(): + cpu_data = get_raw_data() + + out = "CPU(s): {0}\n".format(cpu_data['cpu_number']) + out += "CPU model(s): {0}".format(",".join(cpu_data['models'])) + + return out + +if __name__ == '__main__': + print(get_formatted_output()) -print("CPU(s): {0}".format(cpu_data['cpu_number'])) -print("CPU model(s): {0}".format(",".join(cpu_data['models']))) diff --git a/src/op_mode/show_cpu.py b/src/op_mode/show_cpu.py index 0040e950d..9973d9789 100755 --- a/src/op_mode/show_cpu.py +++ b/src/op_mode/show_cpu.py @@ -21,7 +21,7 @@ from sys import exit from vyos.util import popen, DEVNULL OUT_TMPL_SRC = """ -{% if cpu %} +{%- if cpu -%} {% if 'vendor' in cpu %}CPU Vendor: {{cpu.vendor}}{% endif %} {% if 'model' in cpu %}Model: {{cpu.model}}{% endif %} {% if 'cpus' in cpu %}Total CPUs: {{cpu.cpus}}{% endif %} @@ -31,31 +31,42 @@ OUT_TMPL_SRC = """ {% if 'mhz' in cpu %}Current MHz: {{cpu.mhz}}{% endif %} {% if 'mhz_min' in cpu %}Minimum MHz: {{cpu.mhz_min}}{% endif %} {% if 'mhz_max' in cpu %}Maximum MHz: {{cpu.mhz_max}}{% endif %} -{% endif %} +{%- endif -%} """ -cpu = {} -cpu_json, code = popen('lscpu -J', stderr=DEVNULL) - -if code == 0: - cpu_info = json.loads(cpu_json) - if len(cpu_info) > 0 and 'lscpu' in cpu_info: - for prop in cpu_info['lscpu']: - if (prop['field'].find('Thread(s)') > -1): cpu['threads'] = prop['data'] - if (prop['field'].find('Core(s)')) > -1: cpu['cores'] = prop['data'] - if (prop['field'].find('Socket(s)')) > -1: cpu['sockets'] = prop['data'] - if (prop['field'].find('CPU(s):')) > -1: cpu['cpus'] = prop['data'] - if (prop['field'].find('CPU MHz')) > -1: cpu['mhz'] = prop['data'] - if (prop['field'].find('CPU min MHz')) > -1: cpu['mhz_min'] = prop['data'] - if (prop['field'].find('CPU max MHz')) > -1: cpu['mhz_max'] = prop['data'] - if (prop['field'].find('Vendor ID')) > -1: cpu['vendor'] = prop['data'] - if (prop['field'].find('Model name')) > -1: cpu['model'] = prop['data'] - -if len(cpu) > 0: - tmp = { 'cpu':cpu } +def get_raw_data(): + cpu = {} + cpu_json, code = popen('lscpu -J', stderr=DEVNULL) + + if code == 0: + cpu_info = json.loads(cpu_json) + if len(cpu_info) > 0 and 'lscpu' in cpu_info: + for prop in cpu_info['lscpu']: + if (prop['field'].find('Thread(s)') > -1): cpu['threads'] = prop['data'] + if (prop['field'].find('Core(s)')) > -1: cpu['cores'] = prop['data'] + if (prop['field'].find('Socket(s)')) > -1: cpu['sockets'] = prop['data'] + if (prop['field'].find('CPU(s):')) > -1: cpu['cpus'] = prop['data'] + if (prop['field'].find('CPU MHz')) > -1: cpu['mhz'] = prop['data'] + if (prop['field'].find('CPU min MHz')) > -1: cpu['mhz_min'] = prop['data'] + if (prop['field'].find('CPU max MHz')) > -1: cpu['mhz_max'] = prop['data'] + if (prop['field'].find('Vendor ID')) > -1: cpu['vendor'] = prop['data'] + if (prop['field'].find('Model name')) > -1: cpu['model'] = prop['data'] + + return cpu + +def get_formatted_output(): + cpu = get_raw_data() + + tmp = {'cpu':cpu} tmpl = Template(OUT_TMPL_SRC) - print(tmpl.render(tmp)) - exit(0) -else: - print('CPU information could not be determined\n') - exit(1) + return tmpl.render(tmp) + +if __name__ == '__main__': + cpu = get_raw_data() + + if len(cpu) > 0: + print(get_formatted_output()) + else: + print('CPU information could not be determined\n') + exit(1) + diff --git a/src/op_mode/show_ram.py b/src/op_mode/show_ram.py index 5818ec132..2b0be3965 100755 --- a/src/op_mode/show_ram.py +++ b/src/op_mode/show_ram.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 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 @@ -55,10 +55,17 @@ def get_system_memory_human(): return mem -if __name__ == '__main__': - mem = get_system_memory_human() +def get_raw_data(): + return get_system_memory_human() + +def get_formatted_output(): + mem = get_raw_data() - print("Total: {}".format(mem["total"])) - print("Free: {}".format(mem["free"])) - print("Used: {}".format(mem["used"])) + out = "Total: {}\n".format(mem["total"]) + out += "Free: {}\n".format(mem["free"]) + out += "Used: {}".format(mem["used"]) + return out + +if __name__ == '__main__': + print(get_formatted_output()) diff --git a/src/op_mode/show_uptime.py b/src/op_mode/show_uptime.py index c3dea52e6..1b5e33fa9 100755 --- a/src/op_mode/show_uptime.py +++ b/src/op_mode/show_uptime.py @@ -37,14 +37,27 @@ def get_load_averages(): return res -if __name__ == '__main__': +def get_raw_data(): from vyos.util import seconds_to_human - print("Uptime: {}\n".format(seconds_to_human(get_uptime_seconds()))) + res = {} + res["uptime_seconds"] = get_uptime_seconds() + res["uptime"] = seconds_to_human(get_uptime_seconds()) + res["load_average"] = get_load_averages() + + return res - avgs = get_load_averages() +def get_formatted_output(): + data = get_raw_data() - print("Load averages:") - print("1 minute: {:.02f}%".format(avgs[1]*100)) - print("5 minutes: {:.02f}%".format(avgs[5]*100)) - print("15 minutes: {:.02f}%".format(avgs[15]*100)) + out = "Uptime: {}\n\n".format(data["uptime"]) + avgs = data["load_average"] + out += "Load averages:\n" + out += "1 minute: {:.02f}%\n".format(avgs[1]*100) + out += "5 minutes: {:.02f}%\n".format(avgs[5]*100) + out += "15 minutes: {:.02f}%\n".format(avgs[15]*100) + + return out + +if __name__ == '__main__': + print(get_formatted_output()) diff --git a/src/op_mode/show_version.py b/src/op_mode/show_version.py index 7962e1e7b..b82ab6eca 100755 --- a/src/op_mode/show_version.py +++ b/src/op_mode/show_version.py @@ -26,10 +26,6 @@ from jinja2 import Template from sys import exit from vyos.util import call -parser = argparse.ArgumentParser() -parser.add_argument("-f", "--funny", action="store_true", help="Add something funny to the output") -parser.add_argument("-j", "--json", action="store_true", help="Produce JSON output") - version_output_tmpl = """ Version: VyOS {{version}} Release train: {{release_train}} @@ -51,7 +47,20 @@ Hardware UUID: {{hardware_uuid}} Copyright: VyOS maintainers and contributors """ +def get_raw_data(): + version_data = vyos.version.get_full_version_data() + return version_data + +def get_formatted_output(): + version_data = get_raw_data() + tmpl = Template(version_output_tmpl) + return tmpl.render(version_data) + if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument("-f", "--funny", action="store_true", help="Add something funny to the output") + parser.add_argument("-j", "--json", action="store_true", help="Produce JSON output") + args = parser.parse_args() version_data = vyos.version.get_full_version_data() @@ -60,9 +69,8 @@ if __name__ == '__main__': import json print(json.dumps(version_data)) exit(0) - - tmpl = Template(version_output_tmpl) - print(tmpl.render(version_data)) + else: + print(get_formatted_output()) if args.funny: print(vyos.limericks.get_random()) -- cgit v1.2.3 From 3a605ad020d8d20b08a72cb1284f6e590d1fd7b5 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 21 Feb 2022 18:23:55 +0100 Subject: vxlan: T4120: code cleanup for multiple remotes --- python/vyos/ifconfig/vxlan.py | 24 +++++++++++++++++------- src/conf_mode/interfaces-vxlan.py | 38 ++++++++++---------------------------- 2 files changed, 27 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py index 87b5e40b8..516a19f24 100644 --- a/python/vyos/ifconfig/vxlan.py +++ b/python/vyos/ifconfig/vxlan.py @@ -1,4 +1,4 @@ -# Copyright 2019-2021 VyOS maintainers and contributors +# Copyright 2019-2022 VyOS maintainers and contributors # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -68,6 +68,16 @@ class VXLANIf(Interface): 'vni' : 'id', } + # IPv6 flowlabels can only be used on IPv6 tunnels, thus we need to + # ensure that at least the first remote IP address is passed to the + # tunnel creation command. Subsequent tunnel remote addresses can later + # be added to the FDB + remote_list = None + if 'remote' in self.config: + # skip first element as this is already configured as remote + remote_list = self.config['remote'][1:] + self.config['remote'] = self.config['remote'][0] + cmd = 'ip link add {ifname} type {type} dstport {port}' for vyos_key, iproute2_key in mapping.items(): # dict_search will return an empty dict "{}" for valueless nodes like @@ -83,9 +93,9 @@ class VXLANIf(Interface): # interface is always A/D down. It needs to be enabled explicitly self.set_admin_state('down') - other_remotes = self.config.get('other_remotes') - if other_remotes: - for rem in other_remotes: - self.config['rem'] = rem - cmd2 = 'bridge fdb append to 00:00:00:00:00:00 dst {rem} port {port} dev {ifname}' - self._cmd(cmd2.format(**self.config)) + # VXLAN tunnel is always recreated on any change - see interfaces-vxlan.py + if remote_list: + for remote in remote_list: + cmd = f'bridge fdb append to 00:00:00:00:00:00 dst {remote} ' \ + 'port {port} dev {ifname}' + self._cmd(cmd.format(**self.config)) diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 092f249df..85604508e 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2019-2022 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -34,8 +34,8 @@ airbag.enable() def get_config(config=None): """ - Retrive CLI config as dictionary. Dictionary can never be empty, as at least the - interface name will be added or a deleted flag + Retrive CLI config as dictionary. Dictionary can never be empty, as at least + the interface name will be added or a deleted flag """ if config: conf = config @@ -58,13 +58,6 @@ def get_config(config=None): if len(vxlan['other_tunnels']) == 0: del vxlan['other_tunnels'] - # leave first remote in dict and put the other ones (if they exists) to "other_remotes" - remotes = vxlan.get('remote') - if remotes: - vxlan['remote'] = remotes[0] - if len(remotes) > 1: - del remotes[0] - vxlan['other_remotes'] = remotes return vxlan def verify(vxlan): @@ -77,8 +70,7 @@ def verify(vxlan): if 'group' in vxlan: if 'source_interface' not in vxlan: - raise ConfigError('Multicast VXLAN requires an underlaying interface ') - + raise ConfigError('Multicast VXLAN requires an underlaying interface') verify_source_interface(vxlan) if not any(tmp in ['group', 'remote', 'source_address'] for tmp in vxlan): @@ -122,35 +114,26 @@ def verify(vxlan): protocol = 'ipv6' else: protocol = 'ipv4' + if 'remote' in vxlan: - if is_ipv6(vxlan['remote']): - if protocol == 'ipv4': - raise ConfigError('IPv4 and IPV6 cannot be mixed') - protocol = 'ipv6' - else: - if protocol == 'ipv6': - raise ConfigError('IPv4 and IPV6 cannot be mixed') - protocol = 'ipv4' - if 'other_remotes' in vxlan: - for rem in vxlan['other_remotes']: - if is_ipv6(rem): + error_msg = 'Can not mix both IPv4 and IPv6 for VXLAN underlay' + for remote in vxlan['remote']: + if is_ipv6(remote): if protocol == 'ipv4': - raise ConfigError('IPv4 and IPV6 cannot be mixed') + raise ConfigError(error_msg) protocol = 'ipv6' else: if protocol == 'ipv6': - raise ConfigError('IPv4 and IPV6 cannot be mixed') + raise ConfigError(error_msg) protocol = 'ipv4' verify_mtu_ipv6(vxlan) verify_address(vxlan) return None - def generate(vxlan): return None - def apply(vxlan): # Check if the VXLAN interface already exists if vxlan['ifname'] in interfaces(): @@ -166,7 +149,6 @@ def apply(vxlan): return None - if __name__ == '__main__': try: c = get_config() -- cgit v1.2.3 From 2373b232849c847717cbdcfac7390d8376e227ca Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 22 Feb 2022 10:31:32 +0100 Subject: vxlan: T4264: interface is destroyed and rebuild on description change When changing "general" parameters like: - interface IP address - MTU - description the interface is destroyed and recreated ... this should not happen! --- src/conf_mode/interfaces-vxlan.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 85604508e..29b16af89 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -21,6 +21,7 @@ from netifaces import interfaces from vyos.config import Config from vyos.configdict import get_interface_dict +from vyos.configdict import leaf_node_changed from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mtu_ipv6 @@ -44,6 +45,16 @@ def get_config(config=None): base = ['interfaces', 'vxlan'] vxlan = get_interface_dict(conf, base) + # VXLAN interfaces are picky and require recreation if certain parameters + # change. But a VXLAN interface should - of course - not be re-created if + # it's description or IP address is adjusted. Feels somehow logic doesn't it? + for cli_option in ['external', 'gpe', 'group', 'port', 'remote', + 'source-address', 'source-interface', 'vni', + 'parameters ip dont-fragment', 'parameters ip tos', + 'parameters ip ttl']: + if leaf_node_changed(conf, cli_option.split()): + vxlan.update({'rebuild_required': {}}) + # We need to verify that no other VXLAN tunnel is configured when external # mode is in use - Linux Kernel limitation conf.set_level(base) @@ -136,11 +147,12 @@ def generate(vxlan): def apply(vxlan): # Check if the VXLAN interface already exists - if vxlan['ifname'] in interfaces(): - v = VXLANIf(vxlan['ifname']) - # VXLAN is super picky and the tunnel always needs to be recreated, - # thus we can simply always delete it first. - v.remove() + if 'rebuild_required' in vxlan or 'delete' in vxlan: + if vxlan['ifname'] in interfaces(): + v = VXLANIf(vxlan['ifname']) + # VXLAN is super picky and the tunnel always needs to be recreated, + # thus we can simply always delete it first. + v.remove() if 'deleted' not in vxlan: # Finally create the new interface -- cgit v1.2.3 From e64d45717940aa4fb4a072065bdfa04f884d00cc Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 23 Feb 2022 18:14:11 +0100 Subject: tunnel: T4267: "parameters ip key" on GRE not required for different remotes --- smoketest/scripts/cli/test_interfaces_tunnel.py | 98 +++++++++++++++++++++++-- src/conf_mode/interfaces-tunnel.py | 82 ++++++++++++--------- 2 files changed, 139 insertions(+), 41 deletions(-) (limited to 'src') diff --git a/smoketest/scripts/cli/test_interfaces_tunnel.py b/smoketest/scripts/cli/test_interfaces_tunnel.py index fc2e254d6..b2c045b56 100755 --- a/smoketest/scripts/cli/test_interfaces_tunnel.py +++ b/smoketest/scripts/cli/test_interfaces_tunnel.py @@ -44,14 +44,14 @@ class TunnelInterfaceTest(BasicInterfaceTest.TestCase): # call base-classes classmethod super(cls, cls).setUpClass() - def setUp(self): - super().setUp() - self.cli_set(['interfaces', 'dummy', source_if, 'address', self.local_v4 + '/32']) - self.cli_set(['interfaces', 'dummy', source_if, 'address', self.local_v6 + '/128']) + # create some test interfaces + cls.cli_set(cls, ['interfaces', 'dummy', source_if, 'address', cls.local_v4 + '/32']) + cls.cli_set(cls, ['interfaces', 'dummy', source_if, 'address', cls.local_v6 + '/128']) - def tearDown(self): - self.cli_delete(['interfaces', 'dummy', source_if]) - super().tearDown() + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['interfaces', 'dummy', source_if]) + super().tearDownClass() def test_ipv4_encapsulations(self): # When running tests ensure that for certain encapsulation types the @@ -312,5 +312,89 @@ class TunnelInterfaceTest(BasicInterfaceTest.TestCase): conf = get_interface_config(interface) self.assertEqual(new_remote, conf['linkinfo']['info_data']['remote']) + def test_tunnel_src_any_gre_key(self): + interface = f'tun1280' + encapsulation = 'gre' + src_addr = '0.0.0.0' + key = '127' + + self.cli_set(self._base_path + [interface, 'encapsulation', encapsulation]) + self.cli_set(self._base_path + [interface, 'source-address', src_addr]) + # GRE key must be supplied with a 0.0.0.0 source address + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'parameters', 'ip', 'key', key]) + + self.cli_commit() + + def test_multiple_gre_tunnel_same_remote(self): + tunnels = { + 'tun10' : { + 'encapsulation' : 'gre', + 'source_interface' : source_if, + 'remote' : '1.2.3.4', + }, + 'tun20' : { + 'encapsulation' : 'gre', + 'source_interface' : source_if, + 'remote' : '1.2.3.4', + }, + } + + for tunnel, tunnel_config in tunnels.items(): + self.cli_set(self._base_path + [tunnel, 'encapsulation', tunnel_config['encapsulation']]) + if 'source_interface' in tunnel_config: + self.cli_set(self._base_path + [tunnel, 'source-interface', tunnel_config['source_interface']]) + if 'remote' in tunnel_config: + self.cli_set(self._base_path + [tunnel, 'remote', tunnel_config['remote']]) + + # GRE key must be supplied when two or more tunnels are formed to the same desitnation + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for tunnel, tunnel_config in tunnels.items(): + self.cli_set(self._base_path + [tunnel, 'parameters', 'ip', 'key', tunnel.lstrip('tun')]) + + self.cli_commit() + + for tunnel, tunnel_config in tunnels.items(): + conf = get_interface_config(tunnel) + ip_key = tunnel.lstrip('tun') + + self.assertEqual(tunnel_config['source_interface'], conf['link']) + self.assertEqual(tunnel_config['encapsulation'], conf['linkinfo']['info_kind']) + self.assertEqual(tunnel_config['remote'], conf['linkinfo']['info_data']['remote']) + self.assertEqual(f'0.0.0.{ip_key}', conf['linkinfo']['info_data']['ikey']) + self.assertEqual(f'0.0.0.{ip_key}', conf['linkinfo']['info_data']['okey']) + + def test_multiple_gre_tunnel_different_remote(self): + tunnels = { + 'tun10' : { + 'encapsulation' : 'gre', + 'source_interface' : source_if, + 'remote' : '1.2.3.4', + }, + 'tun20' : { + 'encapsulation' : 'gre', + 'source_interface' : source_if, + 'remote' : '1.2.3.5', + }, + } + + for tunnel, tunnel_config in tunnels.items(): + self.cli_set(self._base_path + [tunnel, 'encapsulation', tunnel_config['encapsulation']]) + if 'source_interface' in tunnel_config: + self.cli_set(self._base_path + [tunnel, 'source-interface', tunnel_config['source_interface']]) + if 'remote' in tunnel_config: + self.cli_set(self._base_path + [tunnel, 'remote', tunnel_config['remote']]) + + self.cli_commit() + + for tunnel, tunnel_config in tunnels.items(): + conf = get_interface_config(tunnel) + + self.assertEqual(tunnel_config['source_interface'], conf['link']) + self.assertEqual(tunnel_config['encapsulation'], conf['linkinfo']['info_kind']) + self.assertEqual(tunnel_config['remote'], conf['linkinfo']['info_data']['remote']) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 4c1204b4e..433764b8a 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -18,7 +18,6 @@ import os from sys import exit from netifaces import interfaces -from ipaddress import IPv4Address from vyos.config import Config from vyos.configdict import get_interface_dict @@ -50,8 +49,24 @@ def get_config(config=None): base = ['interfaces', 'tunnel'] tunnel = get_interface_dict(conf, base) - tmp = leaf_node_changed(conf, ['encapsulation']) - if tmp: tunnel.update({'encapsulation_changed': {}}) + if 'deleted' not in tunnel: + tmp = leaf_node_changed(conf, ['encapsulation']) + if tmp: tunnel.update({'encapsulation_changed': {}}) + + # We also need to inspect other configured tunnels as there are Kernel + # restrictions where we need to comply. E.g. GRE tunnel key can't be used + # twice, or with multiple GRE tunnels to the same location we must specify + # a GRE key + conf.set_level(base) + tunnel['other_tunnels'] = conf.get_config_dict([], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + # delete our own instance from this dict + ifname = tunnel['ifname'] + del tunnel['other_tunnels'][ifname] + # if only one tunnel is present on the system, no need to keep this key + if len(tunnel['other_tunnels']) == 0: + del tunnel['other_tunnels'] # We must check if our interface is configured to be a DMVPN member nhrp_base = ['protocols', 'nhrp', 'tunnel'] @@ -92,48 +107,47 @@ def verify(tunnel): if 'direction' not in tunnel['parameters']['erspan']: raise ConfigError('ERSPAN version 2 requires direction to be set!') - # If tunnel source address any and key not set + # If tunnel source is any and gre key is not set + interface = tunnel['ifname'] if tunnel['encapsulation'] in ['gre'] and \ dict_search('source_address', tunnel) == '0.0.0.0' and \ dict_search('parameters.ip.key', tunnel) == None: - raise ConfigError('Tunnel parameters ip key must be set!') + raise ConfigError(f'"parameters ip key" must be set for {interface} when '\ + 'encapsulation is GRE!') - if tunnel['encapsulation'] in ['gre', 'gretap']: + gre_encapsulations = ['gre', 'gretap'] + if tunnel['encapsulation'] in gre_encapsulations and 'other_tunnels' in tunnel: # Check pairs tunnel source-address/encapsulation/key with exists tunnels. # Prevent the same key for 2 tunnels with same source-address/encap. T2920 - for tunnel_if in Section.interfaces('tunnel'): - # It makes no sense to run the test against our own interface we - # are currently configuring - if tunnel['ifname'] == tunnel_if: - continue - - tunnel_cfg = get_interface_config(tunnel_if) + for o_tunnel, o_tunnel_conf in tunnel['other_tunnels'].items(): # no match on encapsulation - bail out - if dict_search('linkinfo.info_kind', tunnel_cfg) != tunnel['encapsulation']: + our_encapsulation = tunnel['encapsulation'] + their_encapsulation = o_tunnel_conf['encapsulation'] + if our_encapsulation in gre_encapsulations and their_encapsulation \ + not in gre_encapsulations: continue - new_source_address = dict_search('source_address', tunnel) - new_source_interface = dict_search('source_interface', tunnel) - if dict_search('parameters.ip.key', tunnel) != None: - # Convert tunnel key to ip key, format "ip -j link show" - # 1 => 0.0.0.1, 999 => 0.0.3.231 - orig_new_key = dict_search('parameters.ip.key', tunnel) - new_key = IPv4Address(int(orig_new_key)) - new_key = str(new_key) - if dict_search('address', tunnel_cfg) == new_source_address and \ - dict_search('linkinfo.info_data.ikey', tunnel_cfg) == new_key: - raise ConfigError(f'Key "{orig_new_key}" for source-address "{new_source_address}" ' \ + our_address = dict_search('source_address', tunnel) + our_key = dict_search('parameters.ip.key', tunnel) + their_address = dict_search('source_address', o_tunnel_conf) + their_key = dict_search('parameters.ip.key', o_tunnel_conf) + if our_key != None: + if their_address == our_address and their_key == our_key: + raise ConfigError(f'Key "{our_key}" for source-address "{our_address}" ' \ f'is already used for tunnel "{tunnel_if}"!') else: - # If no IP GRE key is used we can not have more then one GRE tunnel - # bound to any one interface/IP address. This will result in a OS - # PermissionError: add tunnel "gre0" failed: File exists - if (dict_search('address', tunnel_cfg) == new_source_address or - (dict_search('address', tunnel_cfg) == '0.0.0.0' and - dict_search('link', tunnel_cfg) == new_source_interface)): - raise ConfigError(f'Missing required "ip key" parameter when \ - running more then one GRE based tunnel on the \ - same source-interface/source-address') + our_source_if = dict_search('source_interface', tunnel) + their_source_if = dict_search('source_interface', o_tunnel_conf) + our_remote = dict_search('remote', tunnel) + their_remote = dict_search('remote', o_tunnel_conf) + # If no IP GRE key is defined we can not have more then one GRE tunnel + # bound to any one interface/IP address and the same remote. This will + # result in a OS PermissionError: add tunnel "gre0" failed: File exists + if (their_address == our_address or our_source_if == their_source_if) and \ + our_remote == their_remote: + raise ConfigError(f'Missing required "ip key" parameter when '\ + 'running more then one GRE based tunnel on the '\ + 'same source-interface/source-address') # Keys are not allowed with ipip and sit tunnels if tunnel['encapsulation'] in ['ipip', 'sit']: -- cgit v1.2.3 From 0daf168d3d7583984431de2ef97682ff4c986f74 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Feb 2022 22:30:34 +0100 Subject: zone-policy: T2199: bugfix defaultValue usage Instead of hardcoding the default behavior inside the Jinaj2 template, all defaults are required to be specified inside teh XML definition. This is required to automatically render the appropriate CLI tab completion commands. --- data/templates/zone_policy/nftables.tmpl | 12 ++++++------ interface-definitions/zone-policy.xml.in | 1 + src/conf_mode/zone_policy.py | 24 ++++++++++++++++++------ 3 files changed, 25 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/data/templates/zone_policy/nftables.tmpl b/data/templates/zone_policy/nftables.tmpl index 093da6bd8..4a6bd2772 100644 --- a/data/templates/zone_policy/nftables.tmpl +++ b/data/templates/zone_policy/nftables.tmpl @@ -16,7 +16,7 @@ table ip filter { iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME_{{ from_conf.firewall.name }} iifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endfor %} - counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }} + counter {{ zone_conf.default_action }} } chain VZONE_{{ zone_name }}_OUT { oifname lo counter return @@ -24,7 +24,7 @@ table ip filter { oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME_{{ from_conf.firewall.name }} oifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endfor %} - counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }} + counter {{ zone_conf.default_action }} } {% else %} chain VZONE_{{ zone_name }} { @@ -38,7 +38,7 @@ table ip filter { iifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endif %} {% endfor %} - counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }} + counter {{ zone_conf.default_action }} } {% endif %} {% endfor %} @@ -53,7 +53,7 @@ table ip6 filter { iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }} iifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endfor %} - counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }} + counter {{ zone_conf.default_action }} } chain VZONE6_{{ zone_name }}_OUT { oifname lo counter return @@ -61,7 +61,7 @@ table ip6 filter { oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }} oifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endfor %} - counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }} + counter {{ zone_conf.default_action }} } {% else %} chain VZONE6_{{ zone_name }} { @@ -75,7 +75,7 @@ table ip6 filter { iifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endif %} {% endfor %} - counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }} + counter {{ zone_conf.default_action }} } {% endif %} {% endfor %} diff --git a/interface-definitions/zone-policy.xml.in b/interface-definitions/zone-policy.xml.in index b898c3ecd..eac63fa6b 100644 --- a/interface-definitions/zone-policy.xml.in +++ b/interface-definitions/zone-policy.xml.in @@ -37,6 +37,7 @@ ^(drop|reject)$ + drop diff --git a/src/conf_mode/zone_policy.py b/src/conf_mode/zone_policy.py index 683f8f034..dc0617353 100755 --- a/src/conf_mode/zone_policy.py +++ b/src/conf_mode/zone_policy.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 @@ -20,10 +20,12 @@ 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() @@ -36,12 +38,22 @@ def get_config(config=None): 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 = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) - if zone_policy: - zone_policy['firewall'] = conf.get_config_dict(['firewall'], 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 -- cgit v1.2.3 From 291558023bbd1bdbef6e00c4eec173cf5c9575d8 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 26 Feb 2022 16:30:10 +0100 Subject: lldp: T4272: migrate to get_config_dict() --- data/templates/lldp/lldpd.tmpl | 3 +- data/templates/lldp/vyos.conf.tmpl | 35 +++--- interface-definitions/lldp.xml.in | 22 ++-- src/conf_mode/lldp.py | 235 ++++++++++--------------------------- 4 files changed, 94 insertions(+), 201 deletions(-) (limited to 'src') diff --git a/data/templates/lldp/lldpd.tmpl b/data/templates/lldp/lldpd.tmpl index 3db955b48..819e70c84 100644 --- a/data/templates/lldp/lldpd.tmpl +++ b/data/templates/lldp/lldpd.tmpl @@ -1,3 +1,2 @@ ### Autogenerated by lldp.py ### -DAEMON_ARGS="-M 4{% if options.snmp %} -x{% endif %}{% if options.cdp %} -c{% endif %}{% if options.edp %} -e{% endif %}{% if options.fdp %} -f{% endif %}{% if options.sonmp %} -s{% endif %}" - +DAEMON_ARGS="-M 4{% if snmp is defined and snmp.enable is defined %} -x{% endif %}{% if legacy_protocols is defined and legacy_protocols.cdp is defined %} -c{% endif %}{% if legacy_protocols is defined and legacy_protocols.edp is defined %} -e{% endif %}{% if legacy_protocols is defined and legacy_protocols.fdp is defined %} -f{% endif %}{% if legacy_protocols is defined and legacy_protocols.sonmp is defined %} -s{% endif %}" diff --git a/data/templates/lldp/vyos.conf.tmpl b/data/templates/lldp/vyos.conf.tmpl index 07bbaf604..592dcf61f 100644 --- a/data/templates/lldp/vyos.conf.tmpl +++ b/data/templates/lldp/vyos.conf.tmpl @@ -1,20 +1,25 @@ ### Autogenerated by lldp.py ### configure system platform VyOS -configure system description "VyOS {{ options.description }}" -{% if options.listen_on %} -configure system interface pattern "{{ ( options.listen_on | select('equalto','all') | map('replace','all','*') | list + options.listen_on | select('equalto','!all') | map('replace','!all','!*') | list + options.listen_on | reject('equalto','all') | reject('equalto','!all') | list ) | unique | join(",") }}" +configure system description "VyOS {{ version }}" +{% if interface is defined and interface is not none %} +{% set tmp = [] %} +{% for iface, iface_options in interface.items() if not iface_options.disable %} +{% if iface == 'all' %} +{% set iface = '*' %} +{% endif %} +{% set _ = tmp.append(iface) %} +{% if iface_options.location is defined and iface_options.location is not none %} +{% if iface_options.location.elin is defined and iface_options.location.elin is not none %} +configure ports {{ iface }} med location elin "{{ iface_options.location.elin }}" +{% endif %} +{% if iface_options.location is defined and iface_options.location.coordinate_based is not none and iface_options.location.coordinate_based is not none %} +configure ports {{ iface }} med location coordinate latitude "{{ iface_options.location.coordinate_based.latitude }}" longitude "{{ iface_options.location.coordinate_based.longitude }}" altitude "{{ iface_options.location.coordinate_based.altitude }}m" datum "{{ iface_options.location.coordinate_based.datum }}" +{% endif %} +{% endif %} +{% endfor %} +configure system interface pattern "{{ tmp | join(",") }}" {% endif %} -{% if options.mgmt_addr %} -configure system ip management pattern {{ options.mgmt_addr | join(",") }} +{% if management_address is defined and management_address is not none %} +configure system ip management pattern {{ management_address | join(",") }} {% endif %} -{% for loc in location %} -{% if loc.elin %} -configure ports {{ loc.name }} med location elin "{{ loc.elin }}" -{% endif %} -{% if loc.coordinate_based %} -configure ports {{ loc.name }} med location coordinate {% if loc.coordinate_based.latitude %}latitude {{ loc.coordinate_based.latitude }}{% endif %} {% if loc.coordinate_based.longitude %}longitude {{ loc.coordinate_based.longitude }}{% endif %} {% if loc.coordinate_based.altitude %}altitude {{ loc.coordinate_based.altitude }} m{% endif %} {% if loc.coordinate_based.datum %}datum {{ loc.coordinate_based.datum }}{% endif %} -{% endif %} - - -{% endfor %} diff --git a/interface-definitions/lldp.xml.in b/interface-definitions/lldp.xml.in index 32ef0ad14..b9ffe234c 100644 --- a/interface-definitions/lldp.xml.in +++ b/interface-definitions/lldp.xml.in @@ -28,7 +28,7 @@ #include - LLDP-MED location data [REQUIRED] + LLDP-MED location data @@ -39,6 +39,10 @@ Altitude in meters + + 0 + No altitude + [+-]<meters> Altitude in meters @@ -48,13 +52,14 @@ + 0 Coordinate datum type WGS84 - WGS84 (default) + WGS84 NAD83 @@ -69,33 +74,34 @@ Datum should be WGS84, NAD83, or MLLW - ^(WGS84|NAD83|MLLW)$ + (WGS84|NAD83|MLLW) + WGS84 - Latitude [REQUIRED] + Latitude <latitude> Latitude (example "37.524449N") Latitude should be a number followed by S or N - (\d+)(\.\d+)?[nNsS]$ + (\d+)(\.\d+)?[nNsS] - Longitude [REQUIRED] + Longitude <longitude> Longitude (example "122.267255W") Longiture should be a number followed by E or W - (\d+)(\.\d+)?[eEwW]$ + (\d+)(\.\d+)?[eEwW] @@ -109,7 +115,7 @@ Emergency Call Service ELIN number (between 10-25 numbers) - [0-9]{10,25}$ + [0-9]{10,25} ELIN number must be between 10-25 numbers diff --git a/src/conf_mode/lldp.py b/src/conf_mode/lldp.py index 082c3e128..db8328259 100755 --- a/src/conf_mode/lldp.py +++ b/src/conf_mode/lldp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2017-2020 VyOS maintainers and contributors +# Copyright (C) 2017-2022 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -15,19 +15,19 @@ # along with this program. If not, see . import os -import re -from copy import deepcopy from sys import exit from vyos.config import Config +from vyos.configdict import dict_merge from vyos.validate import is_addr_assigned from vyos.validate import is_loopback_addr from vyos.version import get_version_data -from vyos import ConfigError from vyos.util import call +from vyos.util import dict_search +from vyos.xml import defaults from vyos.template import render - +from vyos import ConfigError from vyos import airbag airbag.enable() @@ -35,178 +35,73 @@ config_file = "/etc/default/lldpd" vyos_config_file = "/etc/lldpd.d/01-vyos.conf" base = ['service', 'lldp'] -default_config_data = { - "options": '', - "interface_list": '', - "location": '' -} - -def get_options(config): - options = {} - config.set_level(base) - - options['listen_vlan'] = config.exists('listen-vlan') - options['mgmt_addr'] = [] - for addr in config.return_values('management-address'): - if is_addr_assigned(addr) and not is_loopback_addr(addr): - options['mgmt_addr'].append(addr) - else: - message = 'WARNING: LLDP management address {0} invalid - '.format(addr) - if is_loopback_addr(addr): - message += '(loopback address).' - else: - message += 'address not found.' - print(message) - - snmp = config.exists('snmp enable') - options["snmp"] = snmp - if snmp: - config.set_level('') - options["sys_snmp"] = config.exists('service snmp') - config.set_level(base) - - config.set_level(base + ['legacy-protocols']) - options['cdp'] = config.exists('cdp') - options['edp'] = config.exists('edp') - options['fdp'] = config.exists('fdp') - options['sonmp'] = config.exists('sonmp') - - # start with an unknown version information - version_data = get_version_data() - options['description'] = version_data['version'] - options['listen_on'] = [] - - return options - -def get_interface_list(config): - config.set_level(base) - intfs_names = config.list_nodes(['interface']) - if len(intfs_names) < 0: - return 0 - - interface_list = [] - for name in intfs_names: - config.set_level(base + ['interface', name]) - disable = config.exists(['disable']) - intf = { - 'name': name, - 'disable': disable - } - interface_list.append(intf) - return interface_list - - -def get_location_intf(config, name): - path = base + ['interface', name] - config.set_level(path) - - config.set_level(path + ['location']) - elin = '' - coordinate_based = {} - - if config.exists('elin'): - elin = config.return_value('elin') - - if config.exists('coordinate-based'): - config.set_level(path + ['location', 'coordinate-based']) - - coordinate_based['latitude'] = config.return_value(['latitude']) - coordinate_based['longitude'] = config.return_value(['longitude']) - - coordinate_based['altitude'] = '0' - if config.exists(['altitude']): - coordinate_based['altitude'] = config.return_value(['altitude']) - - coordinate_based['datum'] = 'WGS84' - if config.exists(['datum']): - coordinate_based['datum'] = config.return_value(['datum']) - - intf = { - 'name': name, - 'elin': elin, - 'coordinate_based': coordinate_based - - } - return intf - - -def get_location(config): - config.set_level(base) - intfs_names = config.list_nodes(['interface']) - if len(intfs_names) < 0: - return 0 - - if config.exists('disable'): - return 0 - - intfs_location = [] - for name in intfs_names: - intf = get_location_intf(config, name) - intfs_location.append(intf) - - return intfs_location - - def get_config(config=None): - lldp = deepcopy(default_config_data) if config: conf = config else: conf = Config() + if not conf.exists(base): - return None - else: - lldp['options'] = get_options(conf) - lldp['interface_list'] = get_interface_list(conf) - lldp['location'] = get_location(conf) + return {} - return lldp + lldp = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + if conf.exists(['service', 'snmp']): + lldp['system_snmp_enabled'] = '' + + version_data = get_version_data() + lldp['version'] = version_data['version'] + + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + # location coordinates have a default value + if 'interface' in lldp: + for interface, interface_config in lldp['interface'].items(): + default_values = defaults(base + ['interface']) + if dict_search('location.coordinate_based', interface_config) == None: + # no location specified - no need to add defaults + del default_values['location']['coordinate_based']['datum'] + del default_values['location']['coordinate_based']['altitude'] + + # cleanup default_values dictionary from inner to outer + # this might feel overkill here, but it does support easy extension + # in the future with additional default values + if len(default_values['location']['coordinate_based']) == 0: + del default_values['location']['coordinate_based'] + if len(default_values['location']) == 0: + del default_values['location'] + + lldp['interface'][interface] = dict_merge(default_values, + lldp['interface'][interface]) + + return lldp def verify(lldp): # bail out early - looks like removal from running config if lldp is None: return - # check location - for location in lldp['location']: - # check coordinate-based - if len(location['coordinate_based']) > 0: - # check longitude and latitude - if not location['coordinate_based']['longitude']: - raise ConfigError('Must define longitude for interface {0}'.format(location['name'])) - - if not location['coordinate_based']['latitude']: - raise ConfigError('Must define latitude for interface {0}'.format(location['name'])) - - if not re.match(r'^(\d+)(\.\d+)?[nNsS]$', location['coordinate_based']['latitude']): - raise ConfigError('Invalid location for interface {0}:\n' \ - 'latitude should be a number followed by S or N'.format(location['name'])) - - if not re.match(r'^(\d+)(\.\d+)?[eEwW]$', location['coordinate_based']['longitude']): - raise ConfigError('Invalid location for interface {0}:\n' \ - 'longitude should be a number followed by E or W'.format(location['name'])) - - # check altitude and datum if exist - if location['coordinate_based']['altitude']: - if not re.match(r'^[-+0-9\.]+$', location['coordinate_based']['altitude']): - raise ConfigError('Invalid location for interface {0}:\n' \ - 'altitude should be a positive or negative number'.format(location['name'])) - - if location['coordinate_based']['datum']: - if not re.match(r'^(WGS84|NAD83|MLLW)$', location['coordinate_based']['datum']): - raise ConfigError("Invalid location for interface {0}:\n' \ - 'datum should be WGS84, NAD83, or MLLW".format(location['name'])) - - # check elin - elif location['elin']: - if not re.match(r'^[0-9]{10,25}$', location['elin']): - raise ConfigError('Invalid location for interface {0}:\n' \ - 'ELIN number must be between 10-25 numbers'.format(location['name'])) + if 'management_address' in lldp: + for address in lldp['management_address']: + message = f'WARNING: LLDP management address "{address}" is invalid' + if is_loopback_addr(address): + print(f'{message} - loopback address') + elif not is_addr_assigned(address): + print(f'{message} - not assigned to any interface') + + if 'interface' in lldp: + for interface, interface_config in lldp['interface'].items(): + # bail out early if no location info present in interface config + if 'location' not in interface_config: + continue + if 'coordinate_based' in interface_config['location']: + if not {'latitude', 'latitude'} <= set(interface_config['location']['coordinate_based']): + raise ConfigError(f'Must define both longitude and latitude for "{interface}" location!') # check options - if lldp['options']['snmp']: - if not lldp['options']['sys_snmp']: + if 'snmp' in lldp and 'enable' in lldp['snmp']: + if 'system_snmp_enabled' not in lldp: raise ConfigError('SNMP must be configured to enable LLDP SNMP') @@ -215,29 +110,17 @@ def generate(lldp): if lldp is None: return - # generate listen on interfaces - for intf in lldp['interface_list']: - tmp = '' - # add exclamation mark if interface is disabled - if intf['disable']: - tmp = '!' - - tmp += intf['name'] - lldp['options']['listen_on'].append(tmp) - - # generate /etc/default/lldpd render(config_file, 'lldp/lldpd.tmpl', lldp) - # generate /etc/lldpd.d/01-vyos.conf render(vyos_config_file, 'lldp/vyos.conf.tmpl', lldp) - def apply(lldp): + systemd_service = 'lldpd.service' if lldp: # start/restart lldp service - call('systemctl restart lldpd.service') + call(f'systemctl restart {systemd_service}') else: # LLDP service has been terminated - call('systemctl stop lldpd.service') + call(f'systemctl stop {systemd_service}') if os.path.isfile(config_file): os.unlink(config_file) if os.path.isfile(vyos_config_file): -- cgit v1.2.3 From 61fa1c95164e4222e79b078b1a796f41397e0ee3 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 28 Feb 2022 14:28:55 +0100 Subject: ssh: T4273: bugfix cipher and key-exchange multi nodes After hardning the regex validator to be preceeded with ^ and ending with $ it was no longer possible to have a comma separated list as SSH ciphers. The migrations cript is altered to migrate the previous comma separated list to individual multi node entries - cipher and key-exchange always had been multinodes - so this just re-arranges some values and does not break CLI compatibility --- interface-definitions/ssh.xml.in | 8 ++-- smoketest/configs/basic-vyos | 88 ++++++++++++++++++++++++++++++++++++++++ src/migration-scripts/ssh/1-to-2 | 50 +++++++++++++++++------ 3 files changed, 130 insertions(+), 16 deletions(-) create mode 100644 smoketest/configs/basic-vyos (limited to 'src') diff --git a/interface-definitions/ssh.xml.in b/interface-definitions/ssh.xml.in index 187e5f8e8..8edbad110 100644 --- a/interface-definitions/ssh.xml.in +++ b/interface-definitions/ssh.xml.in @@ -44,7 +44,7 @@ 3des-cbc aes128-cbc aes192-cbc aes256-cbc rijndael-cbc@lysator.liu.se aes128-ctr aes192-ctr aes256-ctr aes128-gcm@openssh.com aes256-gcm@openssh.com chacha20-poly1305@openssh.com - ^(3des-cbc|aes128-cbc|aes192-cbc|aes256-cbc|rijndael-cbc@lysator.liu.se|aes128-ctr|aes192-ctr|aes256-ctr|aes128-gcm@openssh.com|aes256-gcm@openssh.com|chacha20-poly1305@openssh.com)$ + (3des-cbc|aes128-cbc|aes192-cbc|aes256-cbc|rijndael-cbc@lysator.liu.se|aes128-ctr|aes192-ctr|aes256-ctr|aes128-gcm@openssh.com|aes256-gcm@openssh.com|chacha20-poly1305@openssh.com) @@ -70,7 +70,7 @@ - ^(diffie-hellman-group1-sha1|diffie-hellman-group14-sha1|diffie-hellman-group14-sha256|diffie-hellman-group16-sha512|diffie-hellman-group18-sha512|diffie-hellman-group-exchange-sha1|diffie-hellman-group-exchange-sha256|ecdh-sha2-nistp256|ecdh-sha2-nistp384|ecdh-sha2-nistp521|curve25519-sha256|curve25519-sha256@libssh.org)$ + (diffie-hellman-group1-sha1|diffie-hellman-group14-sha1|diffie-hellman-group14-sha256|diffie-hellman-group16-sha512|diffie-hellman-group18-sha512|diffie-hellman-group-exchange-sha1|diffie-hellman-group-exchange-sha256|ecdh-sha2-nistp256|ecdh-sha2-nistp384|ecdh-sha2-nistp521|curve25519-sha256|curve25519-sha256@libssh.org) @@ -102,7 +102,7 @@ enable logging of failed login attempts - ^(quiet|fatal|error|info|verbose)$ + (quiet|fatal|error|info|verbose) info @@ -115,7 +115,7 @@ hmac-sha1 hmac-sha1-96 hmac-sha2-256 hmac-sha2-512 hmac-md5 hmac-md5-96 umac-64@openssh.com umac-128@openssh.com hmac-sha1-etm@openssh.com hmac-sha1-96-etm@openssh.com hmac-sha2-256-etm@openssh.com hmac-sha2-512-etm@openssh.com hmac-md5-etm@openssh.com hmac-md5-96-etm@openssh.com umac-64-etm@openssh.com umac-128-etm@openssh.com - ^(hmac-sha1|hmac-sha1-96|hmac-sha2-256|hmac-sha2-512|hmac-md5|hmac-md5-96|umac-64@openssh.com|umac-128@openssh.com|hmac-sha1-etm@openssh.com|hmac-sha1-96-etm@openssh.com|hmac-sha2-256-etm@openssh.com|hmac-sha2-512-etm@openssh.com|hmac-md5-etm@openssh.com|hmac-md5-96-etm@openssh.com|umac-64-etm@openssh.com|umac-128-etm@openssh.com)$ + (hmac-sha1|hmac-sha1-96|hmac-sha2-256|hmac-sha2-512|hmac-md5|hmac-md5-96|umac-64@openssh.com|umac-128@openssh.com|hmac-sha1-etm@openssh.com|hmac-sha1-96-etm@openssh.com|hmac-sha2-256-etm@openssh.com|hmac-sha2-512-etm@openssh.com|hmac-md5-etm@openssh.com|hmac-md5-96-etm@openssh.com|umac-64-etm@openssh.com|umac-128-etm@openssh.com) diff --git a/smoketest/configs/basic-vyos b/smoketest/configs/basic-vyos new file mode 100644 index 000000000..493feed5b --- /dev/null +++ b/smoketest/configs/basic-vyos @@ -0,0 +1,88 @@ +interfaces { + ethernet eth0 { + address 192.168.0.1/24 + duplex auto + smp-affinity auto + speed auto + } + ethernet eth1 { + address 100.64.0.0/31 + duplex auto + smp-affinity auto + speed auto + } + loopback lo { + } +} +protocols { + static { + route 0.0.0.0/0 { + next-hop 100.64.0.1 { + } + } + } +} +service { + dhcp-server { + shared-network-name LAN { + authoritative + subnet 192.168.0.0/24 { + default-router 192.168.0.1 + dns-server 192.168.0.1 + domain-name vyos.net + domain-search vyos.net + range LANDynamic { + start 192.168.0.20 + stop 192.168.0.240 + } + } + } + } + dns { + forwarding { + allow-from 192.168.0.0/16 + cache-size 10000 + dnssec off + listen-address 192.168.0.1 + } + } + ssh { + ciphers aes128-ctr,aes192-ctr,aes256-ctr + ciphers chacha20-poly1305@openssh.com,rijndael-cbc@lysator.liu.se + listen-address 192.168.0.1 + key-exchange curve25519-sha256@libssh.org + key-exchange diffie-hellman-group1-sha1,diffie-hellman-group-exchange-sha1,diffie-hellman-group-exchange-sha256 + port 22 + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + name-server 192.168.0.1 + syslog { + global { + facility all { + level info + } + } + } + time-zone Europe/Berlin +} +/* Warning: Do not remove the following line. */ +/* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pptp@1:qos@1:quagga@6:snmp@1:ssh@1:system@9:vrrp@2:wanloadbalance@3:webgui@1:webproxy@1:webproxy@2:zone-policy@1" === */ +/* Release version: 1.2.6 */ diff --git a/src/migration-scripts/ssh/1-to-2 b/src/migration-scripts/ssh/1-to-2 index bc8815753..31c40df16 100755 --- a/src/migration-scripts/ssh/1-to-2 +++ b/src/migration-scripts/ssh/1-to-2 @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -30,26 +30,52 @@ file_name = argv[1] with open(file_name, 'r') as f: config_file = f.read() -base = ['service', 'ssh', 'loglevel'] +base = ['service', 'ssh'] config = ConfigTree(config_file) if not config.exists(base): # Nothing to do exit(0) -else: - # red in configured loglevel and convert it to lower case - tmp = config.return_value(base).lower() +path_loglevel = base + ['loglevel'] +if config.exists(path_loglevel): + # red in configured loglevel and convert it to lower case + tmp = config.return_value(path_loglevel).lower() # VyOS 1.2 had no proper value validation on the CLI thus the # user could use any arbitrary values - sanitize them if tmp not in ['quiet', 'fatal', 'error', 'info', 'verbose']: tmp = 'info' + config.set(path_loglevel, value=tmp) + +# T4273: migrate ssh cipher list to multi node +path_ciphers = base + ['ciphers'] +if config.exists(path_ciphers): + tmp = [] + # get curtrent cipher list - comma delimited + for cipher in config.return_values(path_ciphers): + tmp.extend(cipher.split(',')) + # delete old cipher suite representation + config.delete(path_ciphers) - config.set(base, value=tmp) + for cipher in tmp: + config.set(path_ciphers, value=cipher, replace=False) - 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) +# T4273: migrate ssh key-exchange list to multi node +path_kex = base + ['key-exchange'] +if config.exists(path_kex): + tmp = [] + # get curtrent cipher list - comma delimited + for kex in config.return_values(path_kex): + tmp.extend(kex.split(',')) + # delete old cipher suite representation + config.delete(path_kex) + + for kex in tmp: + config.set(path_kex, value=kex, replace=False) + +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) -- cgit v1.2.3 From 42c011224e5aef3c27f9de6b5a74e594a404131e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 1 Mar 2022 19:09:12 +0100 Subject: flow-accounting: T4277: support sending flow-data via VRF interface It should be possible to send the gathered data via a VRF bound interface to the collector. This is somehow related to T3981 but it's the opposite side of the netflow process. set system flow-accounting vrf --- data/templates/netflow/uacctd.conf.tmpl | 74 ---------------------- data/templates/pmacct/override.conf.tmpl | 17 +++++ data/templates/pmacct/uacctd.conf.tmpl | 74 ++++++++++++++++++++++ interface-definitions/flow-accounting-conf.xml.in | 1 + .../scripts/cli/test_system_flow-accounting.py | 5 +- src/conf_mode/flow_accounting_conf.py | 14 ++-- .../systemd/system/uacctd.service.d/override.conf | 14 ---- 7 files changed, 106 insertions(+), 93 deletions(-) delete mode 100644 data/templates/netflow/uacctd.conf.tmpl create mode 100644 data/templates/pmacct/override.conf.tmpl create mode 100644 data/templates/pmacct/uacctd.conf.tmpl delete mode 100644 src/etc/systemd/system/uacctd.service.d/override.conf (limited to 'src') diff --git a/data/templates/netflow/uacctd.conf.tmpl b/data/templates/netflow/uacctd.conf.tmpl deleted file mode 100644 index f81002dc1..000000000 --- a/data/templates/netflow/uacctd.conf.tmpl +++ /dev/null @@ -1,74 +0,0 @@ -# Genereated from VyOS configuration -daemonize: true -promisc: false -pidfile: /run/pmacct/uacctd.pid -uacctd_group: 2 -uacctd_nl_size: 2097152 -snaplen: {{ packet_length }} -aggregate: in_iface{{ ',out_iface' if enable_egress is defined }},src_mac,dst_mac,vlan,src_host,dst_host,src_port,dst_port,proto,tos,flows -{% set pipe_size = buffer_size | int *1024 *1024 %} -plugin_pipe_size: {{ pipe_size }} -{# We need an integer division (//) without any remainder or fraction #} -plugin_buffer_size: {{ pipe_size // 1000 }} -{% if syslog_facility is defined and syslog_facility is not none %} -syslog: {{ syslog_facility }} -{% endif %} -{% if disable_imt is not defined %} -imt_path: /tmp/uacctd.pipe -imt_mem_pools_number: 169 -{% endif %} - -{% set plugin = [] %} -{% if disable_imt is not defined %} -{% set plugin = ['memory'] %} -{% endif %} -{% if netflow is defined and netflow.server is defined and netflow.server is not none %} -{% for server in netflow.server %} -{% set plugin = plugin.append('nfprobe[nf_' ~ server ~ ']') %} -{% endfor %} -{% endif %} -{% if sflow is defined and sflow.server is defined and sflow.server is not none %} -{% for server in sflow.server %} -{% set plugin = plugin.append('sfprobe[sf_' ~ server ~ ']') %} -{% endfor %} -{% endif %} -plugins: {{ plugin | join(',') }} - -{% if netflow is defined and netflow.server is defined and netflow.server is not none %} -# NetFlow servers -{% for server, server_config in netflow.server.items() %} -nfprobe_receiver[nf_{{ server }}]: {{ server }}:{{ server_config.port }} -nfprobe_version[nf_{{ server }}]: {{ netflow.version }} -{% if netflow.engine_id is defined and netflow.engine_id is not none %} -nfprobe_engine[nf_{{ server }}]: {{ netflow.engine_id }} -{% endif %} -{% if netflow.max_flows is defined and netflow.max_flows is not none %} -nfprobe_maxflows[nf_{{ server }}]: {{ netflow.max_flows }} -{% endif %} -{% if netflow.sampling_rate is defined and netflow.sampling_rate is not none %} -sampling_rate[nf_{{ server }}]: {{ netflow.sampling_rate }} -{% endif %} -{% if netflow.source_address is defined and netflow.source_address is not none %} -nfprobe_source_ip[nf_{{ server }}]: {{ netflow.source_address }} -{% endif %} -{% if netflow.timeout is defined and netflow.timeout is not none %} -nfprobe_timeouts[nf_{{ server }}]: expint={{ netflow.timeout.expiry_interval }}:general={{ netflow.timeout.flow_generic }}:icmp={{ netflow.timeout.icmp }}:maxlife={{ netflow.timeout.max_active_life }}:tcp.fin={{ netflow.timeout.tcp_fin }}:tcp={{ netflow.timeout.tcp_generic }}:tcp.rst={{ netflow.timeout.tcp_rst }}:udp={{ netflow.timeout.udp }} -{% endif %} - -{% endfor %} -{% endif %} - -{% if sflow is defined and sflow.server is defined and sflow.server is not none %} -# sFlow servers -{% for server, server_config in sflow.server.items() %} -sfprobe_receiver[sf_{{ server }}]: {{ server }}:{{ server_config.port }} -sfprobe_agentip[sf_{{ server }}]: {{ sflow.agent_address }} -{% if sflow.sampling_rate is defined and sflow.sampling_rate is not none %} -sampling_rate[sf_{{ server }}]: {{ sflow.sampling_rate }} -{% endif %} -{% if sflow.source_address is defined and sflow.source_address is not none %} -sfprobe_source_ip[sf_{{ server }}]: {{ sflow.source_address }} -{% endif %} - -{% endfor %} -{% endif %} diff --git a/data/templates/pmacct/override.conf.tmpl b/data/templates/pmacct/override.conf.tmpl new file mode 100644 index 000000000..216927666 --- /dev/null +++ b/data/templates/pmacct/override.conf.tmpl @@ -0,0 +1,17 @@ +{% set vrf_command = 'ip vrf exec ' + vrf + ' ' if vrf is defined else '' %} +[Unit] +After= +After=vyos-router.service +ConditionPathExists= +ConditionPathExists=/run/pmacct/uacctd.conf + +[Service] +EnvironmentFile= +ExecStart= +ExecStart={{vrf_command}}/usr/sbin/uacctd -f /run/pmacct/uacctd.conf +WorkingDirectory= +WorkingDirectory=/run/pmacct +PIDFile= +PIDFile=/run/pmacct/uacctd.pid +Restart=always +RestartSec=10 diff --git a/data/templates/pmacct/uacctd.conf.tmpl b/data/templates/pmacct/uacctd.conf.tmpl new file mode 100644 index 000000000..b58f7c796 --- /dev/null +++ b/data/templates/pmacct/uacctd.conf.tmpl @@ -0,0 +1,74 @@ +# Genereated from VyOS configuration +daemonize: true +promisc: false +pidfile: /run/pmacct/uacctd.pid +uacctd_group: 2 +uacctd_nl_size: 2097152 +snaplen: {{ packet_length }} +aggregate: in_iface{{ ',out_iface' if enable_egress is defined }},src_mac,dst_mac,vlan,src_host,dst_host,src_port,dst_port,proto,tos,flows +{% set pipe_size = buffer_size | int *1024 *1024 %} +plugin_pipe_size: {{ pipe_size }} +{# We need an integer division (//) without any remainder or fraction #} +plugin_buffer_size: {{ pipe_size // 1000 }} +{% if syslog_facility is defined and syslog_facility is not none %} +syslog: {{ syslog_facility }} +{% endif %} +{% if disable_imt is not defined %} +imt_path: /tmp/uacctd.pipe +imt_mem_pools_number: 169 +{% endif %} + +{% set plugin = [] %} +{% if netflow is defined and netflow.server is defined and netflow.server is not none %} +{% for server in netflow.server %} +{% set _ = plugin.append('nfprobe[nf_' ~ server ~ ']') %} +{% endfor %} +{% endif %} +{% if sflow is defined and sflow.server is defined and sflow.server is not none %} +{% for server in sflow.server %} +{% set _ = plugin.append('sfprobe[sf_' ~ server ~ ']') %} +{% endfor %} +{% endif %} +{% if disable_imt is not defined %} +{% set _ = plugin.append('memory') %} +{% endif %} +plugins: {{ plugin | join(',') }} + +{% if netflow is defined and netflow.server is defined and netflow.server is not none %} +# NetFlow servers +{% for server, server_config in netflow.server.items() %} +nfprobe_receiver[nf_{{ server }}]: {{ server }}:{{ server_config.port }} +nfprobe_version[nf_{{ server }}]: {{ netflow.version }} +{% if netflow.engine_id is defined and netflow.engine_id is not none %} +nfprobe_engine[nf_{{ server }}]: {{ netflow.engine_id }} +{% endif %} +{% if netflow.max_flows is defined and netflow.max_flows is not none %} +nfprobe_maxflows[nf_{{ server }}]: {{ netflow.max_flows }} +{% endif %} +{% if netflow.sampling_rate is defined and netflow.sampling_rate is not none %} +sampling_rate[nf_{{ server }}]: {{ netflow.sampling_rate }} +{% endif %} +{% if netflow.source_address is defined and netflow.source_address is not none %} +nfprobe_source_ip[nf_{{ server }}]: {{ netflow.source_address }} +{% endif %} +{% if netflow.timeout is defined and netflow.timeout is not none %} +nfprobe_timeouts[nf_{{ server }}]: expint={{ netflow.timeout.expiry_interval }}:general={{ netflow.timeout.flow_generic }}:icmp={{ netflow.timeout.icmp }}:maxlife={{ netflow.timeout.max_active_life }}:tcp.fin={{ netflow.timeout.tcp_fin }}:tcp={{ netflow.timeout.tcp_generic }}:tcp.rst={{ netflow.timeout.tcp_rst }}:udp={{ netflow.timeout.udp }} +{% endif %} + +{% endfor %} +{% endif %} + +{% if sflow is defined and sflow.server is defined and sflow.server is not none %} +# sFlow servers +{% for server, server_config in sflow.server.items() %} +sfprobe_receiver[sf_{{ server }}]: {{ server }}:{{ server_config.port }} +sfprobe_agentip[sf_{{ server }}]: {{ sflow.agent_address }} +{% if sflow.sampling_rate is defined and sflow.sampling_rate is not none %} +sampling_rate[sf_{{ server }}]: {{ sflow.sampling_rate }} +{% endif %} +{% if sflow.source_address is defined and sflow.source_address is not none %} +sfprobe_source_ip[sf_{{ server }}]: {{ sflow.source_address }} +{% endif %} + +{% endfor %} +{% endif %} diff --git a/interface-definitions/flow-accounting-conf.xml.in b/interface-definitions/flow-accounting-conf.xml.in index 05cf5e170..133e45c72 100644 --- a/interface-definitions/flow-accounting-conf.xml.in +++ b/interface-definitions/flow-accounting-conf.xml.in @@ -431,6 +431,7 @@ #include + #include diff --git a/smoketest/scripts/cli/test_system_flow-accounting.py b/smoketest/scripts/cli/test_system_flow-accounting.py index 857df1be6..84f17bcb0 100755 --- a/smoketest/scripts/cli/test_system_flow-accounting.py +++ b/smoketest/scripts/cli/test_system_flow-accounting.py @@ -39,6 +39,9 @@ class TestSystemFlowAccounting(VyOSUnitTestSHIM.TestCase): cls.cli_delete(cls, base_path) def tearDown(self): + # after service removal process must no longer run + self.assertTrue(process_named_running(PROCESS_NAME)) + self.cli_delete(base_path) self.cli_commit() @@ -213,9 +216,9 @@ class TestSystemFlowAccounting(VyOSUnitTestSHIM.TestCase): uacctd = read_file(uacctd_conf) tmp = [] - tmp.append('memory') for server, server_config in netflow_server.items(): tmp.append(f'nfprobe[nf_{server}]') + tmp.append('memory') self.assertIn('plugins: ' + ','.join(tmp), uacctd) for server, server_config in netflow_server.items(): diff --git a/src/conf_mode/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py index 975f19acf..25bf54790 100755 --- a/src/conf_mode/flow_accounting_conf.py +++ b/src/conf_mode/flow_accounting_conf.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2021 VyOS maintainers and contributors +# Copyright (C) 2018-2022 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -27,6 +27,7 @@ from vyos.configdict import dict_merge from vyos.ifconfig import Section from vyos.ifconfig import Interface from vyos.template import render +from vyos.util import call from vyos.util import cmd from vyos.validate import is_addr_assigned from vyos.xml import defaults @@ -35,6 +36,8 @@ from vyos import airbag airbag.enable() uacctd_conf_path = '/run/pmacct/uacctd.conf' +systemd_service = 'uacctd.service' +systemd_override = f'/etc/systemd/system/{systemd_service}.d/override.conf' nftables_nflog_table = 'raw' nftables_nflog_chain = 'VYOS_CT_PREROUTING_HOOK' egress_nftables_nflog_table = 'inet mangle' @@ -236,7 +239,10 @@ def generate(flow_config): if not flow_config: return None - render(uacctd_conf_path, 'netflow/uacctd.conf.tmpl', flow_config) + render(uacctd_conf_path, 'pmacct/uacctd.conf.tmpl', flow_config) + render(systemd_override, 'pmacct/override.conf.tmpl', flow_config) + # Reload systemd manager configuration + call('systemctl daemon-reload') def apply(flow_config): action = 'restart' @@ -246,13 +252,13 @@ def apply(flow_config): _nftables_config([], 'egress') # Stop flow-accounting daemon and remove configuration file - cmd('systemctl stop uacctd.service') + call(f'systemctl stop {systemd_service}') if os.path.exists(uacctd_conf_path): os.unlink(uacctd_conf_path) return # Start/reload flow-accounting daemon - cmd(f'systemctl restart uacctd.service') + call(f'systemctl restart {systemd_service}') # configure nftables rules for defined interfaces if 'interface' in flow_config: diff --git a/src/etc/systemd/system/uacctd.service.d/override.conf b/src/etc/systemd/system/uacctd.service.d/override.conf deleted file mode 100644 index 38bcce515..000000000 --- a/src/etc/systemd/system/uacctd.service.d/override.conf +++ /dev/null @@ -1,14 +0,0 @@ -[Unit] -After= -After=vyos-router.service -ConditionPathExists= -ConditionPathExists=/run/pmacct/uacctd.conf - -[Service] -EnvironmentFile= -ExecStart= -ExecStart=/usr/sbin/uacctd -f /run/pmacct/uacctd.conf -WorkingDirectory= -WorkingDirectory=/run/pmacct -PIDFile= -PIDFile=/run/pmacct/uacctd.pid -- cgit v1.2.3 From bb78f3a9ad28f62896a536719783011794deb64c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 3 Mar 2022 20:23:09 +0100 Subject: static: T4283: support "reject" routes - emit an ICMP unreachable when matched --- data/templates/frr/static_routes_macro.j2 | 3 ++ .../include/static/static-route-reject.xml.i | 12 +++++ .../include/static/static-route.xml.i | 1 + .../include/static/static-route6.xml.i | 1 + smoketest/scripts/cli/test_protocols_static.py | 57 +++++++++++++++++++--- src/conf_mode/protocols_static.py | 4 ++ 6 files changed, 70 insertions(+), 8 deletions(-) create mode 100644 interface-definitions/include/static/static-route-reject.xml.i (limited to 'src') diff --git a/data/templates/frr/static_routes_macro.j2 b/data/templates/frr/static_routes_macro.j2 index 86c7470ca..8359357b7 100644 --- a/data/templates/frr/static_routes_macro.j2 +++ b/data/templates/frr/static_routes_macro.j2 @@ -2,6 +2,9 @@ {% if prefix_config.blackhole is defined %} {{ ip_ipv6 }} route {{ prefix }} blackhole {{ prefix_config.blackhole.distance if prefix_config.blackhole.distance is defined }} {{ 'tag ' + prefix_config.blackhole.tag if prefix_config.blackhole.tag is defined }} {{ 'table ' + table if table is defined and table is not none }} {% endif %} +{% if prefix_config.reject is defined %} +{{ ip_ipv6 }} route {{ prefix }} reject {{ prefix_config.reject.distance if prefix_config.reject.distance is defined }} {{ 'tag ' + prefix_config.reject.tag if prefix_config.reject.tag is defined }} {{ 'table ' + table if table is defined and table is not none }} +{% endif %} {% if prefix_config.dhcp_interface is defined and prefix_config.dhcp_interface is not none %} {% set next_hop = prefix_config.dhcp_interface | get_dhcp_router %} {% if next_hop is defined and next_hop is not none %} diff --git a/interface-definitions/include/static/static-route-reject.xml.i b/interface-definitions/include/static/static-route-reject.xml.i new file mode 100644 index 000000000..81d4f9afd --- /dev/null +++ b/interface-definitions/include/static/static-route-reject.xml.i @@ -0,0 +1,12 @@ + + + + Emit an ICMP unreachable when matched + + + #include + #include + + + + diff --git a/interface-definitions/include/static/static-route.xml.i b/interface-definitions/include/static/static-route.xml.i index 8433703a5..2de5dc58f 100644 --- a/interface-definitions/include/static/static-route.xml.i +++ b/interface-definitions/include/static/static-route.xml.i @@ -12,6 +12,7 @@ #include + #include #include diff --git a/interface-definitions/include/static/static-route6.xml.i b/interface-definitions/include/static/static-route6.xml.i index 124b2b062..35feef41c 100644 --- a/interface-definitions/include/static/static-route6.xml.i +++ b/interface-definitions/include/static/static-route6.xml.i @@ -12,6 +12,7 @@ #include + #include IPv6 gateway interface name diff --git a/smoketest/scripts/cli/test_protocols_static.py b/smoketest/scripts/cli/test_protocols_static.py index 4c4eb5a7c..3ef9c76d8 100755 --- a/smoketest/scripts/cli/test_protocols_static.py +++ b/smoketest/scripts/cli/test_protocols_static.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 @@ -52,9 +52,16 @@ routes = { }, 'blackhole' : { 'distance' : '90' }, }, - '100.64.0.0/10' : { + '100.64.0.0/16' : { 'blackhole' : { }, }, + '100.65.0.0/16' : { + 'reject' : { 'distance' : '10', 'tag' : '200' }, + }, + '100.66.0.0/16' : { + 'blackhole' : { }, + 'reject' : { 'distance' : '10', 'tag' : '200' }, + }, '2001:db8:100::/40' : { 'next_hop' : { '2001:db8::1' : { 'distance' : '10' }, @@ -74,6 +81,9 @@ routes = { }, 'blackhole' : { 'distance' : '250', 'tag' : '500' }, }, + '2001:db8:300::/40' : { + 'reject' : { 'distance' : '250', 'tag' : '500' }, + }, '2001:db8::/32' : { 'blackhole' : { 'distance' : '200', 'tag' : '600' }, }, @@ -82,9 +92,15 @@ routes = { tables = ['80', '81', '82'] class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): - def setUp(self): - # This is our "target" VRF when leaking routes: - self.cli_set(['vrf', 'name', 'black', 'table', '43210']) + @classmethod + def setUpClass(cls): + super(cls, cls).setUpClass() + cls.cli_set(cls, ['vrf', 'name', 'black', 'table', '43210']) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['vrf']) + super(cls, cls).tearDownClass() def tearDown(self): for route, route_config in routes.items(): @@ -135,6 +151,20 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): if 'tag' in route_config['blackhole']: self.cli_set(base + ['blackhole', 'tag', route_config['blackhole']['tag']]) + if 'reject' in route_config: + self.cli_set(base + ['reject']) + if 'distance' in route_config['reject']: + self.cli_set(base + ['reject', 'distance', route_config['reject']['distance']]) + if 'tag' in route_config['reject']: + self.cli_set(base + ['reject', 'tag', route_config['reject']['tag']]) + + if {'blackhole', 'reject'} <= set(route_config): + # Can not use blackhole and reject at the same time + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base + ['blackhole']) + self.cli_delete(base + ['reject']) + # commit changes self.cli_commit() @@ -177,6 +207,11 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): else: self.assertIn(tmp, frrconfig) + if {'blackhole', 'reject'} <= set(route_config): + # Can not use blackhole and reject at the same time + # Config error validated above - skip this route + continue + if 'blackhole' in route_config: tmp = f'{ip_ipv6} route {route} blackhole' if 'tag' in route_config['blackhole']: @@ -186,6 +221,15 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): self.assertIn(tmp, frrconfig) + if 'reject' in route_config: + tmp = f'{ip_ipv6} route {route} reject' + if 'tag' in route_config['reject']: + tmp += ' tag ' + route_config['reject']['tag'] + if 'distance' in route_config['reject']: + tmp += ' ' + route_config['reject']['distance'] + + self.assertIn(tmp, frrconfig) + def test_02_static_table(self): for table in tables: for route, route_config in routes.items(): @@ -389,11 +433,8 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): self.assertIn(tmp, frrconfig) - self.cli_delete(['vrf']) - def test_04_static_zebra_route_map(self): # Implemented because of T3328 - self.debug = True route_map = 'foo-static-in' self.cli_set(['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit']) diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py index c1e427b16..f0ec48de4 100755 --- a/src/conf_mode/protocols_static.py +++ b/src/conf_mode/protocols_static.py @@ -82,6 +82,10 @@ def verify(static): for interface, interface_config in prefix_options[type].items(): verify_vrf(interface_config) + if {'blackhole', 'reject'} <= set(prefix_options): + raise ConfigError(f'Can not use both blackhole and reject for '\ + 'prefix "{prefix}"!') + return None def generate(static): -- cgit v1.2.3 From c06861440cd21ff7c668b35ed1039f5fac4101b9 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 4 Mar 2022 20:08:27 +0100 Subject: op-mode: lldp: T3999: bugfix cap' referenced before assignment --- src/op_mode/lldp_op.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/op_mode/lldp_op.py b/src/op_mode/lldp_op.py index b9ebc991a..17f6bf552 100755 --- a/src/op_mode/lldp_op.py +++ b/src/op_mode/lldp_op.py @@ -54,6 +54,7 @@ def parse_data(data, interface): for local_if, values in neighbor.items(): if interface is not None and local_if != interface: continue + cap = '' for chassis, c_value in values.get('chassis', {}).items(): # bail out early if no capabilities found if 'capability' not in c_value: @@ -62,7 +63,6 @@ def parse_data(data, interface): if isinstance(capabilities, dict): capabilities = [capabilities] - cap = '' for capability in capabilities: if capability['enabled']: if capability['type'] == 'Router': -- cgit v1.2.3 From 2c94c3ec72a559de405b29b4399250db3085717e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 5 Mar 2022 09:40:32 +0100 Subject: conntrackd: T4259: prevent startup of multiple daemon instances --- debian/vyos-1x.preinst | 2 +- src/etc/logrotate.d/conntrackd | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 src/etc/logrotate.d/conntrackd (limited to 'src') diff --git a/debian/vyos-1x.preinst b/debian/vyos-1x.preinst index 45440bf64..71750b3a1 100644 --- a/debian/vyos-1x.preinst +++ b/debian/vyos-1x.preinst @@ -1,4 +1,4 @@ dpkg-divert --package vyos-1x --add --rename /etc/securetty dpkg-divert --package vyos-1x --add --rename /etc/security/capability.conf dpkg-divert --package vyos-1x --add --rename /lib/systemd/system/lcdproc.service - +dpkg-divert --package vyos-1x --add --rename /etc/logrotate.d/conntrackd diff --git a/src/etc/logrotate.d/conntrackd b/src/etc/logrotate.d/conntrackd new file mode 100644 index 000000000..b0b09dec1 --- /dev/null +++ b/src/etc/logrotate.d/conntrackd @@ -0,0 +1,9 @@ +/var/log/conntrackd-stats.log { + weekly + rotate 2 + missingok + + postrotate + systemctl restart conntrackd.service > /dev/null + endscript +} -- cgit v1.2.3 From aa8080d316dbeb4d26bf67f6d67efeda43b2bc07 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 5 Mar 2022 20:51:13 +0100 Subject: conntrackd: T4259: fix daemon configuration path --- debian/vyos-1x.postinst | 9 +++++++++ src/helpers/vyos-vrrp-conntracksync.sh | 8 +++++--- 2 files changed, 14 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst index 4b4c4c13e..ffd631b1f 100644 --- a/debian/vyos-1x.postinst +++ b/debian/vyos-1x.postinst @@ -83,3 +83,12 @@ fi if [ -L /etc/init.d/README ]; then rm -f /etc/init.d/README fi + +# Remove unwanted daemon files from /etc +# conntackd +DELETE="/etc/logrotate.d/conntrackd.distrib /etc/init.d/conntrackd /etc/default/conntrackd" +for file in $DELETE; do + if [ -f ${file} ]; then + rm -f ${file} + fi +done diff --git a/src/helpers/vyos-vrrp-conntracksync.sh b/src/helpers/vyos-vrrp-conntracksync.sh index 4501aa63e..0cc718938 100755 --- a/src/helpers/vyos-vrrp-conntracksync.sh +++ b/src/helpers/vyos-vrrp-conntracksync.sh @@ -14,12 +14,10 @@ # Modified by : Mohit Mehta # Slight modifications were made to this script for running with Vyatta # The original script came from 0.9.14 debian conntrack-tools package -# -# CONNTRACKD_BIN=/usr/sbin/conntrackd CONNTRACKD_LOCK=/var/lock/conntrack.lock -CONNTRACKD_CONFIG=/etc/conntrackd/conntrackd.conf +CONNTRACKD_CONFIG=/run/conntrackd/conntrackd.conf FACILITY=daemon LEVEL=notice TAG=conntrack-tools @@ -29,6 +27,10 @@ FAILOVER_STATE="/var/run/vyatta-conntrackd-failover-state" $LOGCMD "vyatta-vrrp-conntracksync invoked at `date`" +if ! systemctl is-active --quiet conntrackd.service; then + echo "conntrackd service not running" + exit 1 +fi if [ ! -e $FAILOVER_STATE ]; then mkdir -p /var/run -- cgit v1.2.3 From ebb524702e1cd60a74b00727b7bd24d375648c78 Mon Sep 17 00:00:00 2001 From: zsdc Date: Mon, 7 Mar 2022 18:20:53 +0200 Subject: logrotate: T4250: Fixed logrotate config generation * Removed `/var/log/auth.log` and `/var/log/messages` from `/etc/logrotate.d/rsyslog`, because they conflict with VyOS-controlled items what leads to service error. * Removed generation config file for `/var/log/messages` from `system-syslog.py` - this should be done from `syslom logs` now. * Generate each logfile from `system syslog file` to a dedicated logrotate config file. * Fixed logrotate config file names in `/etc/rsyslog.d/vyos-rsyslog.conf`. * Added default logrotate settins for `/var/log/messages` --- data/templates/syslog/logrotate.tmpl | 9 ++++----- debian/vyos-1x.postinst | 4 ++++ src/conf_mode/system-syslog.py | 14 +++++++++++--- src/etc/logrotate.d/vyos-rsyslog | 12 ++++++++++++ 4 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 src/etc/logrotate.d/vyos-rsyslog (limited to 'src') diff --git a/data/templates/syslog/logrotate.tmpl b/data/templates/syslog/logrotate.tmpl index f758265e4..c1b951e8b 100644 --- a/data/templates/syslog/logrotate.tmpl +++ b/data/templates/syslog/logrotate.tmpl @@ -1,12 +1,11 @@ -{% for file in files %} -{{files[file]['log-file']}} { +{{ config_render['log-file'] }} { missingok notifempty create - rotate {{files[file]['max-files']}} - size={{files[file]['max-size']//1024}}k + rotate {{ config_render['max-files'] }} + size={{ config_render['max-size'] // 1024 }}k postrotate invoke-rc.d rsyslog rotate > /dev/null endscript } -{% endfor %} + diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst index 1a4c830cc..1ca6687a3 100644 --- a/debian/vyos-1x.postinst +++ b/debian/vyos-1x.postinst @@ -93,3 +93,7 @@ for file in $DELETE; do rm -f ${file} fi done + +# Remove logrotate items controlled via CLI and VyOS defaults +sed -i '/^\/var\/log\/messages$/d' /etc/logrotate.d/rsyslog +sed -i '/^\/var\/log\/auth.log$/d' /etc/logrotate.d/rsyslog diff --git a/src/conf_mode/system-syslog.py b/src/conf_mode/system-syslog.py index 3d8a51cd8..309b4bdb0 100755 --- a/src/conf_mode/system-syslog.py +++ b/src/conf_mode/system-syslog.py @@ -17,6 +17,7 @@ import os import re +from pathlib import Path from sys import exit from vyos.config import Config @@ -89,7 +90,7 @@ def get_config(config=None): filename: { 'log-file': '/var/log/user/' + filename, 'max-files': '5', - 'action-on-max-size': '/usr/sbin/logrotate /etc/logrotate.d/' + filename, + 'action-on-max-size': '/usr/sbin/logrotate /etc/logrotate.d/vyos-rsyslog-generated-' + filename, 'selectors': '*.err', 'max-size': 262144 } @@ -205,10 +206,17 @@ def generate(c): conf = '/etc/rsyslog.d/vyos-rsyslog.conf' render(conf, 'syslog/rsyslog.conf.tmpl', c) + # cleanup current logrotate config files + logrotate_files = Path('/etc/logrotate.d/').glob('vyos-rsyslog-generated-*') + for file in logrotate_files: + file.unlink() + # eventually write for each file its own logrotate file, since size is # defined it shouldn't matter - conf = '/etc/logrotate.d/vyos-rsyslog' - render(conf, 'syslog/logrotate.tmpl', c) + for filename, fileconfig in c.get('files', {}).items(): + if fileconfig['log-file'].startswith('/var/log/user/'): + conf = '/etc/logrotate.d/vyos-rsyslog-generated-' + filename + render(conf, 'syslog/logrotate.tmpl', { 'config_render': fileconfig }) def verify(c): diff --git a/src/etc/logrotate.d/vyos-rsyslog b/src/etc/logrotate.d/vyos-rsyslog new file mode 100644 index 000000000..3c087b94e --- /dev/null +++ b/src/etc/logrotate.d/vyos-rsyslog @@ -0,0 +1,12 @@ +/var/log/messages { + create + missingok + nomail + notifempty + rotate 10 + size 1M + postrotate + # inform rsyslog service about rotation + /usr/lib/rsyslog/rsyslog-rotate + endscript +} -- cgit v1.2.3 From 534f677d36285863decb2cdff179687b4fd690cb Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Tue, 8 Mar 2022 11:46:18 -0600 Subject: component_version: T4291: consolidate read/write functions --- python/vyos/component_version.py | 174 ++++++++++++++++++++++++ python/vyos/component_versions.py | 57 -------- python/vyos/formatversions.py | 109 --------------- python/vyos/migrator.py | 26 ++-- python/vyos/systemversions.py | 46 ------- smoketest/scripts/cli/test_component_version.py | 6 +- src/helpers/system-versions-foot.py | 19 +-- 7 files changed, 189 insertions(+), 248 deletions(-) create mode 100644 python/vyos/component_version.py delete mode 100644 python/vyos/component_versions.py delete mode 100644 python/vyos/formatversions.py delete mode 100644 python/vyos/systemversions.py (limited to 'src') diff --git a/python/vyos/component_version.py b/python/vyos/component_version.py new file mode 100644 index 000000000..b1554828d --- /dev/null +++ b/python/vyos/component_version.py @@ -0,0 +1,174 @@ +# Copyright 2022 VyOS maintainers and contributors +# +# 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 . + +""" +Functions for reading/writing component versions. + +The config file version string has the following form: + +VyOS 1.3/1.4: + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.3.0 + +VyOS 1.2: + +/* Warning: Do not remove the following line. */ +/* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pppoe-server@2:pptp@1:qos@1:quagga@7:snmp@1:ssh@1:system@10:vrrp@2:wanloadbalance@3:webgui@1:webproxy@2:zone-policy@1" === */ +/* Release version: 1.2.8 */ + +""" + +import os +import re +import sys +import fileinput +from vyos.xml import component_version +from vyos.version import get_version + +def from_string(string_line, vintage='vyos'): + """ + Get component version dictionary from string. + Return empty dictionary if string contains no config information + or raise error if component version string malformed. + """ + version_dict = {} + + if vintage == 'vyos': + if re.match(r'// vyos-config-version:.+', string_line): + if not re.match(r'// vyos-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s*', string_line): + raise ValueError(f"malformed configuration string: {string_line}") + + for pair in re.findall(r'([\w,-]+)@(\d+)', string_line): + version_dict[pair[0]] = int(pair[1]) + + elif vintage == 'vyatta': + if re.match(r'/\* === vyatta-config-version:.+=== \*/$', string_line): + if not re.match(r'/\* === vyatta-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s+=== \*/$', string_line): + raise ValueError(f"malformed configuration string: {string_line}") + + for pair in re.findall(r'([\w,-]+)@(\d+)', string_line): + version_dict[pair[0]] = int(pair[1]) + else: + raise ValueError("Unknown config string vintage") + + return version_dict + +def from_file(config_file_name='/opt/vyatta/etc/config/config.boot', + vintage='vyos'): + """ + Get component version dictionary parsing config file line by line + """ + f = open(config_file_name, 'r') + for line_in_config in f: + version_dict = from_string(line_in_config, vintage=vintage) + if version_dict: + return version_dict + + # no version information + return {} + +def from_system(): + """ + Get system component version dict. + """ + return component_version() + +def legacy_from_system(): + """ + legacy function; imported as-is. + + Get component versions from running system; critical failure if + unable to read migration directory. + """ + import vyos.defaults + + system_versions = {} + + try: + version_info = os.listdir(vyos.defaults.directories['current']) + except OSError as err: + print("OS error: {}".format(err)) + sys.exit(1) + + for info in version_info: + if re.match(r'[\w,-]+@\d+', info): + pair = info.split('@') + system_versions[pair[0]] = int(pair[1]) + + return system_versions + +def format_string(ver: dict) -> str: + """ + Version dict to string. + """ + keys = list(ver) + keys.sort() + l = [] + for k in keys: + v = ver[k] + l.append(f'{k}@{v}') + sep = ':' + return sep.join(l) + +def system_footer(vintage='vyos') -> str: + """ + Version footer as string. + """ + ver_str = format_string(from_system()) + release = get_version() + if vintage == 'vyos': + ret_str = (f'// Warning: Do not remove the following line.\n' + + f'// vyos-config-version: "{ver_str}"\n' + + f'// Release version: {release}\n') + elif vintage == 'vyatta': + ret_str = (f'/* Warning: Do not remove the following line. */\n' + + f'/* === vyatta-config-version: "{ver_str}" === */\n' + + f'/* Release version: {release} */\n') + else: + raise ValueError("Unknown config string vintage") + + return ret_str + +def write_footer(file_name, vintage='vyos'): + """ + Write version footer to file. + """ + footer = system_footer(vintage=vintage) + if file_name: + with open(file_name, 'a') as f: + f.write(footer) + else: + sys.stdout.write(footer) + +def remove_footer(file_name): + """ + Remove old version footer. + """ + for line in fileinput.input(file_name, inplace=True): + if re.match(r'/\* Warning:.+ \*/$', line): + continue + if re.match(r'/\* === vyatta-config-version:.+=== \*/$', line): + continue + if re.match(r'/\* Release version:.+ \*/$', line): + continue + if re.match('// vyos-config-version:.+', line): + continue + if re.match('// Warning:.+', line): + continue + if re.match('// Release version:.+', line): + continue + sys.stdout.write(line) diff --git a/python/vyos/component_versions.py b/python/vyos/component_versions.py deleted file mode 100644 index 90b458aae..000000000 --- a/python/vyos/component_versions.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2017 VyOS maintainers and contributors -# -# 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 . - -""" -The version data looks like: - -/* Warning: Do not remove the following line. */ -/* === vyatta-config-version: -"cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@1:dhcp-server@4:firewall@5:ipsec@4:nat@4:qos@1:quagga@2:system@8:vrrp@1:wanloadbalance@3:webgui@1:webproxy@1:zone-policy@1" -=== */ -/* Release version: 1.2.0-rolling+201806131737 */ -""" - -import re - -def get_component_version(string_line): - """ - Get component version dictionary from string - return empty dictionary if string contains no config information - or raise error if component version string malformed - """ - return_value = {} - if re.match(r'/\* === vyatta-config-version:.+=== \*/$', string_line): - - if not re.match(r'/\* === vyatta-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s+=== \*/$', string_line): - raise ValueError("malformed configuration string: " + str(string_line)) - - for pair in re.findall(r'([\w,-]+)@(\d+)', string_line): - if pair[0] in return_value.keys(): - raise ValueError("duplicate unit name: \"" + str(pair[0]) + "\" in string: \"" + string_line + "\"") - return_value[pair[0]] = int(pair[1]) - - return return_value - - -def get_component_versions_from_file(config_file_name='/opt/vyatta/etc/config/config.boot'): - """ - Get component version dictionary parsing config file line by line - """ - f = open(config_file_name, 'r') - for line_in_config in f: - component_version = get_component_version(line_in_config) - if component_version: - return component_version - raise ValueError("no config string in file:", config_file_name) diff --git a/python/vyos/formatversions.py b/python/vyos/formatversions.py deleted file mode 100644 index 29117a5d3..000000000 --- a/python/vyos/formatversions.py +++ /dev/null @@ -1,109 +0,0 @@ -# Copyright 2019 VyOS maintainers and contributors -# -# 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 . - -import sys -import os -import re -import fileinput - -def read_vyatta_versions(config_file): - config_file_versions = {} - - with open(config_file, 'r') as config_file_handle: - for config_line in config_file_handle: - if re.match(r'/\* === vyatta-config-version:.+=== \*/$', config_line): - if not re.match(r'/\* === vyatta-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s+=== \*/$', config_line): - raise ValueError("malformed configuration string: " - "{}".format(config_line)) - - for pair in re.findall(r'([\w,-]+)@(\d+)', config_line): - config_file_versions[pair[0]] = int(pair[1]) - - - return config_file_versions - -def read_vyos_versions(config_file): - config_file_versions = {} - - with open(config_file, 'r') as config_file_handle: - for config_line in config_file_handle: - if re.match(r'// vyos-config-version:.+', config_line): - if not re.match(r'// vyos-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s*', config_line): - raise ValueError("malformed configuration string: " - "{}".format(config_line)) - - for pair in re.findall(r'([\w,-]+)@(\d+)', config_line): - config_file_versions[pair[0]] = int(pair[1]) - - return config_file_versions - -def remove_versions(config_file): - """ - Remove old version string. - """ - for line in fileinput.input(config_file, inplace=True): - if re.match(r'/\* Warning:.+ \*/$', line): - continue - if re.match(r'/\* === vyatta-config-version:.+=== \*/$', line): - continue - if re.match(r'/\* Release version:.+ \*/$', line): - continue - if re.match('// vyos-config-version:.+', line): - continue - if re.match('// Warning:.+', line): - continue - if re.match('// Release version:.+', line): - continue - sys.stdout.write(line) - -def format_versions_string(config_versions): - cfg_keys = list(config_versions.keys()) - cfg_keys.sort() - - component_version_strings = [] - - for key in cfg_keys: - cfg_vers = config_versions[key] - component_version_strings.append('{}@{}'.format(key, cfg_vers)) - - separator = ":" - component_version_string = separator.join(component_version_strings) - - return component_version_string - -def write_vyatta_versions_foot(config_file, component_version_string, - os_version_string): - if config_file: - with open(config_file, 'a') as config_file_handle: - config_file_handle.write('/* Warning: Do not remove the following line. */\n') - config_file_handle.write('/* === vyatta-config-version: "{}" === */\n'.format(component_version_string)) - config_file_handle.write('/* Release version: {} */\n'.format(os_version_string)) - else: - sys.stdout.write('/* Warning: Do not remove the following line. */\n') - sys.stdout.write('/* === vyatta-config-version: "{}" === */\n'.format(component_version_string)) - sys.stdout.write('/* Release version: {} */\n'.format(os_version_string)) - -def write_vyos_versions_foot(config_file, component_version_string, - os_version_string): - if config_file: - with open(config_file, 'a') as config_file_handle: - config_file_handle.write('// Warning: Do not remove the following line.\n') - config_file_handle.write('// vyos-config-version: "{}"\n'.format(component_version_string)) - config_file_handle.write('// Release version: {}\n'.format(os_version_string)) - else: - sys.stdout.write('// Warning: Do not remove the following line.\n') - sys.stdout.write('// vyos-config-version: "{}"\n'.format(component_version_string)) - sys.stdout.write('// Release version: {}\n'.format(os_version_string)) - diff --git a/python/vyos/migrator.py b/python/vyos/migrator.py index a2e0daabd..266a2e58e 100644 --- a/python/vyos/migrator.py +++ b/python/vyos/migrator.py @@ -17,10 +17,8 @@ import sys import os import json import subprocess -import vyos.version import vyos.defaults -import vyos.systemversions as systemversions -import vyos.formatversions as formatversions +import vyos.component_version as component_version class MigratorError(Exception): pass @@ -42,13 +40,13 @@ class Migrator(object): cfg_file = self._config_file component_versions = {} - cfg_versions = formatversions.read_vyatta_versions(cfg_file) + cfg_versions = component_version.from_file(cfg_file, vintage='vyatta') if cfg_versions: self._config_file_vintage = 'vyatta' component_versions = cfg_versions - cfg_versions = formatversions.read_vyos_versions(cfg_file) + cfg_versions = component_version.from_file(cfg_file, vintage='vyos') if cfg_versions: self._config_file_vintage = 'vyos' @@ -152,19 +150,11 @@ class Migrator(object): """ Write new versions string. """ - versions_string = formatversions.format_versions_string(cfg_versions) - - os_version_string = vyos.version.get_version() - if self._config_file_vintage == 'vyatta': - formatversions.write_vyatta_versions_foot(self._config_file, - versions_string, - os_version_string) + component_version.write_footer(self._config_file, vintage='vyatta') if self._config_file_vintage == 'vyos': - formatversions.write_vyos_versions_foot(self._config_file, - versions_string, - os_version_string) + component_version.write_footer(self._config_file, vintage='vyos') def save_json_record(self, component_versions: dict): """ @@ -195,7 +185,7 @@ class Migrator(object): # This will force calling all migration scripts: cfg_versions = {} - sys_versions = systemversions.get_system_component_version() + sys_versions = component_version.from_system() # save system component versions in json file for easy reference self.save_json_record(sys_versions) @@ -211,7 +201,7 @@ class Migrator(object): if not self._changed: return - formatversions.remove_versions(cfg_file) + component_version.remove_footer(cfg_file) self.write_config_file_versions(rev_versions) @@ -232,7 +222,7 @@ class VirtualMigrator(Migrator): if not self._changed: return - formatversions.remove_versions(cfg_file) + component_version.remove_footer(cfg_file) self.write_config_file_versions(cfg_versions) diff --git a/python/vyos/systemversions.py b/python/vyos/systemversions.py deleted file mode 100644 index f2da76d4f..000000000 --- a/python/vyos/systemversions.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright 2019 VyOS maintainers and contributors -# -# 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 . - -import os -import re -import sys -import vyos.defaults -from vyos.xml import component_version - -# legacy version, reading from the file names in -# /opt/vyatta/etc/config-migrate/current -def get_system_versions(): - """ - Get component versions from running system; critical failure if - unable to read migration directory. - """ - system_versions = {} - - try: - version_info = os.listdir(vyos.defaults.directories['current']) - except OSError as err: - print("OS error: {}".format(err)) - sys.exit(1) - - for info in version_info: - if re.match(r'[\w,-]+@\d+', info): - pair = info.split('@') - system_versions[pair[0]] = int(pair[1]) - - return system_versions - -# read from xml cache -def get_system_component_version(): - return component_version() diff --git a/smoketest/scripts/cli/test_component_version.py b/smoketest/scripts/cli/test_component_version.py index 777379bdd..21cc1c761 100755 --- a/smoketest/scripts/cli/test_component_version.py +++ b/smoketest/scripts/cli/test_component_version.py @@ -16,7 +16,7 @@ import unittest -from vyos.systemversions import get_system_versions, get_system_component_version +from vyos.component_version import legacy_from_system, from_system # After T3474, component versions should be updated in the files in # vyos-1x/interface-definitions/include/version/ @@ -24,8 +24,8 @@ from vyos.systemversions import get_system_versions, get_system_component_versio # that in the xml cache. class TestComponentVersion(unittest.TestCase): def setUp(self): - self.legacy_d = get_system_versions() - self.xml_d = get_system_component_version() + self.legacy_d = legacy_from_system() + self.xml_d = from_system() def test_component_version(self): self.assertTrue(set(self.legacy_d).issubset(set(self.xml_d))) diff --git a/src/helpers/system-versions-foot.py b/src/helpers/system-versions-foot.py index 2aa687221..b44408542 100755 --- a/src/helpers/system-versions-foot.py +++ b/src/helpers/system-versions-foot.py @@ -16,24 +16,13 @@ # along with this library. If not, see . import sys -import vyos.formatversions as formatversions -import vyos.systemversions as systemversions import vyos.defaults -import vyos.version - -sys_versions = systemversions.get_system_component_version() - -component_string = formatversions.format_versions_string(sys_versions) - -os_version_string = vyos.version.get_version() +from vyos.component_version import write_footer sys.stdout.write("\n\n") if vyos.defaults.cfg_vintage == 'vyos': - formatversions.write_vyos_versions_foot(None, component_string, - os_version_string) + write_footer(None, vintage='vyos') elif vyos.defaults.cfg_vintage == 'vyatta': - formatversions.write_vyatta_versions_foot(None, component_string, - os_version_string) + write_footer(None, vintage='vyatta') else: - formatversions.write_vyatta_versions_foot(None, component_string, - os_version_string) + write_footer(None, vintage='vyatta') -- cgit v1.2.3 From c4d389488970c8510200cac96a67182e9333b891 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Tue, 8 Mar 2022 11:48:29 -0600 Subject: save-config: T4292: rewrite vyatta-save-config.pl to Python --- python/vyos/config.py | 5 ++++ python/vyos/configsession.py | 2 +- src/helpers/vyos-save-config.py | 54 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100755 src/helpers/vyos-save-config.py (limited to 'src') diff --git a/python/vyos/config.py b/python/vyos/config.py index a5c1ad122..829167820 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -93,6 +93,11 @@ class Config(object): (self._running_config, self._session_config) = self._config_source.get_configtree_tuple() + def get_config_tree(self, effective=False): + if effective: + return self._running_config + return self._session_config + def _make_path(self, path): # Backwards-compatibility stuff: original implementation used string paths # libvyosconfig paths are lists, but since node names cannot contain whitespace, diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index f28ad09c5..a7eda6e61 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -28,7 +28,7 @@ DISCARD = '/opt/vyatta/sbin/my_discard' SHOW_CONFIG = ['/bin/cli-shell-api', 'showConfig'] LOAD_CONFIG = ['/bin/cli-shell-api', 'loadFile'] MIGRATE_LOAD_CONFIG = ['/usr/libexec/vyos/vyos-load-config.py'] -SAVE_CONFIG = ['/opt/vyatta/sbin/vyatta-save-config.pl'] +SAVE_CONFIG = ['/usr/libexec/vyos/vyos-save-config.py'] INSTALL_IMAGE = ['/opt/vyatta/sbin/install-image', '--url'] REMOVE_IMAGE = ['/opt/vyatta/bin/vyatta-boot-image.pl', '--del'] GENERATE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'generate'] diff --git a/src/helpers/vyos-save-config.py b/src/helpers/vyos-save-config.py new file mode 100755 index 000000000..628d510dd --- /dev/null +++ b/src/helpers/vyos-save-config.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# +import os +import re +import sys +from tempfile import NamedTemporaryFile + +from vyos.config import Config +from vyos.remote import urlc +from vyos.component_version import system_footer + +DEFAULT_CONFIG_FILE = '/opt/vyatta/etc/config/config.boot' +remote_save = None + +if len(sys.argv) > 1: + save_file = sys.argv[1] +else: + save_file = DEFAULT_CONFIG_FILE + +if re.match(r'\w+:/', save_file): + try: + remote_save = urlc(save_file) + except ValueError as e: + sys.exit(e) + +config = Config() +ct = config.get_config_tree(effective=True) + +write_file = save_file if remote_save is None else NamedTemporaryFile(delete=False).name +with open(write_file, 'w') as f: + f.write(ct.to_string()) + f.write("\n") + f.write(system_footer()) + +if remote_save is not None: + try: + remote_save.upload(write_file) + finally: + os.remove(write_file) -- cgit v1.2.3 From 2a4b45ba7fa4dabf7e592f499cfb06a7ae38cdea Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 9 Mar 2022 11:00:10 -0600 Subject: load-config: T4295: use config_tree instead of legacy loadFile --- src/helpers/vyos-load-config.py | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/helpers/vyos-load-config.py b/src/helpers/vyos-load-config.py index e579e81b2..08f28b24e 100755 --- a/src/helpers/vyos-load-config.py +++ b/src/helpers/vyos-load-config.py @@ -29,15 +29,40 @@ import gzip import tempfile import vyos.defaults import vyos.remote -from vyos.configsource import ConfigSourceSession, VyOSError +from vyos.configsource import ConfigSourceSession +from vyos.configtree import ConfigTree, DiffTree from vyos.migrator import Migrator, VirtualMigrator, MigratorError +from vyos.util import cmd, DEVNULL class LoadConfig(ConfigSourceSession): - """A subclass for calling 'loadFile'. + """A subclass for loading a config file. This does not belong in configsource.py, and only has a single caller. """ - def load_config(self, path): - return self._run(['/bin/cli-shell-api','loadFile',path]) + def load_config(self, file_path): + try: + with open(file_path) as f: + config_file = f.read() + load_ct = ConfigTree(config_file) + except (OSError, ValueError) as e: + print(e) + return + + eff_ct, _ = self.get_configtree_tuple() + diff = DiffTree(eff_ct, load_ct) + commands = diff.to_commands() + # on an empty set of 'add' or 'delete' commands, to_commands + # returns '\n'; prune below + command_list = commands.splitlines() + command_list = [c for c in command_list if c] + + if not command_list: + return + for op in command_list: + try: + cmd(f'/opt/vyatta/sbin/my_{op}', shell=True, stderr=DEVNULL) + except OSError as e: + print(e) + return file_name = sys.argv[1] if len(sys.argv) > 1 else 'config.boot' configdir = vyos.defaults.directories['config'] @@ -93,10 +118,7 @@ with tempfile.NamedTemporaryFile() as fp: except MigratorError as err: sys.exit('{}'.format(err)) - try: - config.load_config(fp.name) - except VyOSError as err: - sys.exit('{}'.format(err)) + config.load_config(fp.name) if config.session_changed(): print("Load complete. Use 'commit' to make changes effective.") -- cgit v1.2.3 From fde48b4d303b471f8c56b81bc7fcef0a028bb60a Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Thu, 10 Mar 2022 09:34:00 -0600 Subject: Revert "load-config: T4295: use config_tree instead of legacy loadFile" This reverts commit 2a4b45ba7fa4dabf7e592f499cfb06a7ae38cdea. Revert while investigating failure in vyos-configtest. --- src/helpers/vyos-load-config.py | 38 ++++++++------------------------------ 1 file changed, 8 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/src/helpers/vyos-load-config.py b/src/helpers/vyos-load-config.py index 08f28b24e..e579e81b2 100755 --- a/src/helpers/vyos-load-config.py +++ b/src/helpers/vyos-load-config.py @@ -29,40 +29,15 @@ import gzip import tempfile import vyos.defaults import vyos.remote -from vyos.configsource import ConfigSourceSession -from vyos.configtree import ConfigTree, DiffTree +from vyos.configsource import ConfigSourceSession, VyOSError from vyos.migrator import Migrator, VirtualMigrator, MigratorError -from vyos.util import cmd, DEVNULL class LoadConfig(ConfigSourceSession): - """A subclass for loading a config file. + """A subclass for calling 'loadFile'. This does not belong in configsource.py, and only has a single caller. """ - def load_config(self, file_path): - try: - with open(file_path) as f: - config_file = f.read() - load_ct = ConfigTree(config_file) - except (OSError, ValueError) as e: - print(e) - return - - eff_ct, _ = self.get_configtree_tuple() - diff = DiffTree(eff_ct, load_ct) - commands = diff.to_commands() - # on an empty set of 'add' or 'delete' commands, to_commands - # returns '\n'; prune below - command_list = commands.splitlines() - command_list = [c for c in command_list if c] - - if not command_list: - return - for op in command_list: - try: - cmd(f'/opt/vyatta/sbin/my_{op}', shell=True, stderr=DEVNULL) - except OSError as e: - print(e) - return + def load_config(self, path): + return self._run(['/bin/cli-shell-api','loadFile',path]) file_name = sys.argv[1] if len(sys.argv) > 1 else 'config.boot' configdir = vyos.defaults.directories['config'] @@ -118,7 +93,10 @@ with tempfile.NamedTemporaryFile() as fp: except MigratorError as err: sys.exit('{}'.format(err)) - config.load_config(fp.name) + try: + config.load_config(fp.name) + except VyOSError as err: + sys.exit('{}'.format(err)) if config.session_changed(): print("Load complete. Use 'commit' to make changes effective.") -- cgit v1.2.3 From ef4870e66f8c1cd6df5fba2abbfdde0eac152e0a Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Thu, 10 Mar 2022 10:07:19 -0600 Subject: Revert "save-config: T4292: rewrite vyatta-save-config.pl to Python" This reverts commit c4d389488970c8510200cac96a67182e9333b891. Revert while investigating failure in vyos-configtest. --- python/vyos/config.py | 5 ---- python/vyos/configsession.py | 2 +- src/helpers/vyos-save-config.py | 54 ----------------------------------------- 3 files changed, 1 insertion(+), 60 deletions(-) delete mode 100755 src/helpers/vyos-save-config.py (limited to 'src') diff --git a/python/vyos/config.py b/python/vyos/config.py index 829167820..a5c1ad122 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -93,11 +93,6 @@ class Config(object): (self._running_config, self._session_config) = self._config_source.get_configtree_tuple() - def get_config_tree(self, effective=False): - if effective: - return self._running_config - return self._session_config - def _make_path(self, path): # Backwards-compatibility stuff: original implementation used string paths # libvyosconfig paths are lists, but since node names cannot contain whitespace, diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index a7eda6e61..f28ad09c5 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -28,7 +28,7 @@ DISCARD = '/opt/vyatta/sbin/my_discard' SHOW_CONFIG = ['/bin/cli-shell-api', 'showConfig'] LOAD_CONFIG = ['/bin/cli-shell-api', 'loadFile'] MIGRATE_LOAD_CONFIG = ['/usr/libexec/vyos/vyos-load-config.py'] -SAVE_CONFIG = ['/usr/libexec/vyos/vyos-save-config.py'] +SAVE_CONFIG = ['/opt/vyatta/sbin/vyatta-save-config.pl'] INSTALL_IMAGE = ['/opt/vyatta/sbin/install-image', '--url'] REMOVE_IMAGE = ['/opt/vyatta/bin/vyatta-boot-image.pl', '--del'] GENERATE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'generate'] diff --git a/src/helpers/vyos-save-config.py b/src/helpers/vyos-save-config.py deleted file mode 100755 index 628d510dd..000000000 --- a/src/helpers/vyos-save-config.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/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 . -# -# -import os -import re -import sys -from tempfile import NamedTemporaryFile - -from vyos.config import Config -from vyos.remote import urlc -from vyos.component_version import system_footer - -DEFAULT_CONFIG_FILE = '/opt/vyatta/etc/config/config.boot' -remote_save = None - -if len(sys.argv) > 1: - save_file = sys.argv[1] -else: - save_file = DEFAULT_CONFIG_FILE - -if re.match(r'\w+:/', save_file): - try: - remote_save = urlc(save_file) - except ValueError as e: - sys.exit(e) - -config = Config() -ct = config.get_config_tree(effective=True) - -write_file = save_file if remote_save is None else NamedTemporaryFile(delete=False).name -with open(write_file, 'w') as f: - f.write(ct.to_string()) - f.write("\n") - f.write(system_footer()) - -if remote_save is not None: - try: - remote_save.upload(write_file) - finally: - os.remove(write_file) -- cgit v1.2.3 From c1e04cea0817db07b22a8bd8e6b2f2c0a1e682f4 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Thu, 10 Mar 2022 10:07:31 -0600 Subject: Revert "component_version: T4291: consolidate read/write functions" This reverts commit 534f677d36285863decb2cdff179687b4fd690cb. Revert while investigating failure in vyos-configtest. --- python/vyos/component_version.py | 174 ------------------------ python/vyos/component_versions.py | 57 ++++++++ python/vyos/formatversions.py | 109 +++++++++++++++ python/vyos/migrator.py | 26 ++-- python/vyos/systemversions.py | 46 +++++++ smoketest/scripts/cli/test_component_version.py | 6 +- src/helpers/system-versions-foot.py | 19 ++- 7 files changed, 248 insertions(+), 189 deletions(-) delete mode 100644 python/vyos/component_version.py create mode 100644 python/vyos/component_versions.py create mode 100644 python/vyos/formatversions.py create mode 100644 python/vyos/systemversions.py (limited to 'src') diff --git a/python/vyos/component_version.py b/python/vyos/component_version.py deleted file mode 100644 index b1554828d..000000000 --- a/python/vyos/component_version.py +++ /dev/null @@ -1,174 +0,0 @@ -# Copyright 2022 VyOS maintainers and contributors -# -# 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 . - -""" -Functions for reading/writing component versions. - -The config file version string has the following form: - -VyOS 1.3/1.4: - -// Warning: Do not remove the following line. -// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" -// Release version: 1.3.0 - -VyOS 1.2: - -/* Warning: Do not remove the following line. */ -/* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pppoe-server@2:pptp@1:qos@1:quagga@7:snmp@1:ssh@1:system@10:vrrp@2:wanloadbalance@3:webgui@1:webproxy@2:zone-policy@1" === */ -/* Release version: 1.2.8 */ - -""" - -import os -import re -import sys -import fileinput -from vyos.xml import component_version -from vyos.version import get_version - -def from_string(string_line, vintage='vyos'): - """ - Get component version dictionary from string. - Return empty dictionary if string contains no config information - or raise error if component version string malformed. - """ - version_dict = {} - - if vintage == 'vyos': - if re.match(r'// vyos-config-version:.+', string_line): - if not re.match(r'// vyos-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s*', string_line): - raise ValueError(f"malformed configuration string: {string_line}") - - for pair in re.findall(r'([\w,-]+)@(\d+)', string_line): - version_dict[pair[0]] = int(pair[1]) - - elif vintage == 'vyatta': - if re.match(r'/\* === vyatta-config-version:.+=== \*/$', string_line): - if not re.match(r'/\* === vyatta-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s+=== \*/$', string_line): - raise ValueError(f"malformed configuration string: {string_line}") - - for pair in re.findall(r'([\w,-]+)@(\d+)', string_line): - version_dict[pair[0]] = int(pair[1]) - else: - raise ValueError("Unknown config string vintage") - - return version_dict - -def from_file(config_file_name='/opt/vyatta/etc/config/config.boot', - vintage='vyos'): - """ - Get component version dictionary parsing config file line by line - """ - f = open(config_file_name, 'r') - for line_in_config in f: - version_dict = from_string(line_in_config, vintage=vintage) - if version_dict: - return version_dict - - # no version information - return {} - -def from_system(): - """ - Get system component version dict. - """ - return component_version() - -def legacy_from_system(): - """ - legacy function; imported as-is. - - Get component versions from running system; critical failure if - unable to read migration directory. - """ - import vyos.defaults - - system_versions = {} - - try: - version_info = os.listdir(vyos.defaults.directories['current']) - except OSError as err: - print("OS error: {}".format(err)) - sys.exit(1) - - for info in version_info: - if re.match(r'[\w,-]+@\d+', info): - pair = info.split('@') - system_versions[pair[0]] = int(pair[1]) - - return system_versions - -def format_string(ver: dict) -> str: - """ - Version dict to string. - """ - keys = list(ver) - keys.sort() - l = [] - for k in keys: - v = ver[k] - l.append(f'{k}@{v}') - sep = ':' - return sep.join(l) - -def system_footer(vintage='vyos') -> str: - """ - Version footer as string. - """ - ver_str = format_string(from_system()) - release = get_version() - if vintage == 'vyos': - ret_str = (f'// Warning: Do not remove the following line.\n' - + f'// vyos-config-version: "{ver_str}"\n' - + f'// Release version: {release}\n') - elif vintage == 'vyatta': - ret_str = (f'/* Warning: Do not remove the following line. */\n' - + f'/* === vyatta-config-version: "{ver_str}" === */\n' - + f'/* Release version: {release} */\n') - else: - raise ValueError("Unknown config string vintage") - - return ret_str - -def write_footer(file_name, vintage='vyos'): - """ - Write version footer to file. - """ - footer = system_footer(vintage=vintage) - if file_name: - with open(file_name, 'a') as f: - f.write(footer) - else: - sys.stdout.write(footer) - -def remove_footer(file_name): - """ - Remove old version footer. - """ - for line in fileinput.input(file_name, inplace=True): - if re.match(r'/\* Warning:.+ \*/$', line): - continue - if re.match(r'/\* === vyatta-config-version:.+=== \*/$', line): - continue - if re.match(r'/\* Release version:.+ \*/$', line): - continue - if re.match('// vyos-config-version:.+', line): - continue - if re.match('// Warning:.+', line): - continue - if re.match('// Release version:.+', line): - continue - sys.stdout.write(line) diff --git a/python/vyos/component_versions.py b/python/vyos/component_versions.py new file mode 100644 index 000000000..90b458aae --- /dev/null +++ b/python/vyos/component_versions.py @@ -0,0 +1,57 @@ +# Copyright 2017 VyOS maintainers and contributors +# +# 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 . + +""" +The version data looks like: + +/* Warning: Do not remove the following line. */ +/* === vyatta-config-version: +"cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@1:dhcp-server@4:firewall@5:ipsec@4:nat@4:qos@1:quagga@2:system@8:vrrp@1:wanloadbalance@3:webgui@1:webproxy@1:zone-policy@1" +=== */ +/* Release version: 1.2.0-rolling+201806131737 */ +""" + +import re + +def get_component_version(string_line): + """ + Get component version dictionary from string + return empty dictionary if string contains no config information + or raise error if component version string malformed + """ + return_value = {} + if re.match(r'/\* === vyatta-config-version:.+=== \*/$', string_line): + + if not re.match(r'/\* === vyatta-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s+=== \*/$', string_line): + raise ValueError("malformed configuration string: " + str(string_line)) + + for pair in re.findall(r'([\w,-]+)@(\d+)', string_line): + if pair[0] in return_value.keys(): + raise ValueError("duplicate unit name: \"" + str(pair[0]) + "\" in string: \"" + string_line + "\"") + return_value[pair[0]] = int(pair[1]) + + return return_value + + +def get_component_versions_from_file(config_file_name='/opt/vyatta/etc/config/config.boot'): + """ + Get component version dictionary parsing config file line by line + """ + f = open(config_file_name, 'r') + for line_in_config in f: + component_version = get_component_version(line_in_config) + if component_version: + return component_version + raise ValueError("no config string in file:", config_file_name) diff --git a/python/vyos/formatversions.py b/python/vyos/formatversions.py new file mode 100644 index 000000000..29117a5d3 --- /dev/null +++ b/python/vyos/formatversions.py @@ -0,0 +1,109 @@ +# Copyright 2019 VyOS maintainers and contributors +# +# 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 . + +import sys +import os +import re +import fileinput + +def read_vyatta_versions(config_file): + config_file_versions = {} + + with open(config_file, 'r') as config_file_handle: + for config_line in config_file_handle: + if re.match(r'/\* === vyatta-config-version:.+=== \*/$', config_line): + if not re.match(r'/\* === vyatta-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s+=== \*/$', config_line): + raise ValueError("malformed configuration string: " + "{}".format(config_line)) + + for pair in re.findall(r'([\w,-]+)@(\d+)', config_line): + config_file_versions[pair[0]] = int(pair[1]) + + + return config_file_versions + +def read_vyos_versions(config_file): + config_file_versions = {} + + with open(config_file, 'r') as config_file_handle: + for config_line in config_file_handle: + if re.match(r'// vyos-config-version:.+', config_line): + if not re.match(r'// vyos-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s*', config_line): + raise ValueError("malformed configuration string: " + "{}".format(config_line)) + + for pair in re.findall(r'([\w,-]+)@(\d+)', config_line): + config_file_versions[pair[0]] = int(pair[1]) + + return config_file_versions + +def remove_versions(config_file): + """ + Remove old version string. + """ + for line in fileinput.input(config_file, inplace=True): + if re.match(r'/\* Warning:.+ \*/$', line): + continue + if re.match(r'/\* === vyatta-config-version:.+=== \*/$', line): + continue + if re.match(r'/\* Release version:.+ \*/$', line): + continue + if re.match('// vyos-config-version:.+', line): + continue + if re.match('// Warning:.+', line): + continue + if re.match('// Release version:.+', line): + continue + sys.stdout.write(line) + +def format_versions_string(config_versions): + cfg_keys = list(config_versions.keys()) + cfg_keys.sort() + + component_version_strings = [] + + for key in cfg_keys: + cfg_vers = config_versions[key] + component_version_strings.append('{}@{}'.format(key, cfg_vers)) + + separator = ":" + component_version_string = separator.join(component_version_strings) + + return component_version_string + +def write_vyatta_versions_foot(config_file, component_version_string, + os_version_string): + if config_file: + with open(config_file, 'a') as config_file_handle: + config_file_handle.write('/* Warning: Do not remove the following line. */\n') + config_file_handle.write('/* === vyatta-config-version: "{}" === */\n'.format(component_version_string)) + config_file_handle.write('/* Release version: {} */\n'.format(os_version_string)) + else: + sys.stdout.write('/* Warning: Do not remove the following line. */\n') + sys.stdout.write('/* === vyatta-config-version: "{}" === */\n'.format(component_version_string)) + sys.stdout.write('/* Release version: {} */\n'.format(os_version_string)) + +def write_vyos_versions_foot(config_file, component_version_string, + os_version_string): + if config_file: + with open(config_file, 'a') as config_file_handle: + config_file_handle.write('// Warning: Do not remove the following line.\n') + config_file_handle.write('// vyos-config-version: "{}"\n'.format(component_version_string)) + config_file_handle.write('// Release version: {}\n'.format(os_version_string)) + else: + sys.stdout.write('// Warning: Do not remove the following line.\n') + sys.stdout.write('// vyos-config-version: "{}"\n'.format(component_version_string)) + sys.stdout.write('// Release version: {}\n'.format(os_version_string)) + diff --git a/python/vyos/migrator.py b/python/vyos/migrator.py index 266a2e58e..a2e0daabd 100644 --- a/python/vyos/migrator.py +++ b/python/vyos/migrator.py @@ -17,8 +17,10 @@ import sys import os import json import subprocess +import vyos.version import vyos.defaults -import vyos.component_version as component_version +import vyos.systemversions as systemversions +import vyos.formatversions as formatversions class MigratorError(Exception): pass @@ -40,13 +42,13 @@ class Migrator(object): cfg_file = self._config_file component_versions = {} - cfg_versions = component_version.from_file(cfg_file, vintage='vyatta') + cfg_versions = formatversions.read_vyatta_versions(cfg_file) if cfg_versions: self._config_file_vintage = 'vyatta' component_versions = cfg_versions - cfg_versions = component_version.from_file(cfg_file, vintage='vyos') + cfg_versions = formatversions.read_vyos_versions(cfg_file) if cfg_versions: self._config_file_vintage = 'vyos' @@ -150,11 +152,19 @@ class Migrator(object): """ Write new versions string. """ + versions_string = formatversions.format_versions_string(cfg_versions) + + os_version_string = vyos.version.get_version() + if self._config_file_vintage == 'vyatta': - component_version.write_footer(self._config_file, vintage='vyatta') + formatversions.write_vyatta_versions_foot(self._config_file, + versions_string, + os_version_string) if self._config_file_vintage == 'vyos': - component_version.write_footer(self._config_file, vintage='vyos') + formatversions.write_vyos_versions_foot(self._config_file, + versions_string, + os_version_string) def save_json_record(self, component_versions: dict): """ @@ -185,7 +195,7 @@ class Migrator(object): # This will force calling all migration scripts: cfg_versions = {} - sys_versions = component_version.from_system() + sys_versions = systemversions.get_system_component_version() # save system component versions in json file for easy reference self.save_json_record(sys_versions) @@ -201,7 +211,7 @@ class Migrator(object): if not self._changed: return - component_version.remove_footer(cfg_file) + formatversions.remove_versions(cfg_file) self.write_config_file_versions(rev_versions) @@ -222,7 +232,7 @@ class VirtualMigrator(Migrator): if not self._changed: return - component_version.remove_footer(cfg_file) + formatversions.remove_versions(cfg_file) self.write_config_file_versions(cfg_versions) diff --git a/python/vyos/systemversions.py b/python/vyos/systemversions.py new file mode 100644 index 000000000..f2da76d4f --- /dev/null +++ b/python/vyos/systemversions.py @@ -0,0 +1,46 @@ +# Copyright 2019 VyOS maintainers and contributors +# +# 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 . + +import os +import re +import sys +import vyos.defaults +from vyos.xml import component_version + +# legacy version, reading from the file names in +# /opt/vyatta/etc/config-migrate/current +def get_system_versions(): + """ + Get component versions from running system; critical failure if + unable to read migration directory. + """ + system_versions = {} + + try: + version_info = os.listdir(vyos.defaults.directories['current']) + except OSError as err: + print("OS error: {}".format(err)) + sys.exit(1) + + for info in version_info: + if re.match(r'[\w,-]+@\d+', info): + pair = info.split('@') + system_versions[pair[0]] = int(pair[1]) + + return system_versions + +# read from xml cache +def get_system_component_version(): + return component_version() diff --git a/smoketest/scripts/cli/test_component_version.py b/smoketest/scripts/cli/test_component_version.py index 21cc1c761..777379bdd 100755 --- a/smoketest/scripts/cli/test_component_version.py +++ b/smoketest/scripts/cli/test_component_version.py @@ -16,7 +16,7 @@ import unittest -from vyos.component_version import legacy_from_system, from_system +from vyos.systemversions import get_system_versions, get_system_component_version # After T3474, component versions should be updated in the files in # vyos-1x/interface-definitions/include/version/ @@ -24,8 +24,8 @@ from vyos.component_version import legacy_from_system, from_system # that in the xml cache. class TestComponentVersion(unittest.TestCase): def setUp(self): - self.legacy_d = legacy_from_system() - self.xml_d = from_system() + self.legacy_d = get_system_versions() + self.xml_d = get_system_component_version() def test_component_version(self): self.assertTrue(set(self.legacy_d).issubset(set(self.xml_d))) diff --git a/src/helpers/system-versions-foot.py b/src/helpers/system-versions-foot.py index b44408542..2aa687221 100755 --- a/src/helpers/system-versions-foot.py +++ b/src/helpers/system-versions-foot.py @@ -16,13 +16,24 @@ # along with this library. If not, see . import sys +import vyos.formatversions as formatversions +import vyos.systemversions as systemversions import vyos.defaults -from vyos.component_version import write_footer +import vyos.version + +sys_versions = systemversions.get_system_component_version() + +component_string = formatversions.format_versions_string(sys_versions) + +os_version_string = vyos.version.get_version() sys.stdout.write("\n\n") if vyos.defaults.cfg_vintage == 'vyos': - write_footer(None, vintage='vyos') + formatversions.write_vyos_versions_foot(None, component_string, + os_version_string) elif vyos.defaults.cfg_vintage == 'vyatta': - write_footer(None, vintage='vyatta') + formatversions.write_vyatta_versions_foot(None, component_string, + os_version_string) else: - write_footer(None, vintage='vyatta') + formatversions.write_vyatta_versions_foot(None, component_string, + os_version_string) -- cgit v1.2.3 From ff0e43807789f3c5c228683eaeb5fc4fbb8f75ce Mon Sep 17 00:00:00 2001 From: Nicolas Fort Date: Sat, 12 Mar 2022 15:10:52 +0000 Subject: Firewall: T4286: Correct ipv6-range validator --- src/validators/ipv6-range | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/validators/ipv6-range b/src/validators/ipv6-range index a3c401281..7080860c4 100755 --- a/src/validators/ipv6-range +++ b/src/validators/ipv6-range @@ -1,17 +1,20 @@ -#!/usr/bin/python3 +#!/usr/bin/env python3 -import sys -import re -from vyos.template import is_ipv6 +from ipaddress import IPv6Address +from sys import argv, exit if __name__ == '__main__': - if len(sys.argv)>1: - ipv6_range = sys.argv[1] - # Regex for ipv6-ipv6 https://regexr.com/ - if re.search('([a-f0-9:]+:+)+[a-f0-9]+-([a-f0-9:]+:+)+[a-f0-9]+', ipv6_range): - for tmp in ipv6_range.split('-'): - if not is_ipv6(tmp): - print(f'Error: {ipv6_range} is not a valid IPv6 range') - sys.exit(1) - - sys.exit(0) + if len(argv) > 1: + # try to pass validation and raise an error if failed + try: + ipv6_range = argv[1] + range_left = ipv6_range.split('-')[0] + range_right = ipv6_range.split('-')[1] + if not IPv6Address(range_left) < IPv6Address(range_right): + raise ValueError(f'left element {range_left} must be less than right element {range_right}') + except Exception as err: + print(f'Error: {ipv6_range} is not a valid IPv6 range: {err}') + exit(1) + else: + print('Error: an IPv6 range argument must be provided') + exit(1) -- cgit v1.2.3 From a7a7e38049d4601d55dd032b7d3aecf96c7e8781 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Sun, 13 Mar 2022 12:48:46 +0000 Subject: bgp: T4290: Add verify source-interface for none ip neighbor When we use neighbor as interface we must not use option 'source-interface' for example: neighbor eth0 source-interface eth0 Such option can be used for IP/IPv6 neighbors --- src/conf_mode/protocols_bgp.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index d8704727c..9e59177a8 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -166,6 +166,8 @@ def verify(bgp): raise ConfigError(f'peer-group must be set under the interface node of "{peer}"') if 'remote_as' in peer_config: raise ConfigError(f'remote-as must be set under the interface node of "{peer}"') + if 'source_interface' in peer_config['interface']: + raise ConfigError(f'"source-interface" option not allowed for neighbor "{peer}"') for afi in ['ipv4_unicast', 'ipv4_multicast', 'ipv4_labeled_unicast', 'ipv4_flowspec', 'ipv6_unicast', 'ipv6_multicast', 'ipv6_labeled_unicast', 'ipv6_flowspec', -- cgit v1.2.3 From df4b544c29974e36b52fc42bcbf617f50738a4a6 Mon Sep 17 00:00:00 2001 From: zsdc Date: Tue, 15 Mar 2022 18:29:12 +0200 Subject: bonding: T4301: Fixed arp-monitor option In verify function for arp-monitor option was used by mistake an extra conversion for incoming data before comparing items. This commit removed these unnecessary conversions and makes the option operable. --- src/conf_mode/interfaces-bonding.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index 431d65f1f..d5be21949 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -132,10 +132,10 @@ def verify(bond): return None if 'arp_monitor' in bond: - if 'target' in bond['arp_monitor'] and len(int(bond['arp_monitor']['target'])) > 16: + if 'target' in bond['arp_monitor'] and len(bond['arp_monitor']['target']) > 16: raise ConfigError('The maximum number of arp-monitor targets is 16') - if 'interval' in bond['arp_monitor'] and len(int(bond['arp_monitor']['interval'])) > 0: + if 'interval' in bond['arp_monitor'] and int(bond['arp_monitor']['interval']) > 0: if bond['mode'] in ['802.3ad', 'balance-tlb', 'balance-alb']: raise ConfigError('ARP link monitoring does not work for mode 802.3ad, ' \ 'transmit-load-balance or adaptive-load-balance') -- cgit v1.2.3 From 91d19038f9e31657e660a88cbfc1443e454177ef Mon Sep 17 00:00:00 2001 From: fett0 Date: Fri, 18 Mar 2022 13:00:04 +0000 Subject: OSPF : T4304: add check access-list is defined --- src/conf_mode/protocols_ospf.py | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'src') diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index 4895cde6f..26d491838 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -25,6 +25,7 @@ from vyos.configdict import node_changed from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_route_map from vyos.configverify import verify_interface_exists +from vyos.configverify import verify_access_list from vyos.template import render_to_string from vyos.util import dict_search from vyos.util import get_interface_config @@ -159,6 +160,16 @@ def verify(ospf): route_map_name = dict_search('default_information.originate.route_map', ospf) if route_map_name: verify_route_map(route_map_name, ospf) + # Validate if configured Access-list exists + if 'area' in ospf: + for area, area_config in ospf['area'].items(): + if 'import_list' in area_config: + acl_import = area_config['import_list'] + if acl_import: verify_access_list(acl_import, ospf) + if 'export_list' in area_config: + acl_export = area_config['export_list'] + if acl_export: verify_access_list(acl_export, ospf) + if 'interface' in ospf: for interface, interface_config in ospf['interface'].items(): verify_interface_exists(interface) -- cgit v1.2.3 From 18483a2f7d18aecb40b2003cec9a9dae6bcfaa24 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 21 Mar 2022 18:52:12 +0100 Subject: mirror: T3089: add verify_mirror() also for bond and bridge interfaces --- src/conf_mode/interfaces-bonding.py | 2 ++ src/conf_mode/interfaces-bridge.py | 2 ++ 2 files changed, 4 insertions(+) (limited to 'src') diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index d5be21949..bb53cd6c2 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -28,6 +28,7 @@ from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_dhcpv6 from vyos.configverify import verify_source_interface +from vyos.configverify import verify_mirror from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf @@ -149,6 +150,7 @@ def verify(bond): verify_address(bond) verify_dhcpv6(bond) verify_vrf(bond) + verify_mirror(bond) # use common function to verify VLAN configuration verify_vlan_config(bond) diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index f4dba9d4a..9f840cb58 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -27,6 +27,7 @@ from vyos.configdict import is_source_interface from vyos.configdict import has_vlan_subinterface_configured from vyos.configdict import dict_merge from vyos.configverify import verify_dhcpv6 +from vyos.configverify import verify_mirror from vyos.configverify import verify_vrf from vyos.ifconfig import BridgeIf from vyos.validate import has_address_configured @@ -105,6 +106,7 @@ def verify(bridge): verify_dhcpv6(bridge) verify_vrf(bridge) + verify_mirror(bridge) ifname = bridge['ifname'] -- cgit v1.2.3 From 3584691b35f35e40a1bfc22c34da031141fd0dfa Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 21 Mar 2022 21:41:41 +0100 Subject: qos: T4284: initial XML interface definitions for rewrite --- Makefile | 6 + data/configd-include.json | 1 + .../include/interface/redirect.xml.i | 17 + .../include/interface/traffic-policy.xml.i | 43 ++ .../include/interface/vif-s.xml.i | 4 + interface-definitions/include/interface/vif.xml.i | 4 +- interface-definitions/include/qos/bandwidth.xml.i | 15 + interface-definitions/include/qos/burst.xml.i | 16 + .../include/qos/codel-quantum.xml.i | 16 + interface-definitions/include/qos/dscp.xml.i | 143 ++++ interface-definitions/include/qos/flows.xml.i | 16 + interface-definitions/include/qos/hfsc-d.xml.i | 15 + interface-definitions/include/qos/hfsc-m1.xml.i | 32 + interface-definitions/include/qos/hfsc-m2.xml.i | 32 + interface-definitions/include/qos/interval.xml.i | 16 + interface-definitions/include/qos/match.xml.i | 221 +++++++ interface-definitions/include/qos/max-length.xml.i | 15 + .../include/qos/queue-limit-1-4294967295.xml.i | 15 + .../include/qos/queue-limit-2-10999.xml.i | 16 + interface-definitions/include/qos/queue-type.xml.i | 30 + interface-definitions/include/qos/set-dscp.xml.i | 63 ++ interface-definitions/include/qos/target.xml.i | 16 + interface-definitions/include/qos/tcp-flags.xml.i | 21 + interface-definitions/interfaces-bonding.xml.in | 2 + interface-definitions/interfaces-bridge.xml.in | 2 + interface-definitions/interfaces-dummy.xml.in | 2 + interface-definitions/interfaces-ethernet.xml.in | 2 + interface-definitions/interfaces-geneve.xml.in | 2 + interface-definitions/interfaces-input.xml.in | 30 + interface-definitions/interfaces-l2tpv3.xml.in | 1 + interface-definitions/interfaces-loopback.xml.in | 2 + interface-definitions/interfaces-macsec.xml.in | 2 + interface-definitions/interfaces-openvpn.xml.in | 2 + interface-definitions/interfaces-pppoe.xml.in | 4 +- .../interfaces-pseudo-ethernet.xml.in | 2 + interface-definitions/interfaces-tunnel.xml.in | 4 +- interface-definitions/interfaces-vti.xml.in | 2 + interface-definitions/interfaces-vxlan.xml.in | 2 + interface-definitions/interfaces-wireguard.xml.in | 4 +- interface-definitions/interfaces-wireless.xml.in | 2 + interface-definitions/interfaces-wwan.xml.in | 4 +- interface-definitions/qos.xml.in | 721 +++++++++++++++++++++ python/vyos/configverify.py | 16 + src/conf_mode/interfaces-bonding.py | 4 +- src/conf_mode/interfaces-bridge.py | 2 + src/conf_mode/interfaces-dummy.py | 2 + src/conf_mode/interfaces-ethernet.py | 2 + src/conf_mode/interfaces-geneve.py | 2 + src/conf_mode/interfaces-l2tpv3.py | 2 + src/conf_mode/interfaces-loopback.py | 2 + src/conf_mode/interfaces-macsec.py | 2 + src/conf_mode/interfaces-pppoe.py | 2 + src/conf_mode/interfaces-pseudo-ethernet.py | 2 + src/conf_mode/interfaces-tunnel.py | 2 + src/conf_mode/interfaces-vti.py | 2 + src/conf_mode/interfaces-vxlan.py | 2 + src/conf_mode/interfaces-wireguard.py | 2 + src/conf_mode/interfaces-wireless.py | 2 + src/conf_mode/interfaces-wwan.py | 2 + src/conf_mode/qos.py | 90 +++ 60 files changed, 1699 insertions(+), 6 deletions(-) create mode 100644 interface-definitions/include/interface/redirect.xml.i create mode 100644 interface-definitions/include/interface/traffic-policy.xml.i create mode 100644 interface-definitions/include/qos/bandwidth.xml.i create mode 100644 interface-definitions/include/qos/burst.xml.i create mode 100644 interface-definitions/include/qos/codel-quantum.xml.i create mode 100644 interface-definitions/include/qos/dscp.xml.i create mode 100644 interface-definitions/include/qos/flows.xml.i create mode 100644 interface-definitions/include/qos/hfsc-d.xml.i create mode 100644 interface-definitions/include/qos/hfsc-m1.xml.i create mode 100644 interface-definitions/include/qos/hfsc-m2.xml.i create mode 100644 interface-definitions/include/qos/interval.xml.i create mode 100644 interface-definitions/include/qos/match.xml.i create mode 100644 interface-definitions/include/qos/max-length.xml.i create mode 100644 interface-definitions/include/qos/queue-limit-1-4294967295.xml.i create mode 100644 interface-definitions/include/qos/queue-limit-2-10999.xml.i create mode 100644 interface-definitions/include/qos/queue-type.xml.i create mode 100644 interface-definitions/include/qos/set-dscp.xml.i create mode 100644 interface-definitions/include/qos/target.xml.i create mode 100644 interface-definitions/include/qos/tcp-flags.xml.i create mode 100644 interface-definitions/interfaces-input.xml.in create mode 100644 interface-definitions/qos.xml.in create mode 100755 src/conf_mode/qos.py (limited to 'src') diff --git a/Makefile b/Makefile index 29744b323..431f3a8c2 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,12 @@ interface_definitions: $(config_xml_obj) # XXX: delete top level node.def's that now live in other packages # IPSec VPN EAP-RADIUS does not support source-address rm -rf $(TMPL_DIR)/vpn/ipsec/remote-access/radius/source-address + + # T4284 neq QoS implementation is not yet live + find $(TMPL_DIR)/interfaces -name traffic-policy -type d -exec rm -rf {} \; + find $(TMPL_DIR)/interfaces -name redirect -type d -exec rm -rf {} \; + rm -rf $(TMPL_DIR)/interfaces/input + # XXX: test if there are empty node.def files - this is not allowed as these # could mask help strings or mandatory priority statements find $(TMPL_DIR) -name node.def -type f -empty -exec false {} + || sh -c 'echo "There are empty node.def files! Check your interface definitions." && exit 1' diff --git a/data/configd-include.json b/data/configd-include.json index c85ab0725..b77d48001 100644 --- a/data/configd-include.json +++ b/data/configd-include.json @@ -48,6 +48,7 @@ "protocols_ripng.py", "protocols_static.py", "protocols_static_multicast.py", +"qos.py", "salt-minion.py", "service_console-server.py", "service_ids_fastnetmon.py", diff --git a/interface-definitions/include/interface/redirect.xml.i b/interface-definitions/include/interface/redirect.xml.i new file mode 100644 index 000000000..3be9ee16b --- /dev/null +++ b/interface-definitions/include/interface/redirect.xml.i @@ -0,0 +1,17 @@ + + + + Incoming packet redirection destination + + + + + txt + Interface name + + + + + + + diff --git a/interface-definitions/include/interface/traffic-policy.xml.i b/interface-definitions/include/interface/traffic-policy.xml.i new file mode 100644 index 000000000..cd60b62a5 --- /dev/null +++ b/interface-definitions/include/interface/traffic-policy.xml.i @@ -0,0 +1,43 @@ + + + + Traffic-policy for interface + + + + + Ingress traffic policy for interface + + traffic-policy drop-tail + traffic-policy fair-queue + traffic-policy fq-codel + traffic-policy limiter + traffic-policy network-emulator + traffic-policy priority-queue + traffic-policy random-detect + traffic-policy rate-control + traffic-policy round-robin + traffic-policy shaper + traffic-policy shaper-hfsc + + + txt + Policy name + + + + + + Egress traffic policy for interface + + traffic-policy + + + txt + Policy name + + + + + + \ No newline at end of file diff --git a/interface-definitions/include/interface/vif-s.xml.i b/interface-definitions/include/interface/vif-s.xml.i index f1a61ff64..59a47b5ff 100644 --- a/interface-definitions/include/interface/vif-s.xml.i +++ b/interface-definitions/include/interface/vif-s.xml.i @@ -64,11 +64,15 @@ #include #include #include + #include + #include #include #include #include + #include + #include #include diff --git a/interface-definitions/include/interface/vif.xml.i b/interface-definitions/include/interface/vif.xml.i index 11ba7e2f8..8a1475711 100644 --- a/interface-definitions/include/interface/vif.xml.i +++ b/interface-definitions/include/interface/vif.xml.i @@ -18,7 +18,6 @@ #include #include #include - #include #include #include @@ -51,6 +50,9 @@ #include #include #include + #include + #include + #include diff --git a/interface-definitions/include/qos/bandwidth.xml.i b/interface-definitions/include/qos/bandwidth.xml.i new file mode 100644 index 000000000..82af22f42 --- /dev/null +++ b/interface-definitions/include/qos/bandwidth.xml.i @@ -0,0 +1,15 @@ + + + + Traffic-limit used for this class + + <number> + Rate in kbit (kilobit per second) + + + <number><suffix> + Rate with scaling suffix (mbit, mbps, ...) + + + + diff --git a/interface-definitions/include/qos/burst.xml.i b/interface-definitions/include/qos/burst.xml.i new file mode 100644 index 000000000..761618027 --- /dev/null +++ b/interface-definitions/include/qos/burst.xml.i @@ -0,0 +1,16 @@ + + + + Burst size for this class + + <number> + Bytes + + + <number><suffix> + Bytes with scaling suffix (kb, mb, gb) + + + 15k + + diff --git a/interface-definitions/include/qos/codel-quantum.xml.i b/interface-definitions/include/qos/codel-quantum.xml.i new file mode 100644 index 000000000..bc24630b6 --- /dev/null +++ b/interface-definitions/include/qos/codel-quantum.xml.i @@ -0,0 +1,16 @@ + + + + Deficit in the fair queuing algorithm + + u32:0-1048576 + Number of bytes used as 'deficit' + + + + + Interval must be in range 0 to 1048576 + + 1514 + + diff --git a/interface-definitions/include/qos/dscp.xml.i b/interface-definitions/include/qos/dscp.xml.i new file mode 100644 index 000000000..bb90850ac --- /dev/null +++ b/interface-definitions/include/qos/dscp.xml.i @@ -0,0 +1,143 @@ + + + + Match on Differentiated Services Codepoint (DSCP) + + default reliability throughput lowdelay priority immediate flash flash-override critical internet network AF11 AF12 AF13 AF21 AF22 AF23 AF31 AF32 AF33 AF41 AF42 AF43 CS1 CS2 CS3 CS4 CS5 CS6 CS7 EF + + + u32:0-63 + Differentiated Services Codepoint (DSCP) value + + + default + match DSCP (000000) + + + reliability + match DSCP (000001) + + + throughput + match DSCP (000010) + + + lowdelay + match DSCP (000100) + + + priority + match DSCP (001000) + + + immediate + match DSCP (010000) + + + flash + match DSCP (011000) + + + flash-override + match DSCP (100000) + + + critical + match DSCP (101000) + + + internet + match DSCP (110000) + + + network + match DSCP (111000) + + + AF11 + High-throughput data + + + AF12 + High-throughput data + + + AF13 + High-throughput data + + + AF21 + Low-latency data + + + AF22 + Low-latency data + + + AF23 + Low-latency data + + + AF31 + Multimedia streaming + + + AF32 + Multimedia streaming + + + AF33 + Multimedia streaming + + + AF41 + Multimedia conferencing + + + AF42 + Multimedia conferencing + + + AF43 + Multimedia conferencing + + + CS1 + Low-priority data + + + CS2 + OAM + + + CS3 + Broadcast video + + + CS4 + Real-time interactive + + + CS5 + Signaling + + + CS6 + Network control + + + CS7 + + + + EF + Expedited Forwarding + + + + (default|reliability|throughput|lowdelay|priority|immediate|flash|flash-override|critical|internet|network|AF11|AF12|AF13|AF21|AF22|AF23|AF31|AF32|AF33|AF41|AF42|AF43|CS1|CS2|CS3|CS4|CS5|CS6|CS7|EF) + + Priority must be between 0 and 63 + + + diff --git a/interface-definitions/include/qos/flows.xml.i b/interface-definitions/include/qos/flows.xml.i new file mode 100644 index 000000000..a7d7c6422 --- /dev/null +++ b/interface-definitions/include/qos/flows.xml.i @@ -0,0 +1,16 @@ + + + + Number of flows into which the incoming packets are classified + + u32:1-65536 + Number of flows + + + + + Interval must be in range 1 to 65536 + + 1024 + + diff --git a/interface-definitions/include/qos/hfsc-d.xml.i b/interface-definitions/include/qos/hfsc-d.xml.i new file mode 100644 index 000000000..2a513509c --- /dev/null +++ b/interface-definitions/include/qos/hfsc-d.xml.i @@ -0,0 +1,15 @@ + + + + Service curve delay + + <number> + Time in milliseconds + + + + + Priority must be between 0 and 65535 + + + diff --git a/interface-definitions/include/qos/hfsc-m1.xml.i b/interface-definitions/include/qos/hfsc-m1.xml.i new file mode 100644 index 000000000..749d01f57 --- /dev/null +++ b/interface-definitions/include/qos/hfsc-m1.xml.i @@ -0,0 +1,32 @@ + + + + Linkshare m1 parameter for class traffic + + <number> + Rate in kbit (kilobit per second) + + + <number>%% + Percentage of overall rate + + + <number>bit + bit(1), kbit(10^3), mbit(10^6), gbit, tbit + + + <number>ibit + kibit(1024), mibit(1024^2), gibit(1024^3), tbit(1024^4) + + + <number>ibps + kibps(1024*8), mibps(1024^2*8), gibps, tibps - Byte/sec + + + <number>bps + bps(8),kbps(8*10^3),mbps(8*10^6), gbps, tbps - Byte/sec + + + 100% + + diff --git a/interface-definitions/include/qos/hfsc-m2.xml.i b/interface-definitions/include/qos/hfsc-m2.xml.i new file mode 100644 index 000000000..24e8f5d63 --- /dev/null +++ b/interface-definitions/include/qos/hfsc-m2.xml.i @@ -0,0 +1,32 @@ + + + + Linkshare m2 parameter for class traffic + + <number> + Rate in kbit (kilobit per second) + + + <number>%% + Percentage of overall rate + + + <number>bit + bit(1), kbit(10^3), mbit(10^6), gbit, tbit + + + <number>ibit + kibit(1024), mibit(1024^2), gibit(1024^3), tbit(1024^4) + + + <number>ibps + kibps(1024*8), mibps(1024^2*8), gibps, tibps - Byte/sec + + + <number>bps + bps(8),kbps(8*10^3),mbps(8*10^6), gbps, tbps - Byte/sec + + + 100% + + diff --git a/interface-definitions/include/qos/interval.xml.i b/interface-definitions/include/qos/interval.xml.i new file mode 100644 index 000000000..41896ac9c --- /dev/null +++ b/interface-definitions/include/qos/interval.xml.i @@ -0,0 +1,16 @@ + + + + Interval used to measure the delay + + u32 + Interval in milliseconds + + + + + Interval must be in range 0 to 4294967295 + + 100 + + diff --git a/interface-definitions/include/qos/match.xml.i b/interface-definitions/include/qos/match.xml.i new file mode 100644 index 000000000..7d89e4460 --- /dev/null +++ b/interface-definitions/include/qos/match.xml.i @@ -0,0 +1,221 @@ + + + + Class matching rule name + + [^-].* + + Match queue name cannot start with hyphen (-) + + + #include + + + Ethernet header match + + + + + Ethernet destination address for this match + + macaddr + MAC address to match + + + + + + + + + Ethernet protocol for this match + + + all 802.1Q 802_2 802_3 aarp aoe arp atalk dec ip ipv6 ipx lat localtalk rarp snap x25 + + + u32:0-65535 + Ethernet protocol number + + + txt + Ethernet protocol name + + + all + Any protocol + + + ip + Internet IP (IPv4) + + + ipv6 + Internet IP (IPv6) + + + arp + Address Resolution Protocol + + + atalk + Appletalk + + + ipx + Novell Internet Packet Exchange + + + 802.1Q + 802.1Q VLAN tag + + + + + + + + + Ethernet source address for this match + + macaddr + MAC address to match + + + + + + + + + #include + + + Match IP protocol header + + + + + Match on destination port or address + + + + + IPv4 destination address for this match + + ipv4net + IPv4 address and prefix length + + + + + + + #include + + + #include + #include + #include + + + Match on source port or address + + + + + IPv4 source address for this match + + ipv4net + IPv4 address and prefix length + + + + + + + #include + + + #include + + + + + Match IPv6 protocol header + + + + + Match on destination port or address + + + + + IPv6 destination address for this match + + ipv6net + IPv6 address and prefix length + + + + + + + #include + + + #include + #include + #include + + + Match on source port or address + + + + + IPv6 source address for this match + + ipv6net + IPv6 address and prefix length + + + + + + + #include + + + #include + + + + + Match on mark applied by firewall + + txt + FW mark to match + + + + + + + + + Virtual Local Area Network (VLAN) ID for this match + + u32:0-4095 + Virtual Local Area Network (VLAN) tag + + + + + VLAN ID must be between 0 and 4095 + + + + + diff --git a/interface-definitions/include/qos/max-length.xml.i b/interface-definitions/include/qos/max-length.xml.i new file mode 100644 index 000000000..4cc20f8c4 --- /dev/null +++ b/interface-definitions/include/qos/max-length.xml.i @@ -0,0 +1,15 @@ + + + + Maximum packet length (ipv4) + + u32:0-65535 + Maximum packet/payload length + + + + + Maximum IPv4 total packet length is 65535 + + + diff --git a/interface-definitions/include/qos/queue-limit-1-4294967295.xml.i b/interface-definitions/include/qos/queue-limit-1-4294967295.xml.i new file mode 100644 index 000000000..2f2d44631 --- /dev/null +++ b/interface-definitions/include/qos/queue-limit-1-4294967295.xml.i @@ -0,0 +1,15 @@ + + + + Maximum queue size + + u32:1-4294967295 + Queue size in packets + + + + + Queue limit must be greater than zero + + + diff --git a/interface-definitions/include/qos/queue-limit-2-10999.xml.i b/interface-definitions/include/qos/queue-limit-2-10999.xml.i new file mode 100644 index 000000000..7a9c8266b --- /dev/null +++ b/interface-definitions/include/qos/queue-limit-2-10999.xml.i @@ -0,0 +1,16 @@ + + + + Upper limit of the queue + + u32:2-10999 + Queue size in packets + + + + + Queue limit must greater than 1 and less than 11000 + + 10240 + + diff --git a/interface-definitions/include/qos/queue-type.xml.i b/interface-definitions/include/qos/queue-type.xml.i new file mode 100644 index 000000000..634f61024 --- /dev/null +++ b/interface-definitions/include/qos/queue-type.xml.i @@ -0,0 +1,30 @@ + + + + Queue type for default traffic + + fq-codel fair-queue drop-tail random-detect + + + fq-codel + Fair Queue Codel + + + fair-queue + Stochastic Fair Queue (SFQ) + + + drop-tail + First-In-First-Out (FIFO) + + + random-detect + Random Early Detection (RED) + + + (fq-codel|fair-queue|drop-tail|random-detect) + + + drop-tail + + diff --git a/interface-definitions/include/qos/set-dscp.xml.i b/interface-definitions/include/qos/set-dscp.xml.i new file mode 100644 index 000000000..55c0ea44d --- /dev/null +++ b/interface-definitions/include/qos/set-dscp.xml.i @@ -0,0 +1,63 @@ + + + + Change the Differentiated Services (DiffServ) field in the IP header + + default reliability throughput lowdelay priority immediate flash flash-override critical internet network + + + u32:0-63 + Priority order for bandwidth pool + + + default + match DSCP (000000) + + + reliability + match DSCP (000001) + + + throughput + match DSCP (000010) + + + lowdelay + match DSCP (000100) + + + priority + match DSCP (001000) + + + immediate + match DSCP (010000) + + + flash + match DSCP (011000) + + + flash-override + match DSCP (100000) + + + critical + match DSCP (101000) + + + internet + match DSCP (110000) + + + network + match DSCP (111000) + + + + (default|reliability|throughput|lowdelay|priority|immediate|flash|flash-override|critical|internet|network) + + Priority must be between 0 and 63 + + + diff --git a/interface-definitions/include/qos/target.xml.i b/interface-definitions/include/qos/target.xml.i new file mode 100644 index 000000000..bf6342ac9 --- /dev/null +++ b/interface-definitions/include/qos/target.xml.i @@ -0,0 +1,16 @@ + + + + Acceptable minimum standing/persistent queue delay + + u32 + Queue delay in milliseconds + + + + + Delay must be in range 0 to 4294967295 + + 5 + + diff --git a/interface-definitions/include/qos/tcp-flags.xml.i b/interface-definitions/include/qos/tcp-flags.xml.i new file mode 100644 index 000000000..81d70d1f3 --- /dev/null +++ b/interface-definitions/include/qos/tcp-flags.xml.i @@ -0,0 +1,21 @@ + + + + TCP Flags matching + + + + + Match TCP ACK + + + + + + Match TCP SYN + + + + + + diff --git a/interface-definitions/interfaces-bonding.xml.in b/interface-definitions/interfaces-bonding.xml.in index b98f4b960..20ece5137 100644 --- a/interface-definitions/interfaces-bonding.xml.in +++ b/interface-definitions/interfaces-bonding.xml.in @@ -207,6 +207,8 @@ + #include + #include #include #include #include diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in index fabfb917a..6957067cd 100644 --- a/interface-definitions/interfaces-bridge.xml.in +++ b/interface-definitions/interfaces-bridge.xml.in @@ -210,6 +210,8 @@ + #include + #include #include diff --git a/interface-definitions/interfaces-dummy.xml.in b/interface-definitions/interfaces-dummy.xml.in index 3bca8b950..109ed1b50 100644 --- a/interface-definitions/interfaces-dummy.xml.in +++ b/interface-definitions/interfaces-dummy.xml.in @@ -30,6 +30,8 @@ #include + #include + #include #include diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in index be7bddfa4..7d28912c0 100644 --- a/interface-definitions/interfaces-ethernet.xml.in +++ b/interface-definitions/interfaces-ethernet.xml.in @@ -196,6 +196,8 @@ + #include + #include #include #include #include diff --git a/interface-definitions/interfaces-geneve.xml.in b/interface-definitions/interfaces-geneve.xml.in index dd4d324d4..aa5809e60 100644 --- a/interface-definitions/interfaces-geneve.xml.in +++ b/interface-definitions/interfaces-geneve.xml.in @@ -50,6 +50,8 @@ + #include + #include #include #include diff --git a/interface-definitions/interfaces-input.xml.in b/interface-definitions/interfaces-input.xml.in new file mode 100644 index 000000000..f2eb01c58 --- /dev/null +++ b/interface-definitions/interfaces-input.xml.in @@ -0,0 +1,30 @@ + + + + + + + Input Functional Block (IFB) interface name + + 310 + + ifb[0-9]+ + + Input interface must be named ifbN + + ifbN + Input interface name + + + + #include + #include + #include + #include + #include + #include + + + + + diff --git a/interface-definitions/interfaces-l2tpv3.xml.in b/interface-definitions/interfaces-l2tpv3.xml.in index ba9bcb0a2..124863653 100644 --- a/interface-definitions/interfaces-l2tpv3.xml.in +++ b/interface-definitions/interfaces-l2tpv3.xml.in @@ -125,6 +125,7 @@ + #include #include diff --git a/interface-definitions/interfaces-loopback.xml.in b/interface-definitions/interfaces-loopback.xml.in index 7be15ab89..ffffc0220 100644 --- a/interface-definitions/interfaces-loopback.xml.in +++ b/interface-definitions/interfaces-loopback.xml.in @@ -26,6 +26,8 @@ #include + #include + #include diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in index 7206e57b1..311e95c2f 100644 --- a/interface-definitions/interfaces-macsec.xml.in +++ b/interface-definitions/interfaces-macsec.xml.in @@ -122,6 +122,8 @@ 1460 #include + #include + #include #include diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in index eb574eb52..73e30e590 100644 --- a/interface-definitions/interfaces-openvpn.xml.in +++ b/interface-definitions/interfaces-openvpn.xml.in @@ -816,6 +816,8 @@ + #include + #include #include diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in index ed0e45840..1d888236e 100644 --- a/interface-definitions/interfaces-pppoe.xml.in +++ b/interface-definitions/interfaces-pppoe.xml.in @@ -49,7 +49,6 @@ #include #include #include - #include Delay before disconnecting idle session (in seconds) @@ -134,6 +133,9 @@ Service name must be alphanumeric only + #include + #include + #include diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in index bf7055f8d..7baeac537 100644 --- a/interface-definitions/interfaces-pseudo-ethernet.xml.in +++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in @@ -59,6 +59,8 @@ private #include + #include + #include #include #include diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in index eb1708aaa..bc9297c86 100644 --- a/interface-definitions/interfaces-tunnel.xml.in +++ b/interface-definitions/interfaces-tunnel.xml.in @@ -20,7 +20,6 @@ #include #include #include - #include #include 1476 @@ -288,6 +287,9 @@ + #include + #include + #include diff --git a/interface-definitions/interfaces-vti.xml.in b/interface-definitions/interfaces-vti.xml.in index f03c7476d..538194c2b 100644 --- a/interface-definitions/interfaces-vti.xml.in +++ b/interface-definitions/interfaces-vti.xml.in @@ -34,6 +34,8 @@ #include #include #include + #include + #include #include #include #include diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index 0546b4199..18abf9f20 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -99,6 +99,8 @@ #include #include #include + #include + #include #include #include diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in index 1b4b4a816..2f130c6f2 100644 --- a/interface-definitions/interfaces-wireguard.xml.in +++ b/interface-definitions/interfaces-wireguard.xml.in @@ -19,7 +19,6 @@ #include #include #include - #include #include #include #include @@ -120,6 +119,9 @@ + #include + #include + #include diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in index 9db9fd757..eebe8f841 100644 --- a/interface-definitions/interfaces-wireless.xml.in +++ b/interface-definitions/interfaces-wireless.xml.in @@ -778,6 +778,8 @@ monitor + #include + #include #include #include diff --git a/interface-definitions/interfaces-wwan.xml.in b/interface-definitions/interfaces-wwan.xml.in index 03554feed..7007a67ae 100644 --- a/interface-definitions/interfaces-wwan.xml.in +++ b/interface-definitions/interfaces-wwan.xml.in @@ -30,7 +30,6 @@ #include #include #include - #include #include #include @@ -41,6 +40,9 @@ #include #include #include + #include + #include + #include diff --git a/interface-definitions/qos.xml.in b/interface-definitions/qos.xml.in new file mode 100644 index 000000000..d4468543c --- /dev/null +++ b/interface-definitions/qos.xml.in @@ -0,0 +1,721 @@ + + + + + Quality of Service (QOS) policy type + 900 + + + + + Packet limited First In, First Out queue + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed + + + #include + #include + + + + + Stochastic Fairness Queueing + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed + + + #include + + + Interval in seconds for queue algorithm perturbation + + u32:0 + No perturbation + + + u32:1-127 + Interval in seconds for queue algorithm perturbation (advised: 10) + + + + + Interval must be in range 0 to 127 + + 0 + + + + Upper limit of the SFQ + + u32:2-127 + Queue size in packets + + + + + Queue limit must greater than 1 and less than 128 + + 127 + + + + + + Fair Queuing Controlled Delay + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed + + + #include + #include + #include + #include + #include + #include + + + + + Traffic input limiting policy + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed + + + + + Class ID + + u32:1-4090 + Class Identifier + + + + + Class identifier must be between 1 and 4090 + + + #include + #include + #include + #include + + + Priority for rule evaluation + + u32:0-20 + Priority for match rule evaluation + + + + + Priority must be between 0 and 20 + + 20 + + + + + + Default policy + + + #include + #include + + + #include + + + + + Network emulator policy + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed + + + #include + #include + #include + + + Adds delay to packets outgoing to chosen network interface + + <number> + Time in milliseconds + + + + + Priority must be between 0 and 65535 + + + + + Introducing error in a random position for chosen percent of packets + + <number> + Percentage of packets affected + + + + + Priority must be between 0 and 100 + + + + + Add independent loss probability to the packets outgoing to chosen network interface + + <number> + Percentage of packets affected + + + + + Must be between 0 and 100 + + + + + Add independent loss probability to the packets outgoing to chosen network interface + + <number> + Percentage of packets affected + + + + + Must be between 0 and 100 + + + + + Packet reordering percentage + + <number> + Percentage of packets affected + + + + + Must be between 0 and 100 + + + #include + + + + + Priority queuing based policy + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed + + + + + Class Handle + + u32:1-7 + Priority + + + + + Class handle must be between 1 and 7 + + + #include + #include + #include + #include + #include + #include + #include + #include + + + + + Default policy + + + #include + #include + #include + #include + #include + #include + #include + + + #include + + + + + Priority queuing based policy + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed + + + #include + + auto + + #include + + + IP precedence + + u32:0-7 + IP precedence value + + + + + IP precedence value must be between 0 and 7 + + + #include + + + Average packet size (bytes) + + u32:16-10240 + Average packet size in bytes + + + + + Average packet size must be between 16 and 10240 + + 1024 + + + + Mark probability for this precedence + + <number> + Numeric value (1/N) + + + + + Mark probability must be greater than 0 + + + + + Maximum threshold for random detection + + u32:0-4096 + Maximum Threshold in packets + + + + + Threshold must be between 0 and 4096 + + + + + Minimum threshold for random detection + + u32:0-4096 + Maximum Threshold in packets + + + + + Threshold must be between 0 and 4096 + + + + + + + + + Rate limiting policy (Token Bucket Filter) + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed + + + #include + #include + #include + + + Maximum latency + + <number> + Time in milliseconds + + + + + Threshold must be between 0 and 4096 + + 50 + + + + + + Round-Robin based policy + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed + + + #include + + + Class ID + + u32:1-4095 + Class Identifier + + + + + Class identifier must be between 1 and 4095 + + + #include + #include + #include + #include + #include + + + Packet scheduling quantum + + u32:1-4294967295 + Packet scheduling quantum (bytes) + + + + + Quantum must be in range 1 to 4294967295 + + + #include + #include + #include + + + + + + + Hierarchical Fair Service Curve's policy + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed + + + #include + + auto + + #include + + + Class ID + + u32:1-4095 + Class Identifier + + + + + Class identifier must be between 1 and 4095 + + + #include + + + Linkshare class settings + + + #include + #include + #include + + + #include + + + Realtime class settings + + + #include + #include + #include + + + + + Upperlimit class settings + + + #include + #include + #include + + + + + + + Default policy + + + + + Linkshare class settings + + + #include + #include + #include + + + + + Realtime class settings + + + #include + #include + #include + + + + + Upperlimit class settings + + + #include + #include + #include + + + + + + + + + Traffic shaping based policy (Hierarchy Token Bucket) + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed + + + #include + + auto + + + + Class ID + + u32:2-4095 + Class Identifier + + + + + Class identifier must be between 2 and 4095 + + + #include + + 100% + + #include + + + Bandwidth limit for this class + + <number> + Rate in kbit (kilobit per second) + + + <number>%% + Percentage of overall rate + + + <number>bit + bit(1), kbit(10^3), mbit(10^6), gbit, tbit + + + <number>ibit + kibit(1024), mibit(1024^2), gibit(1024^3), tbit(1024^4) + + + <number>ibps + kibps(1024*8), mibps(1024^2*8), gibps, tibps - Byte/sec + + + <number>bps + bps(8),kbps(8*10^3),mbps(8*10^6), gbps, tbps - Byte/sec + + + + #include + #include + #include + #include + #include + + + Priority for usage of excess bandwidth + + u32:0-7 + Priority order for bandwidth pool + + + + + Priority must be between 0 and 7 + + 20 + + #include + #include + #include + #include + + + #include + + + Default policy + + + #include + #include + + + Bandwidth limit for this class + + <number> + Rate in kbit (kilobit per second) + + + <number>%% + Percentage of overall rate + + + <number>bit + bit(1), kbit(10^3), mbit(10^6), gbit, tbit + + + <number>ibit + kibit(1024), mibit(1024^2), gibit(1024^3), tbit(1024^4) + + + <number>ibps + kibps(1024*8), mibps(1024^2*8), gibps, tibps - Byte/sec + + + <number>bps + bps(8),kbps(8*10^3),mbps(8*10^6), gbps, tbps - Byte/sec + + + + #include + #include + #include + #include + + + Priority for usage of excess bandwidth + + u32:0-7 + Priority order for bandwidth pool + + + + + Priority must be between 0 and 7 + + 20 + + #include + #include + #include + #include + + + + + + + diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index fab88bc72..7f1258575 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -191,6 +191,19 @@ def verify_mirror(config): raise ConfigError(f'Can not mirror "{direction}" traffic back ' \ 'the originating interface!') +def verify_redirect(config): + """ + Common helper function used by interface implementations to perform + recurring validation of the redirect interface configuration. + + It makes no sense to mirror and redirect traffic at the same time! + """ + if {'mirror', 'redirect'} <= set(config): + raise ConfigError('Can not do both redirect and mirror') + + if dict_search('traffic_policy.in', config) != None: + raise ConfigError('Can not use ingress policy and redirect') + def verify_authentication(config): """ Common helper function used by interface implementations to perform @@ -315,6 +328,7 @@ def verify_vlan_config(config): verify_dhcpv6(vlan) verify_address(vlan) verify_vrf(vlan) + verify_redirect(vlan) verify_mtu_parent(vlan, config) # 802.1ad (Q-in-Q) VLANs @@ -323,6 +337,7 @@ def verify_vlan_config(config): verify_dhcpv6(s_vlan) verify_address(s_vlan) verify_vrf(s_vlan) + verify_redirect(s_vlan) verify_mtu_parent(s_vlan, config) for c_vlan in s_vlan.get('vif_c', {}): @@ -330,6 +345,7 @@ def verify_vlan_config(config): verify_dhcpv6(c_vlan) verify_address(c_vlan) verify_vrf(c_vlan) + verify_redirect(c_vlan) verify_mtu_parent(c_vlan, config) verify_mtu_parent(c_vlan, s_vlan) diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index bb53cd6c2..661dc2298 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -27,9 +27,10 @@ from vyos.configdict import is_source_interface from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_dhcpv6 -from vyos.configverify import verify_source_interface from vyos.configverify import verify_mirror from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_redirect +from vyos.configverify import verify_source_interface from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf from vyos.ifconfig import BondIf @@ -151,6 +152,7 @@ def verify(bond): verify_dhcpv6(bond) verify_vrf(bond) verify_mirror(bond) + verify_redirect(bond) # use common function to verify VLAN configuration verify_vlan_config(bond) diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index 9f840cb58..e16c0e9f4 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -28,6 +28,7 @@ from vyos.configdict import has_vlan_subinterface_configured from vyos.configdict import dict_merge from vyos.configverify import verify_dhcpv6 from vyos.configverify import verify_mirror +from vyos.configverify import verify_redirect from vyos.configverify import verify_vrf from vyos.ifconfig import BridgeIf from vyos.validate import has_address_configured @@ -107,6 +108,7 @@ def verify(bridge): verify_dhcpv6(bridge) verify_vrf(bridge) verify_mirror(bridge) + verify_redirect(bridge) ifname = bridge['ifname'] diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py index 55c783f38..4072c4452 100755 --- a/src/conf_mode/interfaces-dummy.py +++ b/src/conf_mode/interfaces-dummy.py @@ -21,6 +21,7 @@ from vyos.configdict import get_interface_dict from vyos.configverify import verify_vrf from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_redirect from vyos.ifconfig import DummyIf from vyos import ConfigError from vyos import airbag @@ -46,6 +47,7 @@ def verify(dummy): verify_vrf(dummy) verify_address(dummy) + verify_redirect(dummy) return None diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 2a8a126f2..3eeddf190 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -28,6 +28,7 @@ from vyos.configverify import verify_interface_exists from vyos.configverify import verify_mirror from vyos.configverify import verify_mtu from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_redirect from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf from vyos.ethtool import Ethtool @@ -84,6 +85,7 @@ def verify(ethernet): verify_vrf(ethernet) verify_eapol(ethernet) verify_mirror(ethernet) + verify_redirect(ethernet) ethtool = Ethtool(ifname) # No need to check speed and duplex keys as both have default values. diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py index 2a63b60aa..a94b5e1f7 100755 --- a/src/conf_mode/interfaces-geneve.py +++ b/src/conf_mode/interfaces-geneve.py @@ -24,6 +24,7 @@ from vyos.configdict import get_interface_dict from vyos.configverify import verify_address from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_redirect from vyos.ifconfig import GeneveIf from vyos import ConfigError @@ -50,6 +51,7 @@ def verify(geneve): verify_mtu_ipv6(geneve) verify_address(geneve) + verify_redirect(geneve) if 'remote' not in geneve: raise ConfigError('Remote side must be configured') diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py index 9b6ddd5aa..5ea7159dc 100755 --- a/src/conf_mode/interfaces-l2tpv3.py +++ b/src/conf_mode/interfaces-l2tpv3.py @@ -25,6 +25,7 @@ from vyos.configdict import leaf_node_changed from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_redirect from vyos.ifconfig import L2TPv3If from vyos.util import check_kmod from vyos.validate import is_addr_assigned @@ -76,6 +77,7 @@ def verify(l2tpv3): verify_mtu_ipv6(l2tpv3) verify_address(l2tpv3) + verify_redirect(l2tpv3) return None def generate(l2tpv3): diff --git a/src/conf_mode/interfaces-loopback.py b/src/conf_mode/interfaces-loopback.py index 193334443..e6a851113 100755 --- a/src/conf_mode/interfaces-loopback.py +++ b/src/conf_mode/interfaces-loopback.py @@ -20,6 +20,7 @@ from sys import exit from vyos.config import Config from vyos.configdict import get_interface_dict +from vyos.configverify import verify_redirect from vyos.ifconfig import LoopbackIf from vyos import ConfigError from vyos import airbag @@ -39,6 +40,7 @@ def get_config(config=None): return loopback def verify(loopback): + verify_redirect(loopback) return None def generate(loopback): diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index eab69f36e..6a29fdb11 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -29,6 +29,7 @@ from vyos.configverify import verify_vrf from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_redirect from vyos.configverify import verify_source_interface from vyos import ConfigError from vyos import airbag @@ -66,6 +67,7 @@ def verify(macsec): verify_vrf(macsec) verify_mtu_ipv6(macsec) verify_address(macsec) + verify_redirect(macsec) if not (('security' in macsec) and ('cipher' in macsec['security'])): diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index 584adc75e..9962e0a08 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -28,6 +28,7 @@ from vyos.configverify import verify_source_interface from vyos.configverify import verify_interface_exists from vyos.configverify import verify_vrf from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_redirect from vyos.ifconfig import PPPoEIf from vyos.template import render from vyos.util import call @@ -85,6 +86,7 @@ def verify(pppoe): verify_authentication(pppoe) verify_vrf(pppoe) verify_mtu_ipv6(pppoe) + verify_redirect(pppoe) if {'connect_on_demand', 'vrf'} <= set(pppoe): raise ConfigError('On-demand dialing and VRF can not be used at the same time') diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index 945a2ea9c..f57e41cc4 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -25,6 +25,7 @@ from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_source_interface from vyos.configverify import verify_vlan_config from vyos.configverify import verify_mtu_parent +from vyos.configverify import verify_redirect from vyos.ifconfig import MACVLANIf from vyos import ConfigError @@ -60,6 +61,7 @@ def verify(peth): verify_vrf(peth) verify_address(peth) verify_mtu_parent(peth, peth['parent']) + verify_redirect(peth) # use common function to verify VLAN configuration verify_vlan_config(peth) diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 433764b8a..005fae5eb 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -26,6 +26,7 @@ from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_interface_exists from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_redirect from vyos.configverify import verify_vrf from vyos.configverify import verify_tunnel from vyos.ifconfig import Interface @@ -157,6 +158,7 @@ def verify(tunnel): verify_mtu_ipv6(tunnel) verify_address(tunnel) verify_vrf(tunnel) + verify_redirect(tunnel) if 'source_interface' in tunnel: verify_interface_exists(tunnel['source_interface']) diff --git a/src/conf_mode/interfaces-vti.py b/src/conf_mode/interfaces-vti.py index 57950ffea..30e13536f 100755 --- a/src/conf_mode/interfaces-vti.py +++ b/src/conf_mode/interfaces-vti.py @@ -19,6 +19,7 @@ from sys import exit from vyos.config import Config from vyos.configdict import get_interface_dict +from vyos.configverify import verify_redirect from vyos.ifconfig import VTIIf from vyos.util import dict_search from vyos import ConfigError @@ -39,6 +40,7 @@ def get_config(config=None): return vti def verify(vti): + verify_redirect(vti) return None def generate(vti): diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 29b16af89..a29836efd 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -25,6 +25,7 @@ from vyos.configdict import leaf_node_changed from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_redirect from vyos.configverify import verify_source_interface from vyos.ifconfig import Interface from vyos.ifconfig import VXLANIf @@ -140,6 +141,7 @@ def verify(vxlan): verify_mtu_ipv6(vxlan) verify_address(vxlan) + verify_redirect(vxlan) return None def generate(vxlan): diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index da64dd076..dc0fe7b9c 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -28,6 +28,7 @@ from vyos.configverify import verify_vrf from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_redirect from vyos.ifconfig import WireGuardIf from vyos.util import check_kmod from vyos.util import check_port_availability @@ -70,6 +71,7 @@ def verify(wireguard): verify_mtu_ipv6(wireguard) verify_address(wireguard) verify_vrf(wireguard) + verify_redirect(wireguard) if 'private_key' not in wireguard: raise ConfigError('Wireguard private-key not defined') diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index af35b5f03..fdf9e3988 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -27,6 +27,7 @@ from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_dhcpv6 from vyos.configverify import verify_source_interface +from vyos.configverify import verify_redirect from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf from vyos.ifconfig import WiFiIf @@ -189,6 +190,7 @@ def verify(wifi): verify_address(wifi) verify_vrf(wifi) + verify_redirect(wifi) # use common function to verify VLAN configuration verify_vlan_config(wifi) diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py index a4b033374..367a50e82 100755 --- a/src/conf_mode/interfaces-wwan.py +++ b/src/conf_mode/interfaces-wwan.py @@ -23,6 +23,7 @@ from vyos.config import Config from vyos.configdict import get_interface_dict from vyos.configverify import verify_authentication from vyos.configverify import verify_interface_exists +from vyos.configverify import verify_redirect from vyos.configverify import verify_vrf from vyos.ifconfig import WWANIf from vyos.util import cmd @@ -77,6 +78,7 @@ def verify(wwan): verify_interface_exists(ifname) verify_authentication(wwan) verify_vrf(wwan) + verify_redirect(wwan) return None diff --git a/src/conf_mode/qos.py b/src/conf_mode/qos.py new file mode 100755 index 000000000..cf447d4b5 --- /dev/null +++ b/src/conf_mode/qos.py @@ -0,0 +1,90 @@ +#!/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 . + +from sys import exit + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.xml import defaults +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['traffic-policy'] + if not conf.exists(base): + return None + + qos = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + for traffic_policy in ['drop-tail', 'fair-queue', 'fq-codel', 'limiter', + 'network-emulator', 'priority-queue', 'random-detect', + 'rate-control', 'round-robin', 'shaper', 'shaper-hfsc']: + traffic_policy_us = traffic_policy.replace('-','_') + # Individual policy type not present on CLI - no need to blend in + # any default values + if traffic_policy_us not in qos: + continue + + default_values = defaults(base + [traffic_policy_us]) + + # class is another tag node which requires individual handling + class_default_values = defaults(base + [traffic_policy_us, 'class']) + if 'class' in default_values: + del default_values['class'] + + for policy, policy_config in qos[traffic_policy_us].items(): + qos[traffic_policy_us][policy] = dict_merge( + default_values, qos[traffic_policy_us][policy]) + + if 'class' in policy_config: + for policy_class in policy_config['class']: + qos[traffic_policy_us][policy]['class'][policy_class] = dict_merge( + class_default_values, qos[traffic_policy_us][policy]['class'][policy_class]) + + import pprint + pprint.pprint(qos) + return qos + +def verify(qos): + if not qos: + return None + + # network policy emulator + # reorder rerquires delay to be set + + raise ConfigError('123') + return None + +def generate(qos): + return None + +def apply(qos): + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) -- cgit v1.2.3 From c37829f1e902b84a5bc3bc5618ee97ae1ba0dd86 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Tue, 22 Mar 2022 04:50:34 -0400 Subject: T4313: handle exceptions in the "generate public-key-command" script --- src/op_mode/generate_public_key_command.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/op_mode/generate_public_key_command.py b/src/op_mode/generate_public_key_command.py index 7a7b6c923..f071ae350 100755 --- a/src/op_mode/generate_public_key_command.py +++ b/src/op_mode/generate_public_key_command.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 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 @@ -29,8 +29,12 @@ def get_key(path): key_string = vyos.remote.get_remote_config(path) return key_string.split() -username = sys.argv[1] -algorithm, key, identifier = get_key(sys.argv[2]) +try: + username = sys.argv[1] + algorithm, key, identifier = get_key(sys.argv[2]) +except Exception as e: + print("Failed to retrieve the public key: {}".format(e)) + sys.exit(1) print('# To add this key as an embedded key, run the following commands:') print('configure') @@ -39,3 +43,4 @@ print(f'set system login user {username} authentication public-keys {identifier} print('commit') print('save') print('exit') + -- cgit v1.2.3 From d193e5cb9040bfca5011400acef601e8c7111346 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 23 Mar 2022 08:42:40 -0500 Subject: bgp: T4314: add missing check to migration script --- src/migration-scripts/bgp/0-to-1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/migration-scripts/bgp/0-to-1 b/src/migration-scripts/bgp/0-to-1 index b1d5a6514..5e9dffe1f 100755 --- a/src/migration-scripts/bgp/0-to-1 +++ b/src/migration-scripts/bgp/0-to-1 @@ -33,7 +33,7 @@ with open(file_name, 'r') as f: base = ['protocols', 'bgp'] config = ConfigTree(config_file) -if not config.exists(base): +if not config.exists(base) or not config.is_tag(base): # Nothing to do exit(0) -- cgit v1.2.3 From 78a4676f787e5e37f67afd5c2453ce06e3f0f9e9 Mon Sep 17 00:00:00 2001 From: srividya0208 Date: Fri, 18 Mar 2022 08:39:14 -0400 Subject: ike-group: T4288 : close-action is missing in swanctl.conf close-action parameter is missing in the swanctl.conf file --- data/templates/ipsec/swanctl/peer.tmpl | 6 +++-- interface-definitions/vpn_ipsec.xml.in | 8 ++---- src/migration-scripts/ipsec/8-to-9 | 49 ++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 8 deletions(-) create mode 100755 src/migration-scripts/ipsec/8-to-9 (limited to 'src') diff --git a/data/templates/ipsec/swanctl/peer.tmpl b/data/templates/ipsec/swanctl/peer.tmpl index 562e8fdd5..a622cbf74 100644 --- a/data/templates/ipsec/swanctl/peer.tmpl +++ b/data/templates/ipsec/swanctl/peer.tmpl @@ -87,9 +87,10 @@ start_action = none {% endif %} {% if ike.dead_peer_detection is defined %} -{% set dpd_translate = {'clear': 'clear', 'hold': 'trap', 'restart': 'start'} %} +{% set dpd_translate = {'clear': 'clear', 'hold': 'trap', 'restart': 'restart'} %} dpd_action = {{ dpd_translate[ike.dead_peer_detection.action] }} {% endif %} + close_action = {{ {'none': 'none', 'hold': 'trap', 'restart': 'start'}[ike.close_action] }} } {% elif peer_conf.tunnel is defined %} {% for tunnel_id, tunnel_conf in peer_conf.tunnel.items() if tunnel_conf.disable is not defined %} @@ -137,9 +138,10 @@ start_action = none {% endif %} {% if ike.dead_peer_detection is defined %} -{% set dpd_translate = {'clear': 'clear', 'hold': 'trap', 'restart': 'start'} %} +{% set dpd_translate = {'clear': 'clear', 'hold': 'trap', 'restart': 'restart'} %} dpd_action = {{ dpd_translate[ike.dead_peer_detection.action] }} {% endif %} + close_action = {{ {'none': 'none', 'hold': 'trap', 'restart': 'start'}[ike.close_action] }} {% if peer_conf.vti is defined and peer_conf.vti.bind is defined %} updown = "/etc/ipsec.d/vti-up-down {{ peer_conf.vti.bind }}" {# The key defaults to 0 and will match any policies which similarly do not have a lookup key configuration. #} diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in index d8c06a310..a86951ce8 100644 --- a/interface-definitions/vpn_ipsec.xml.in +++ b/interface-definitions/vpn_ipsec.xml.in @@ -231,7 +231,7 @@ Action to take if a child SA is unexpectedly closed - none hold clear restart + none hold restart none @@ -241,16 +241,12 @@ hold Attempt to re-negotiate when matching traffic is seen - - clear - Remove the connection immediately - restart Attempt to re-negotiate the connection immediately - ^(none|hold|clear|restart)$ + ^(none|hold|restart)$ diff --git a/src/migration-scripts/ipsec/8-to-9 b/src/migration-scripts/ipsec/8-to-9 new file mode 100755 index 000000000..209cd8ac9 --- /dev/null +++ b/src/migration-scripts/ipsec/8-to-9 @@ -0,0 +1,49 @@ + +#!/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 . + +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 = ['vpn', 'ipsec', 'ike-group'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) +else: + for ike_group in config.list_nodes(base): + base_closeaction = base + [ike_group, 'close-action'] + if config.exists(base_closeaction) and config.return_value(base_closeaction) == 'clear': + config.set(base_closeaction, 'none', replace=True) + +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) -- cgit v1.2.3 From 999b1e50dfdea8694174e82d22b2438cb1bf5e28 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 24 Mar 2022 17:42:59 +0100 Subject: openvpn: T4294: force service restart on openvpn-option node change --- src/conf_mode/interfaces-openvpn.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 29a25eedc..c30c0bdd0 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -32,6 +32,7 @@ from shutil import rmtree from vyos.config import Config from vyos.configdict import get_interface_dict +from vyos.configdict import leaf_node_changed from vyos.configverify import verify_vrf from vyos.configverify import verify_bridge_delete from vyos.ifconfig import VTunIf @@ -88,6 +89,9 @@ def get_config(config=None): if 'deleted' not in openvpn: openvpn['pki'] = tmp_pki + tmp = leaf_node_changed(conf, ['openvpn-option']) + if tmp: openvpn['restart_required'] = '' + # We have to get the dict using 'get_config_dict' instead of 'get_interface_dict' # as 'get_interface_dict' merges the defaults in, so we can not check for defaults in there. tmp = conf.get_config_dict(base + [openvpn['ifname']], get_first_key=True) @@ -651,7 +655,10 @@ def apply(openvpn): # No matching OpenVPN process running - maybe it got killed or none # existed - nevertheless, spawn new OpenVPN process - call(f'systemctl reload-or-restart openvpn@{interface}.service') + action = 'reload-or-restart' + if 'restart_required' in openvpn: + action = 'restart' + call(f'systemctl {action} openvpn@{interface}.service') o = VTunIf(**openvpn) o.update(openvpn) -- cgit v1.2.3 From cb499ee9d467d31f9a75a76f668c9ca0d8a3484f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 24 Mar 2022 18:28:49 +0100 Subject: ipsec: T4288: drop leading empty line to detect runtime environment --- src/migration-scripts/ipsec/8-to-9 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src') diff --git a/src/migration-scripts/ipsec/8-to-9 b/src/migration-scripts/ipsec/8-to-9 index 209cd8ac9..eb44b6216 100755 --- a/src/migration-scripts/ipsec/8-to-9 +++ b/src/migration-scripts/ipsec/8-to-9 @@ -1,7 +1,6 @@ - #!/usr/bin/env python3 # -# Copyright (C) 2021 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 -- cgit v1.2.3 From 1b16a4eab926462c0d2752d698bedf28c995058d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Mar 2022 18:55:32 +0100 Subject: system: T4319: align ipv6 settings with ipv4 by using get_config_dict() --- src/conf_mode/system-ip.py | 43 +++++++++--------- src/conf_mode/system-ipv6.py | 103 +++++++++++++++++++------------------------ 2 files changed, 66 insertions(+), 80 deletions(-) (limited to 'src') diff --git a/src/conf_mode/system-ip.py b/src/conf_mode/system-ip.py index 32cb2f036..8b97725ac 100755 --- a/src/conf_mode/system-ip.py +++ b/src/conf_mode/system-ip.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2019-2022 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -20,14 +20,13 @@ from vyos.config import Config from vyos.configdict import dict_merge from vyos.util import call from vyos.util import dict_search +from vyos.util import sysctl +from vyos.util import write_file from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() -def sysctl(name, value): - call(f'sysctl -wq {name}={value}') - def get_config(config=None): if config: conf = config @@ -50,29 +49,29 @@ def generate(opt): pass def apply(opt): + # Apply ARP threshold values + # table_size has a default value - thus the key always exists size = int(dict_search('arp.table_size', opt)) - if size: - # apply ARP threshold values - sysctl('net.ipv4.neigh.default.gc_thresh3', str(size)) - sysctl('net.ipv4.neigh.default.gc_thresh2', str(size // 2)) - sysctl('net.ipv4.neigh.default.gc_thresh1', str(size // 8)) + # Amount upon reaching which the records begin to be cleared immediately + sysctl('net.ipv4.neigh.default.gc_thresh3', size) + # Amount after which the records begin to be cleaned after 5 seconds + sysctl('net.ipv4.neigh.default.gc_thresh2', size // 2) + # Minimum number of stored records is indicated which is not cleared + sysctl('net.ipv4.neigh.default.gc_thresh1', size // 8) # enable/disable IPv4 forwarding - tmp = '1' - if 'disable_forwarding' in opt: - tmp = '0' - sysctl('net.ipv4.conf.all.forwarding', tmp) + tmp = dict_search('disable_forwarding', opt) + value = '0' if (tmp != None) else '1' + write_file('/proc/sys/net/ipv4/conf/all/forwarding', value) - tmp = '0' - # configure multipath - dict_search() returns an empty dict if key was found - if isinstance(dict_search('multipath.ignore_unreachable_nexthops', opt), dict): - tmp = '1' - sysctl('net.ipv4.fib_multipath_use_neigh', tmp) + # configure multipath + tmp = dict_search('multipath.ignore_unreachable_nexthops', opt) + value = '1' if (tmp != None) else '0' + sysctl('net.ipv4.fib_multipath_use_neigh', value) - tmp = '0' - if isinstance(dict_search('multipath.layer4_hashing', opt), dict): - tmp = '1' - sysctl('net.ipv4.fib_multipath_hash_policy', tmp) + tmp = dict_search('multipath.layer4_hashing', opt) + value = '1' if (tmp != None) else '0' + sysctl('net.ipv4.fib_multipath_hash_policy', value) if __name__ == '__main__': try: diff --git a/src/conf_mode/system-ipv6.py b/src/conf_mode/system-ipv6.py index f70ec2631..8195beaa6 100755 --- a/src/conf_mode/system-ipv6.py +++ b/src/conf_mode/system-ipv6.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019 VyOS maintainers and contributors +# Copyright (C) 2019-2022 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -15,95 +15,82 @@ # along with this program. If not, see . import os -import sys from sys import exit -from copy import deepcopy from vyos.config import Config -from vyos import ConfigError +from vyos.configdict import dict_merge +from vyos.configdict import leaf_node_changed from vyos.util import call - +from vyos.util import dict_search +from vyos.util import sysctl +from vyos.util import write_file +from vyos.xml import defaults +from vyos import ConfigError from vyos import airbag airbag.enable() -ipv6_disable_file = '/etc/modprobe.d/vyos_disable_ipv6.conf' - -default_config_data = { - 'reboot_message': False, - 'ipv6_forward': '1', - 'disable_addr_assignment': False, - 'mp_layer4_hashing': '0', - 'neighbor_cache': 8192, - 'strict_dad': '1' - -} - -def sysctl(name, value): - call('sysctl -wq {}={}'.format(name, value)) - def get_config(config=None): - ip_opt = deepcopy(default_config_data) if config: conf = config else: conf = Config() - conf.set_level('system ipv6') - if conf.exists(''): - ip_opt['disable_addr_assignment'] = conf.exists('disable') - if conf.exists_effective('disable') != conf.exists('disable'): - ip_opt['reboot_message'] = True - - if conf.exists('disable-forwarding'): - ip_opt['ipv6_forward'] = '0' + base = ['system', 'ipv6'] - if conf.exists('multipath layer4-hashing'): - ip_opt['mp_layer4_hashing'] = '1' + opt = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - if conf.exists('neighbor table-size'): - ip_opt['neighbor_cache'] = int(conf.return_value('neighbor table-size')) + tmp = leaf_node_changed(conf, base + ['disable']) + if tmp: opt['reboot_required'] = {} - if conf.exists('strict-dad'): - ip_opt['strict_dad'] = 2 + # 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) + opt = dict_merge(default_values, opt) - return ip_opt + return opt -def verify(ip_opt): +def verify(opt): pass -def generate(ip_opt): +def generate(opt): pass -def apply(ip_opt): - # disable IPv6 address assignment - if ip_opt['disable_addr_assignment']: - with open(ipv6_disable_file, 'w') as f: - f.write('options ipv6 disable_ipv6=1') - else: - if os.path.exists(ipv6_disable_file): - os.unlink(ipv6_disable_file) +def apply(opt): + # disable IPv6 globally + tmp = dict_search('disable', opt) + value = '1' if (tmp != None) else '0' + sysctl('net.ipv6.conf.all.disable_ipv6', value) - if ip_opt['reboot_message']: + if 'reboot_required' in opt: print('Changing IPv6 disable parameter will only take affect\n' \ 'when the system is rebooted.') # configure multipath - sysctl('net.ipv6.fib_multipath_hash_policy', ip_opt['mp_layer4_hashing']) - - # apply neighbor table threshold values - sysctl('net.ipv6.neigh.default.gc_thresh3', ip_opt['neighbor_cache']) - sysctl('net.ipv6.neigh.default.gc_thresh2', ip_opt['neighbor_cache'] // 2) - sysctl('net.ipv6.neigh.default.gc_thresh1', ip_opt['neighbor_cache'] // 8) + tmp = dict_search('multipath.layer4_hashing', opt) + value = '1' if (tmp != None) else '0' + sysctl('net.ipv6.fib_multipath_hash_policy', value) + + # Apply ND threshold values + # table_size has a default value - thus the key always exists + size = int(dict_search('neighbor.table_size', opt)) + # Amount upon reaching which the records begin to be cleared immediately + sysctl('net.ipv6.neigh.default.gc_thresh3', size) + # Amount after which the records begin to be cleaned after 5 seconds + sysctl('net.ipv6.neigh.default.gc_thresh2', size // 2) + # Minimum number of stored records is indicated which is not cleared + sysctl('net.ipv6.neigh.default.gc_thresh1', size // 8) # enable/disable IPv6 forwarding - with open('/proc/sys/net/ipv6/conf/all/forwarding', 'w') as f: - f.write(ip_opt['ipv6_forward']) + tmp = dict_search('disable_forwarding', opt) + value = '0' if (tmp != None) else '1' + write_file('/proc/sys/net/ipv6/conf/all/forwarding', value) # configure IPv6 strict-dad + tmp = dict_search('strict_dad', opt) + value = '2' if (tmp != None) else '1' for root, dirs, files in os.walk('/proc/sys/net/ipv6/conf'): for name in files: - if name == "accept_dad": - with open(os.path.join(root, name), 'w') as f: - f.write(str(ip_opt['strict_dad'])) + if name == 'accept_dad': + write_file(os.path.join(root, name), value) if __name__ == '__main__': try: -- cgit v1.2.3 From 364009e4317fb5c6732635726b511613aa2ed519 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Mar 2022 19:00:36 +0100 Subject: vyos.util: T4319: rename sysctl() -> sysctl_write() --- python/vyos/util.py | 2 +- src/conf_mode/system-ip.py | 12 ++++++------ src/conf_mode/system-ipv6.py | 12 ++++++------ src/conf_mode/vrf.py | 6 +++--- 4 files changed, 16 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/python/vyos/util.py b/python/vyos/util.py index da39ee8d1..f46775490 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -1011,7 +1011,7 @@ def sysctl_read(name): tmp = cmd(f'sysctl {name}') return tmp.split()[-1] -def sysctl(name, value): +def sysctl_write(name, value): """ Change value via sysctl() - return True if changed, False otherwise """ tmp = cmd(f'sysctl {name}') # last list index contains the actual value - only write if value differs diff --git a/src/conf_mode/system-ip.py b/src/conf_mode/system-ip.py index 8b97725ac..05fc3a97a 100755 --- a/src/conf_mode/system-ip.py +++ b/src/conf_mode/system-ip.py @@ -20,7 +20,7 @@ from vyos.config import Config from vyos.configdict import dict_merge from vyos.util import call from vyos.util import dict_search -from vyos.util import sysctl +from vyos.util import sysctl_write from vyos.util import write_file from vyos.xml import defaults from vyos import ConfigError @@ -53,11 +53,11 @@ def apply(opt): # table_size has a default value - thus the key always exists size = int(dict_search('arp.table_size', opt)) # Amount upon reaching which the records begin to be cleared immediately - sysctl('net.ipv4.neigh.default.gc_thresh3', size) + sysctl_write('net.ipv4.neigh.default.gc_thresh3', size) # Amount after which the records begin to be cleaned after 5 seconds - sysctl('net.ipv4.neigh.default.gc_thresh2', size // 2) + sysctl_write('net.ipv4.neigh.default.gc_thresh2', size // 2) # Minimum number of stored records is indicated which is not cleared - sysctl('net.ipv4.neigh.default.gc_thresh1', size // 8) + sysctl_write('net.ipv4.neigh.default.gc_thresh1', size // 8) # enable/disable IPv4 forwarding tmp = dict_search('disable_forwarding', opt) @@ -67,11 +67,11 @@ def apply(opt): # configure multipath tmp = dict_search('multipath.ignore_unreachable_nexthops', opt) value = '1' if (tmp != None) else '0' - sysctl('net.ipv4.fib_multipath_use_neigh', value) + sysctl_write('net.ipv4.fib_multipath_use_neigh', value) tmp = dict_search('multipath.layer4_hashing', opt) value = '1' if (tmp != None) else '0' - sysctl('net.ipv4.fib_multipath_hash_policy', value) + sysctl_write('net.ipv4.fib_multipath_hash_policy', value) if __name__ == '__main__': try: diff --git a/src/conf_mode/system-ipv6.py b/src/conf_mode/system-ipv6.py index 8195beaa6..7fb2dd1cf 100755 --- a/src/conf_mode/system-ipv6.py +++ b/src/conf_mode/system-ipv6.py @@ -22,7 +22,7 @@ from vyos.configdict import dict_merge from vyos.configdict import leaf_node_changed from vyos.util import call from vyos.util import dict_search -from vyos.util import sysctl +from vyos.util import sysctl_write from vyos.util import write_file from vyos.xml import defaults from vyos import ConfigError @@ -58,7 +58,7 @@ def apply(opt): # disable IPv6 globally tmp = dict_search('disable', opt) value = '1' if (tmp != None) else '0' - sysctl('net.ipv6.conf.all.disable_ipv6', value) + sysctl_write('net.ipv6.conf.all.disable_ipv6', value) if 'reboot_required' in opt: print('Changing IPv6 disable parameter will only take affect\n' \ @@ -67,17 +67,17 @@ def apply(opt): # configure multipath tmp = dict_search('multipath.layer4_hashing', opt) value = '1' if (tmp != None) else '0' - sysctl('net.ipv6.fib_multipath_hash_policy', value) + sysctl_write('net.ipv6.fib_multipath_hash_policy', value) # Apply ND threshold values # table_size has a default value - thus the key always exists size = int(dict_search('neighbor.table_size', opt)) # Amount upon reaching which the records begin to be cleared immediately - sysctl('net.ipv6.neigh.default.gc_thresh3', size) + sysctl_write('net.ipv6.neigh.default.gc_thresh3', size) # Amount after which the records begin to be cleaned after 5 seconds - sysctl('net.ipv6.neigh.default.gc_thresh2', size // 2) + sysctl_write('net.ipv6.neigh.default.gc_thresh2', size // 2) # Minimum number of stored records is indicated which is not cleared - sysctl('net.ipv6.neigh.default.gc_thresh1', size // 8) + sysctl_write('net.ipv6.neigh.default.gc_thresh1', size // 8) # enable/disable IPv6 forwarding tmp = dict_search('disable_forwarding', opt) diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index cfe0f4d8e..6a521a0dd 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -29,7 +29,7 @@ from vyos.util import dict_search from vyos.util import get_interface_config from vyos.util import popen from vyos.util import run -from vyos.util import sysctl +from vyos.util import sysctl_write from vyos import ConfigError from vyos import frr from vyos import airbag @@ -154,8 +154,8 @@ def apply(vrf): bind_all = '0' if 'bind-to-all' in vrf: bind_all = '1' - sysctl('net.ipv4.tcp_l3mdev_accept', bind_all) - sysctl('net.ipv4.udp_l3mdev_accept', bind_all) + sysctl_write('net.ipv4.tcp_l3mdev_accept', bind_all) + sysctl_write('net.ipv4.udp_l3mdev_accept', bind_all) for tmp in (dict_search('vrf_remove', vrf) or []): if os.path.isdir(f'/sys/class/net/{tmp}'): -- cgit v1.2.3 From cabe0c06e2312cc872d3e22d91611a3ccecefdb0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Mar 2022 19:08:56 +0100 Subject: mpls: T915: use vyos.util.sysctl_write() helper function --- src/conf_mode/protocols_mpls.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py index 0b0c7d07b..933e23065 100755 --- a/src/conf_mode/protocols_mpls.py +++ b/src/conf_mode/protocols_mpls.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -20,11 +20,10 @@ from sys import exit from glob import glob from vyos.config import Config -from vyos.configdict import node_changed from vyos.template import render_to_string -from vyos.util import call from vyos.util import dict_search from vyos.util import read_file +from vyos.util import sysctl_write from vyos import ConfigError from vyos import frr from vyos import airbag @@ -89,21 +88,21 @@ def apply(mpls): labels = '0' if 'interface' in mpls: labels = '1048575' - call(f'sysctl -wq net.mpls.platform_labels={labels}') + sysctl_write('net.mpls.platform_labels', labels) # Check for changes in global MPLS options if 'parameters' in mpls: # Choose whether to copy IP TTL to MPLS header TTL if 'no_propagate_ttl' in mpls['parameters']: - call('sysctl -wq net.mpls.ip_ttl_propagate=0') + sysctl_write('net.mpls.ip_ttl_propagate', 0) # Choose whether to limit maximum MPLS header TTL if 'maximum_ttl' in mpls['parameters']: ttl = mpls['parameters']['maximum_ttl'] - call(f'sysctl -wq net.mpls.default_ttl={ttl}') + sysctl_write('net.mpls.default_ttl', ttl) else: # Set default global MPLS options if not defined. - call('sysctl -wq net.mpls.ip_ttl_propagate=1') - call('sysctl -wq net.mpls.default_ttl=255') + sysctl_write('net.mpls.ip_ttl_propagate', 1) + sysctl_write('net.mpls.default_ttl', 255) # Enable and disable MPLS processing on interfaces per configuration if 'interface' in mpls: @@ -117,11 +116,11 @@ def apply(mpls): if '1' in interface_state: if system_interface not in mpls['interface']: system_interface = system_interface.replace('.', '/') - call(f'sysctl -wq net.mpls.conf.{system_interface}.input=0') + sysctl_write(f'net.mpls.conf.{system_interface}.input', 0) elif '0' in interface_state: if system_interface in mpls['interface']: system_interface = system_interface.replace('.', '/') - call(f'sysctl -wq net.mpls.conf.{system_interface}.input=1') + sysctl_write(f'net.mpls.conf.{system_interface}.input', 1) else: system_interfaces = [] # If MPLS interfaces are not configured, set MPLS processing disabled @@ -129,7 +128,7 @@ def apply(mpls): system_interfaces.append(os.path.basename(interface)) for system_interface in system_interfaces: system_interface = system_interface.replace('.', '/') - call(f'sysctl -wq net.mpls.conf.{system_interface}.input=0') + sysctl_write(f'net.mpls.conf.{system_interface}.input', 0) return None -- cgit v1.2.3 From 772d05156aaa75c904fe340cfce024da00f187f4 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 26 Mar 2022 09:25:02 +0100 Subject: bgp: T4321: check neighbor IP addresses against VRF context --- src/conf_mode/protocols_bgp.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 9e59177a8..64b113873 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -159,8 +159,14 @@ def verify(bgp): # Only checks for ipv4 and ipv6 neighbors # Check if neighbor address is assigned as system interface address - if is_ip(peer) and is_addr_assigned(peer): - raise ConfigError(f'Can not configure a local address as neighbor "{peer}"') + vrf = None + vrf_error_msg = f' in default VRF!' + if 'vrf' in bgp: + vrf = bgp['vrf'] + vrf_error_msg = f' in VRF "{vrf}"!' + + if is_ip(peer) and is_addr_assigned(peer, vrf): + raise ConfigError(f'Can not configure local address as neighbor "{peer}"{vrf_error_msg}') elif is_interface(peer): if 'peer_group' in peer_config: raise ConfigError(f'peer-group must be set under the interface node of "{peer}"') -- cgit v1.2.3 From 9d3acc2b55f2d1c563f1941e59c98c159211dc58 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Sun, 27 Mar 2022 01:30:21 -0500 Subject: graphql: T3993: add unsettable gql option; this is not exposed by CLI --- python/vyos/defaults.py | 1 + src/conf_mode/http-api.py | 9 +++++++++ src/services/vyos-http-api-server | 4 +++- 3 files changed, 13 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index c77b695bd..fcb6a7fbc 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -48,6 +48,7 @@ api_data = { 'port' : '8080', 'socket' : False, 'strict' : False, + 'gql' : False, 'debug' : False, 'api_keys' : [ {"id": "testapp", "key": "qwerty"} ] } diff --git a/src/conf_mode/http-api.py b/src/conf_mode/http-api.py index b5f5e919f..00f3d4f7f 100755 --- a/src/conf_mode/http-api.py +++ b/src/conf_mode/http-api.py @@ -66,6 +66,15 @@ def get_config(config=None): if conf.exists('debug'): http_api['debug'] = True + # this node is not available by CLI by default, and is reserved for + # the graphql tools. One can enable it for testing, with the warning + # that this will open an unauthenticated server. To do so + # mkdir /opt/vyatta/share/vyatta-cfg/templates/service/https/api/gql + # touch /opt/vyatta/share/vyatta-cfg/templates/service/https/api/gql/node.def + # and configure; editing the config alone is insufficient. + if conf.exists('gql'): + http_api['gql'] = True + if conf.exists('socket'): http_api['socket'] = True diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index 06871f1d6..1000d8b72 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -648,10 +648,12 @@ if __name__ == '__main__': app.state.vyos_keys = server_config['api_keys'] app.state.vyos_debug = server_config['debug'] + app.state.vyos_gql = server_config['gql'] app.state.vyos_strict = server_config['strict'] app.state.vyos_origins = server_config.get('cors', {}).get('origins', []) - graphql_init(app) + if app.state.vyos_gql: + graphql_init(app) try: if not server_config['socket']: -- cgit v1.2.3 From 60f093464692f08c1c32c9e31513a6ae98636617 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Mon, 28 Mar 2022 10:46:30 +0300 Subject: Revert "openvpn: T4230: globally enable ip_nonlocal_bind" This reverts commit 1cbcbf40b7721849f9696c05fac65db010a66b7c. --- src/conf_mode/interfaces-openvpn.py | 7 +++++++ src/etc/sysctl.d/33-vyos-nonlocal-bind.conf | 8 -------- 2 files changed, 7 insertions(+), 8 deletions(-) delete mode 100644 src/etc/sysctl.d/33-vyos-nonlocal-bind.conf (limited to 'src') diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index c30c0bdd0..8f9c0b3f1 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -653,6 +653,13 @@ def apply(openvpn): return None + # verify specified IP address is present on any interface on this system + # Allow to bind service to nonlocal address, if it virtaual-vrrp address + # or if address will be assign later + if 'local_host' in openvpn: + if not is_addr_assigned(openvpn['local_host']): + cmd('sysctl -w net.ipv4.ip_nonlocal_bind=1') + # No matching OpenVPN process running - maybe it got killed or none # existed - nevertheless, spawn new OpenVPN process action = 'reload-or-restart' diff --git a/src/etc/sysctl.d/33-vyos-nonlocal-bind.conf b/src/etc/sysctl.d/33-vyos-nonlocal-bind.conf deleted file mode 100644 index aa81b5336..000000000 --- a/src/etc/sysctl.d/33-vyos-nonlocal-bind.conf +++ /dev/null @@ -1,8 +0,0 @@ -### Added by vyos-1x ### -# -# ip_nonlocal_bind - BOOLEAN -# If set, allows processes to bind() to non-local IP addresses, -# which can be quite useful - but may break some applications. -# Default: 0 -net.ipv4.ip_nonlocal_bind = 1 -net.ipv6.ip_nonlocal_bind = 1 -- cgit v1.2.3