From e2259e25029a142ba607b5865337b38ff8a482aa Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Thu, 4 Aug 2022 13:04:30 +0000 Subject: utils: T4594: Add convert_data util Convert multiple types of data to types usable in CLI For example 'vici' returns values in bytestring/bytes and we can decode them all at once --- python/vyos/util.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/python/vyos/util.py b/python/vyos/util.py index b86b1949c..8df9ef7d6 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -800,6 +800,32 @@ def dict_search_recursive(dict_object, key, path=[]): for x in dict_search_recursive(j, key, new_path): yield x +def convert_data(data): + """Convert multiple types of data to types usable in CLI + + Args: + data (str | bytes | list | OrderedDict): input data + + Returns: + str | list | dict: converted data + """ + from collections import OrderedDict + + if isinstance(data, str): + return data + if isinstance(data, bytes): + return data.decode() + if isinstance(data, list): + list_tmp = [] + for item in data: + list_tmp.append(convert_data(item)) + return list_tmp + if isinstance(data, OrderedDict): + dict_tmp = {} + for key, value in data.items(): + dict_tmp[key] = convert_data(value) + return dict_tmp + def get_bridge_fdb(interface): """ Returns the forwarding database entries for a given interface """ if not os.path.exists(f'/sys/class/net/{interface}'): -- cgit v1.2.3 From 2131309c162065ebfdd99d58ebb3e2d377dfed67 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Thu, 4 Aug 2022 13:08:22 +0000 Subject: graphql: T4544: Add ipsec.py to op-mode-standardized.json Add overwritten script 'ipsec.py' to 'op-mode-standardized.json' --- data/op-mode-standardized.json | 1 + 1 file changed, 1 insertion(+) diff --git a/data/op-mode-standardized.json b/data/op-mode-standardized.json index b5e9308c5..90ef804f3 100644 --- a/data/op-mode-standardized.json +++ b/data/op-mode-standardized.json @@ -7,6 +7,7 @@ "nat.py", "neighbor.py", "route.py", +"ipsec.py", "version.py", "vrf.py" ] -- cgit v1.2.3 From dcf89afba457fd4e4d4f764484f329c19d8ed554 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Thu, 4 Aug 2022 13:10:03 +0000 Subject: ipsec: T4594: Rewrite op-mode show vpn ipsec sa Rewrite op-mode "show vpn ipsec sa" to new format Use vyos.opmode format Ability to get raw and formatted output --- op-mode-definitions/vpn-ipsec.xml.in | 2 +- src/op_mode/ipsec.py | 116 +++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 1 deletion(-) diff --git a/op-mode-definitions/vpn-ipsec.xml.in b/op-mode-definitions/vpn-ipsec.xml.in index a98cf8ff2..8c9e76651 100644 --- a/op-mode-definitions/vpn-ipsec.xml.in +++ b/op-mode-definitions/vpn-ipsec.xml.in @@ -187,7 +187,7 @@ if pgrep charon >/dev/null ; then sudo /usr/sbin/ipsec statusall ; else echo "IPSec process not running" ; fi - if pgrep charon >/dev/null ; then sudo ${vyos_op_scripts_dir}/show_ipsec_sa.py ; else echo "IPSec process not running" ; fi + if pgrep charon >/dev/null ; then sudo ${vyos_op_scripts_dir}/ipsec.py show_sa ; else echo "IPSec process not running" ; fi diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py index 432856585..caa1eef51 100755 --- a/src/op_mode/ipsec.py +++ b/src/op_mode/ipsec.py @@ -16,13 +16,122 @@ import re import sys + +from collections import OrderedDict +from hurry import filesize +from re import split as re_split +from tabulate import tabulate + from vyos.util import call +from vyos.util import convert_data +from vyos.util import seconds_to_human + import vyos.opmode SWANCTL_CONF = '/etc/swanctl/swanctl.conf' +def _convert(text): + return int(text) if text.isdigit() else text.lower() + + +def _alphanum_key(key): + return [_convert(c) for c in re_split('([0-9]+)', str(key))] + + +def _get_vici_sas(): + from vici import Session as vici_session + + session = vici_session() + sas = list(session.list_sas()) + return sas + + +def _get_raw_data_sas(): + get_sas = _get_vici_sas() + sas = convert_data(get_sas) + return sas + + +def _get_formatted_output_sas(sas): + sa_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 + if sa_state: + if sa_state == 'INSTALLED': + sa_out_state = 'up' + else: + sa_out_state = 'down' + if sa_uptime: + sa_out_uptime = seconds_to_human(sa_uptime) + if sa_bytes_in and sa_bytes_out: + bytes_in = filesize.size(int(sa_bytes_in)) + bytes_out = filesize.size(int(sa_bytes_out)) + sa_out_bytes = f'{bytes_in}/{bytes_out}' + if sa_packets_in and sa_packets_out: + packets_in = filesize.size(int(sa_packets_in), + system=filesize.si) + packets_out = filesize.size(int(sa_packets_out), + system=filesize.si) + packets_str = f'{packets_in}/{packets_out}' + sa_out_packets = re.sub(r'B', r'', packets_str) + if sa_remote_addr: + sa_out_remote_addr = sa_remote_addr + if sa_remote_id: + sa_out_remote_id = sa_remote_id + # format proposal + if sa_proposal_encr_alg: + sa_out_proposal = sa_proposal_encr_alg + if sa_proposal_encr_keysize: + sa_proposal_encr_keysize_str = sa_proposal_encr_keysize + 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 + 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 + 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 + ]) + + headers = [ + "Connection", "State", "Uptime", "Bytes In/Out", "Packets In/Out", + "Remote address", "Remote ID", "Proposal" + ] + sa_data = sorted(sa_data, key=_alphanum_key) + output = tabulate(sa_data, headers) + return output + + def get_peer_connections(peer, tunnel, return_all = False): peer = peer.replace(':', '-') search = rf'^[\s]*(peer_{peer}_(tunnel_[\d]+|vti)).*' @@ -61,6 +170,13 @@ def reset_peer(peer: str, tunnel:str): print('Peer reset result: ' + ('success' if result else 'failed')) +def show_sa(raw: bool): + sa_data = _get_raw_data_sas() + if raw: + return sa_data + return _get_formatted_output_sas(sa_data) + + if __name__ == '__main__': try: res = vyos.opmode.run(sys.modules[__name__]) -- cgit v1.2.3 From 1bd3a9635a5ff703f5623743a487e0effb846c41 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Sat, 6 Aug 2022 10:09:09 +0000 Subject: ocserv: T4596: Rewrite show openconnect sessions op-mode Rewrite "show openconnect-server sessions" to vyos.opmode format Ability to get raw and formatted output Ability to get data via API --- data/op-mode-standardized.json | 1 + op-mode-definitions/openconnect.xml.in | 2 +- src/op_mode/openconnect-control.py | 5 --- src/op_mode/openconnect.py | 81 ++++++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 6 deletions(-) create mode 100755 src/op_mode/openconnect.py diff --git a/data/op-mode-standardized.json b/data/op-mode-standardized.json index b5e9308c5..5f49be781 100644 --- a/data/op-mode-standardized.json +++ b/data/op-mode-standardized.json @@ -6,6 +6,7 @@ "memory.py", "nat.py", "neighbor.py", +"openconnect.py", "route.py", "version.py", "vrf.py" diff --git a/op-mode-definitions/openconnect.xml.in b/op-mode-definitions/openconnect.xml.in index 9343637c0..88e1f9f15 100644 --- a/op-mode-definitions/openconnect.xml.in +++ b/op-mode-definitions/openconnect.xml.in @@ -11,7 +11,7 @@ Show active OpenConnect server sessions - ${vyos_op_scripts_dir}/openconnect-control.py --action="show_sessions" + ${vyos_op_scripts_dir}/openconnect.py show_sessions diff --git a/src/op_mode/openconnect-control.py b/src/op_mode/openconnect-control.py index a128cc011..20c50e779 100755 --- a/src/op_mode/openconnect-control.py +++ b/src/op_mode/openconnect-control.py @@ -19,7 +19,6 @@ import argparse import json from vyos.config import Config -from vyos.util import commit_in_progress from vyos.util import popen from vyos.util import run from vyos.util import DEVNULL @@ -60,10 +59,6 @@ def main(): # Check is Openconnect server configured is_ocserv_configured() - if commit_in_progress(): - print('Cannot restart openconnect while a commit is in progress') - exit(1) - if args.action == "restart": run("sudo systemctl restart ocserv.service") sys.exit(0) diff --git a/src/op_mode/openconnect.py b/src/op_mode/openconnect.py new file mode 100755 index 000000000..00992c66a --- /dev/null +++ b/src/op_mode/openconnect.py @@ -0,0 +1,81 @@ +#!/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 sys +import json + +from tabulate import tabulate +from vyos.configquery import ConfigTreeQuery +from vyos.util import rc_cmd + +import vyos.opmode + + +occtl = '/usr/bin/occtl' +occtl_socket = '/run/ocserv/occtl.socket' + + +def _get_raw_data_sessions(): + rc, out = rc_cmd(f'sudo {occtl} --json --socket-file {occtl_socket} show users') + if rc != 0: + output = {'openconnect': + { + 'configured': False, + 'return_code': rc, + 'reason': out + } + } + return output + + sessions = json.loads(out) + return sessions + + +def _get_formatted_sessions(data): + headers = ["Interface", "Username", "IP", "Remote IP", "RX", "TX", "State", "Uptime"] + ses_list = [] + for ses in data: + ses_list.append([ + ses["Device"], ses["Username"], ses["IPv4"], ses["Remote IP"], + ses["_RX"], ses["_TX"], ses["State"], ses["_Connected at"] + ]) + if len(ses_list) > 0: + output = tabulate(ses_list, headers) + else: + output = 'No active openconnect sessions' + return output + + +def show_sessions(raw: bool): + config = ConfigTreeQuery() + if not config.exists('vpn openconnect') and not raw: + print('Openconnect is not configured') + exit(0) + + openconnect_data = _get_raw_data_sessions() + if raw: + return openconnect_data + return _get_formatted_sessions(openconnect_data) + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) -- cgit v1.2.3 From fed4cbf9b2f02628745229305cfec4f8a342c554 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Wed, 10 Aug 2022 21:52:48 +0000 Subject: dmvpn: T4595: Fix dpd profile options Fix template for configuration DMVPN IKE profile dead-peer-detection delay and dead-peer-detecion timeout options --- data/templates/ipsec/swanctl/profile.j2 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/templates/ipsec/swanctl/profile.j2 b/data/templates/ipsec/swanctl/profile.j2 index d4f417378..8519a84f8 100644 --- a/data/templates/ipsec/swanctl/profile.j2 +++ b/data/templates/ipsec/swanctl/profile.j2 @@ -9,6 +9,10 @@ version = {{ ike.key_exchange[4:] if ike.key_exchange is vyos_defined else "0" }} rekey_time = {{ ike.lifetime }}s keyingtries = 0 +{% if ike.dead_peer_detection is vyos_defined %} + dpd_timeout = {{ ike.dead_peer_detection.timeout }} + dpd_delay = {{ ike.dead_peer_detection.interval }} +{% endif %} {% if profile_conf.authentication.mode is vyos_defined('pre-shared-secret') %} local { auth = psk -- cgit v1.2.3 From 03e69f280725dae06ed5d30569a48e283031fd48 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Mon, 15 Aug 2022 09:32:32 +0000 Subject: container: T4609: Fix restart container Add 2 dashes for arg "name" --- op-mode-definitions/container.xml.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/op-mode-definitions/container.xml.in b/op-mode-definitions/container.xml.in index a7048e5ed..97a087ce2 100644 --- a/op-mode-definitions/container.xml.in +++ b/op-mode-definitions/container.xml.in @@ -149,7 +149,7 @@ container name - sudo ${vyos_op_scripts_dir}/container.py restart name="$3" + sudo ${vyos_op_scripts_dir}/container.py restart --name="$3" -- cgit v1.2.3 From 6e82d5d87f0f7603a9de77f895b3ac3b67323a63 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 15 Aug 2022 20:05:18 +0200 Subject: ocserv: T4333: migrate to new vyos_defined Jinja2 test --- data/templates/ocserv/ocserv_config.j2 | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/data/templates/ocserv/ocserv_config.j2 b/data/templates/ocserv/ocserv_config.j2 index 8418a2185..d3d022bb0 100644 --- a/data/templates/ocserv/ocserv_config.j2 +++ b/data/templates/ocserv/ocserv_config.j2 @@ -56,34 +56,25 @@ ban-reset-time = 300 # The name to use for the tun device device = sslvpn -# An alternative way of specifying the network: -{% if network_settings %} # DNS settings -{% if network_settings.name_server is string %} -dns = {{ network_settings.name_server }} -{% else %} -{% for dns in network_settings.name_server %} +{% if network_settings.name_server is vyos_defined %} +{% for dns in network_settings.name_server %} dns = {{ dns }} -{% endfor %} -{% endif %} +{% endfor %} +{% endif %} + # IPv4 network pool -{% if network_settings.client_ip_settings %} -{% if network_settings.client_ip_settings.subnet %} +{% if network_settings.client_ip_settings.subnet is vyos_defined %} ipv4-network = {{ network_settings.client_ip_settings.subnet }} -{% endif %} -{% endif %} +{% endif %} + # IPv6 network pool -{% if network_settings.client_ipv6_pool %} -{% if network_settings.client_ipv6_pool.prefix %} +{% if network_settings.client_ipv6_pool.prefix is vyos_defined %} ipv6-network = {{ network_settings.client_ipv6_pool.prefix }} ipv6-subnet-prefix = {{ network_settings.client_ipv6_pool.mask }} -{% endif %} -{% endif %} {% endif %} -{% if network_settings.push_route is string %} -route = {{ network_settings.push_route }} -{% else %} +{% if network_settings.push_route is vyos_defined %} {% for route in network_settings.push_route %} route = {{ route }} {% endfor %} -- cgit v1.2.3 From bd102eac6d0c97a5f75324d1248814ebdad42da5 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 15 Aug 2022 20:04:29 +0200 Subject: smoketest: ocserv: implement config file validation --- smoketest/scripts/cli/test_vpn_openconnect.py | 75 +++++++++++++++++++++------ 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/smoketest/scripts/cli/test_vpn_openconnect.py b/smoketest/scripts/cli/test_vpn_openconnect.py index bda279342..094812791 100755 --- a/smoketest/scripts/cli/test_vpn_openconnect.py +++ b/smoketest/scripts/cli/test_vpn_openconnect.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 @@ -19,6 +19,7 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.util import process_named_running +from vyos.util import read_file OCSERV_CONF = '/run/ocserv/ocserv.conf' base_path = ['vpn', 'openconnect'] @@ -46,36 +47,80 @@ MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPLpD0Ohhoq0g4nhx u8/3jHMM7sDwL3aWzW/zp54/LhCWUoLMjDdDEEigK4fal4ZF9aA9F0Ww """ -class TestVpnOpenconnect(VyOSUnitTestSHIM.TestCase): +PROCESS_NAME = 'ocserv-main' +config_file = '/run/ocserv/ocserv.conf' +auth_file = '/run/ocserv/ocpasswd' +otp_file = '/run/ocserv/users.oath' + +class TestVPNOpenConnect(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestVPNOpenConnect, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + cls.cli_set(cls, pki_path + ['ca', 'openconnect', 'certificate', cert_data.replace('\n','')]) + cls.cli_set(cls, pki_path + ['certificate', 'openconnect', 'certificate', cert_data.replace('\n','')]) + cls.cli_set(cls, pki_path + ['certificate', 'openconnect', 'private', 'key', key_data.replace('\n','')]) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, pki_path) + super(TestVPNOpenConnect, cls).tearDownClass() + def tearDown(self): - # Delete vpn openconnect configuration - self.cli_delete(pki_path) + self.assertTrue(process_named_running(PROCESS_NAME)) + self.cli_delete(base_path) self.cli_commit() - def test_vpn(self): + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_ocserv(self): user = 'vyos_user' password = 'vyos_pass' otp = '37500000026900000000200000000000' - - self.cli_delete(pki_path) - self.cli_delete(base_path) - - self.cli_set(pki_path + ['ca', 'openconnect', 'certificate', cert_data.replace('\n','')]) - self.cli_set(pki_path + ['certificate', 'openconnect', 'certificate', cert_data.replace('\n','')]) - self.cli_set(pki_path + ['certificate', 'openconnect', 'private', 'key', key_data.replace('\n','')]) + v4_subnet = '192.0.2.0/24' + v6_prefix = '2001:db8:1000::/64' + v6_len = '126' + name_server = ['1.2.3.4', '1.2.3.5', '2001:db8::1'] + split_dns = ['vyos.net', 'vyos.io'] self.cli_set(base_path + ['authentication', 'local-users', 'username', user, 'password', password]) self.cli_set(base_path + ['authentication', 'local-users', 'username', user, 'otp', 'key', otp]) self.cli_set(base_path + ['authentication', 'mode', 'local', 'password-otp']) - self.cli_set(base_path + ['network-settings', 'client-ip-settings', 'subnet', '192.0.2.0/24']) + + self.cli_set(base_path + ['network-settings', 'client-ip-settings', 'subnet', v4_subnet]) + self.cli_set(base_path + ['network-settings', 'client-ipv6-pool', 'prefix', v6_prefix]) + self.cli_set(base_path + ['network-settings', 'client-ipv6-pool', 'mask', v6_len]) + + for ns in name_server: + self.cli_set(base_path + ['network-settings', 'name-server', ns]) + self.cli_set(base_path + ['ssl', 'ca-certificate', 'openconnect']) self.cli_set(base_path + ['ssl', 'certificate', 'openconnect']) self.cli_commit() - # Check for running process - self.assertTrue(process_named_running('ocserv-main')) + # Verify configuration + daemon_config = read_file(config_file) + + # authentication mode local password-otp + self.assertIn(f'auth = "plain[passwd=/run/ocserv/ocpasswd,otp=/run/ocserv/users.oath]"', daemon_config) + self.assertIn(f'ipv4-network = {v4_subnet}', daemon_config) + self.assertIn(f'ipv6-network = {v6_prefix}', daemon_config) + self.assertIn(f'ipv6-subnet-prefix = {v6_len}', daemon_config) + + for ns in name_server: + self.assertIn(f'dns = {ns}', daemon_config) + + auth_config = read_file(auth_file) + self.assertIn(f'{user}:*:$', auth_config) + + otp_config = read_file(otp_file) + self.assertIn(f'HOTP/T30/6 {user} - {otp}', otp_config) if __name__ == '__main__': unittest.main(verbosity=2) -- cgit v1.2.3 From e41685a2f56cca0a53b4f8c084f61a85cf561c80 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 15 Aug 2022 20:16:02 +0200 Subject: ocserv: openconnect: T4614: add support for split-dns set vpn openconnect network-settings split-dns --- data/templates/ocserv/ocserv_config.j2 | 5 +++++ interface-definitions/vpn-openconnect.xml.in | 13 +++++++++++++ smoketest/scripts/cli/test_vpn_openconnect.py | 4 ++++ 3 files changed, 22 insertions(+) diff --git a/data/templates/ocserv/ocserv_config.j2 b/data/templates/ocserv/ocserv_config.j2 index d3d022bb0..e0cad5181 100644 --- a/data/templates/ocserv/ocserv_config.j2 +++ b/data/templates/ocserv/ocserv_config.j2 @@ -80,3 +80,8 @@ route = {{ route }} {% endfor %} {% endif %} +{% if network_settings.split_dns is vyos_defined %} +{% for tmp in network_settings.split_dns %} +split-dns = {{ tmp }} +{% endfor %} +{% endif %} diff --git a/interface-definitions/vpn-openconnect.xml.in b/interface-definitions/vpn-openconnect.xml.in index 21b47125d..6309863c5 100644 --- a/interface-definitions/vpn-openconnect.xml.in +++ b/interface-definitions/vpn-openconnect.xml.in @@ -265,6 +265,19 @@ #include + + + Domains over which the provided DNS should be used + + txt + Client prefix length + + + + + + + diff --git a/smoketest/scripts/cli/test_vpn_openconnect.py b/smoketest/scripts/cli/test_vpn_openconnect.py index 094812791..8572d6d66 100755 --- a/smoketest/scripts/cli/test_vpn_openconnect.py +++ b/smoketest/scripts/cli/test_vpn_openconnect.py @@ -98,6 +98,8 @@ class TestVPNOpenConnect(VyOSUnitTestSHIM.TestCase): for ns in name_server: self.cli_set(base_path + ['network-settings', 'name-server', ns]) + for domain in split_dns: + self.cli_set(base_path + ['network-settings', 'split-dns', domain]) self.cli_set(base_path + ['ssl', 'ca-certificate', 'openconnect']) self.cli_set(base_path + ['ssl', 'certificate', 'openconnect']) @@ -115,6 +117,8 @@ class TestVPNOpenConnect(VyOSUnitTestSHIM.TestCase): for ns in name_server: self.assertIn(f'dns = {ns}', daemon_config) + for domain in split_dns: + self.assertIn(f'split-dns = {domain}', daemon_config) auth_config = read_file(auth_file) self.assertIn(f'{user}:*:$', auth_config) -- cgit v1.2.3 From 160262edaf1b6b6405baa5c65f69573225cbe208 Mon Sep 17 00:00:00 2001 From: mkorobeinikov <92354771+mkorobeinikov@users.noreply.github.com> Date: Tue, 16 Aug 2022 10:02:46 +1000 Subject: dhcp-relay: T4601: restart dhcp relay-agent The command "restart dhcp relay-agent" doesn't restart "isc-dhcp-relay" service. --- src/op_mode/restart_dhcp_relay.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/op_mode/restart_dhcp_relay.py b/src/op_mode/restart_dhcp_relay.py index db5a48970..9203c009f 100755 --- a/src/op_mode/restart_dhcp_relay.py +++ b/src/op_mode/restart_dhcp_relay.py @@ -43,7 +43,7 @@ if __name__ == '__main__': if commit_in_progress(): print('Cannot restart DHCP relay while a commit is in progress') exit(1) - call('systemctl restart isc-dhcp-server.service') + call('systemctl restart isc-dhcp-relay.service') sys.exit(0) elif args.ipv6: @@ -54,7 +54,7 @@ if __name__ == '__main__': if commit_in_progress(): print('Cannot restart DHCPv6 relay while commit is in progress') exit(1) - call('systemctl restart isc-dhcp-server6.service') + call('systemctl restart isc-dhcp-relay6.service') sys.exit(0) else: -- cgit v1.2.3 From 681bdf2946d1d10f3b432f70452a8d018b7a98ae Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 16 Aug 2022 08:23:06 +0200 Subject: Debian: T4584: remove version number from hostap package requirement --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 6a6ccf602..0db098be6 100644 --- a/debian/control +++ b/debian/control @@ -59,7 +59,7 @@ Depends: frr-rpki-rtrlib, frr-snmp, grc, - hostapd (>= 0.6.8), + hostapd, hvinfo, igmpproxy, ipaddrcheck, -- cgit v1.2.3 From 9c9e7618cdc5c293c41cad1a3e4666f98298870f Mon Sep 17 00:00:00 2001 From: DaniilHarun Date: Tue, 16 Aug 2022 15:38:57 +0300 Subject: T4619: Replacing instead of adding a static arp entry --- src/conf_mode/arp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conf_mode/arp.py b/src/conf_mode/arp.py index 1cd8f5451..7dc5206e0 100755 --- a/src/conf_mode/arp.py +++ b/src/conf_mode/arp.py @@ -61,7 +61,7 @@ def apply(arp): continue for address, address_config in interface_config['address'].items(): mac = address_config['mac'] - call(f'ip neigh add {address} lladdr {mac} dev {interface}') + call(f'ip neigh replace {address} lladdr {mac} dev {interface}') if __name__ == '__main__': try: -- cgit v1.2.3 From d69b7989620da1348fe187975dc5a1c467400354 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Tue, 16 Aug 2022 14:55:10 +0000 Subject: upnp: T4613: Verify listen key in dictionary There is no check if 'listen' is exist in the dictionary, fix it Fix odd ValueHelp format --- interface-definitions/service-upnp.xml.in | 12 ++++++------ src/conf_mode/service_upnp.py | 19 +++++++++++++------ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/interface-definitions/service-upnp.xml.in b/interface-definitions/service-upnp.xml.in index a129b7260..b1e6f170a 100644 --- a/interface-definitions/service-upnp.xml.in +++ b/interface-definitions/service-upnp.xml.in @@ -103,19 +103,19 @@ ipv4 - IP address to listen for incoming connections + IPv4 address to listen for incoming connections - ipv4-prefix - IP prefix to listen for incoming connections + ipv4net + IPv4 prefix to listen for incoming connections ipv6 - IP address to listen for incoming connections + IPv6 address to listen for incoming connections - ipv6-prefix - IP prefix to listen for incoming connections + ipv6net + IPv6 prefix to listen for incoming connections diff --git a/src/conf_mode/service_upnp.py b/src/conf_mode/service_upnp.py index 36f3e18a7..c798fd515 100755 --- a/src/conf_mode/service_upnp.py +++ b/src/conf_mode/service_upnp.py @@ -24,8 +24,6 @@ from ipaddress import IPv6Network from vyos.config import Config from vyos.configdict import dict_merge -from vyos.configdict import get_interface_dict -from vyos.configverify import verify_vrf from vyos.util import call from vyos.template import render from vyos.template import is_ipv4 @@ -113,19 +111,28 @@ def verify(upnpd): listen_dev = [] system_addrs_cidr = get_all_interface_addr(True, [], [netifaces.AF_INET, netifaces.AF_INET6]) system_addrs = get_all_interface_addr(False, [], [netifaces.AF_INET, netifaces.AF_INET6]) + if 'listen' not in upnpd: + raise ConfigError(f'Listen address or interface is required!') for listen_if_or_addr in upnpd['listen']: if listen_if_or_addr not in netifaces.interfaces(): listen_dev.append(listen_if_or_addr) - if (listen_if_or_addr not in system_addrs) and (listen_if_or_addr not in system_addrs_cidr) and (listen_if_or_addr not in netifaces.interfaces()): + if (listen_if_or_addr not in system_addrs) and (listen_if_or_addr not in system_addrs_cidr) and \ + (listen_if_or_addr not in netifaces.interfaces()): if is_ipv4(listen_if_or_addr) and IPv4Network(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!') + raise ConfigError(f'The address "{listen_if_or_addr}" is an address that is not allowed' + f'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!') + raise ConfigError(f'The address "{listen_if_or_addr}" is an address that is not allowed' + f'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']: - if listen_if_or_addr not in netifaces.interfaces() and (listen_if_or_addr not in system_listening_dev_addrs_cidr) and (listen_if_or_addr not in system_listening_dev_addrs) and is_ipv6(listen_if_or_addr) and (not IPv6Network(listen_if_or_addr).is_multicast): + if listen_if_or_addr not in netifaces.interfaces() and \ + (listen_if_or_addr not in system_listening_dev_addrs_cidr) and \ + (listen_if_or_addr not in system_listening_dev_addrs) and \ + is_ipv6(listen_if_or_addr) and \ + (not IPv6Network(listen_if_or_addr).is_multicast): raise ConfigError(f'{listen_if_or_addr} must listen on the interface of the network card') def generate(upnpd): -- cgit v1.2.3 From d0858015f121416a042ec847c01fefb733661bab Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Tue, 16 Aug 2022 16:05:23 +0000 Subject: UPnP: T4620: Fix Jinja2 template rules --- data/templates/firewall/upnpd.conf.j2 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/templates/firewall/upnpd.conf.j2 b/data/templates/firewall/upnpd.conf.j2 index 27573cbf9..b993052b4 100644 --- a/data/templates/firewall/upnpd.conf.j2 +++ b/data/templates/firewall/upnpd.conf.j2 @@ -129,7 +129,7 @@ lease_file=/config/upnp.leases #serial=12345678 #model_number=1 -{% if rules is vyos_defined %} +{% if rule is vyos_defined %} # UPnP permission rules # (allow|deny) (external port range) IP/mask (internal port range) # A port range is - or if there is only @@ -142,8 +142,8 @@ lease_file=/config/upnp.leases # modify the IP ranges to match their own internal networks, and # also consider implementing network-specific restrictions # CAUTION: failure to enforce any rules may permit insecure requests to be made! -{% for rule, config in rules.items() %} -{% if config.disable is vyos_defined %} +{% for rule, config in rule.items() %} +{% if config.disable is not vyos_defined %} {{ config.action }} {{ config.external_port_range }} {{ config.ip }} {{ config.internal_port_range }} {% endif %} {% endfor %} -- cgit v1.2.3 From fd15f9d2ab6a7e5bbc07ff2e8b10c064984492ce Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Thu, 18 Aug 2022 17:09:17 +0000 Subject: firewall: T4622: Add TCP MSS option Ability to drop|accept packets based on TCP MSS size set firewall name rule tcp mss '501-1460' --- interface-definitions/include/firewall/tcp-flags.xml.i | 17 +++++++++++++++++ python/vyos/firewall.py | 5 +++++ smoketest/scripts/cli/test_firewall.py | 8 +++++++- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/interface-definitions/include/firewall/tcp-flags.xml.i b/interface-definitions/include/firewall/tcp-flags.xml.i index b99896687..5a7b5a8d3 100644 --- a/interface-definitions/include/firewall/tcp-flags.xml.i +++ b/interface-definitions/include/firewall/tcp-flags.xml.i @@ -114,6 +114,23 @@ + + + Maximum segment size (MSS) + + u32:1-16384 + Maximum segment size + + + <min>-<max> + TCP MSS range (use '-' as delimiter) + + + + + + + diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index 3e2de4c3f..663c4394a 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -297,6 +297,11 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): if tcp_flags: output.append(parse_tcp_flags(tcp_flags)) + # TCP MSS + tcp_mss = dict_search_args(rule_conf, 'tcp', 'mss') + if tcp_mss: + output.append(f'tcp option maxseg size {tcp_mss}') + output.append('counter') if 'set' in rule_conf: diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index 4de90e1ec..684a07681 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -177,6 +177,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.verify_nftables(nftables_search, 'ip filter') def test_basic_rules(self): + mss_range = '501-1460' self.cli_set(['firewall', 'name', 'smoketest', 'default-action', 'drop']) self.cli_set(['firewall', 'name', 'smoketest', 'enable-default-log']) self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'action', 'accept']) @@ -203,6 +204,10 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'destination', 'port', '22']) self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'recent', 'count', '10']) self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'recent', 'time', 'minute']) + self.cli_set(['firewall', 'name', 'smoketest', 'rule', '5', 'action', 'accept']) + self.cli_set(['firewall', 'name', 'smoketest', 'rule', '5', 'protocol', 'tcp']) + self.cli_set(['firewall', 'name', 'smoketest', 'rule', '5', 'tcp', 'flags', 'syn']) + self.cli_set(['firewall', 'name', 'smoketest', 'rule', '5', 'tcp', 'mss', mss_range]) self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest']) @@ -214,7 +219,8 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): ['tcp flags & (syn | ack) == syn', 'tcp dport { 8888 }', 'log prefix "[smoketest-2-R]" level err', 'ip ttl > 102', 'reject'], ['tcp dport { 22 }', 'limit rate 5/minute', 'return'], ['log prefix "[smoketest-default-D]"','smoketest default-action', 'drop'], - ['tcp dport { 22 }', 'add @RECENT_smoketest_4 { ip saddr limit rate over 10/minute burst 10 packets }', 'drop'] + ['tcp dport { 22 }', 'add @RECENT_smoketest_4 { ip saddr limit rate over 10/minute burst 10 packets }', 'drop'], + [f'tcp flags & syn == syn tcp option maxseg size {mss_range}'] ] self.verify_nftables(nftables_search, 'ip filter') -- cgit v1.2.3 From 6940bcf8d650eea714075df74b47bf0c99642743 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Tue, 16 Aug 2022 16:19:02 +0000 Subject: UPnP: T4611: Rule must be as prefix instead of an address From the doc miniupnpd IP/mask format must be nnn.nnn.nnn.nnn/nn Comment out invalid option "anchor" --- data/templates/firewall/upnpd.conf.j2 | 9 ++++++--- interface-definitions/service-upnp.xml.in | 7 ++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/data/templates/firewall/upnpd.conf.j2 b/data/templates/firewall/upnpd.conf.j2 index b993052b4..e964fc696 100644 --- a/data/templates/firewall/upnpd.conf.j2 +++ b/data/templates/firewall/upnpd.conf.j2 @@ -71,7 +71,7 @@ min_lifetime={{ pcp_lifetime.min }} {% if friendly_name is vyos_defined %} # Name of this service, default is "`uname -s` router" -friendly_name= {{ friendly_name }} +friendly_name={{ friendly_name }} {% endif %} # Manufacturer name, default is "`uname -s`" @@ -117,7 +117,10 @@ clean_ruleset_threshold=10 clean_ruleset_interval=600 # Anchor name in pf (default is miniupnpd) -anchor=VyOS +# Something wrong with this option "anchor", comment it out +# vyos@r14# miniupnpd -vv -f /run/upnp/miniupnp.conf +# invalid option in file /run/upnp/miniupnp.conf line 74 : anchor=VyOS +#anchor=VyOS uuid={{ uuid }} @@ -144,7 +147,7 @@ lease_file=/config/upnp.leases # CAUTION: failure to enforce any rules may permit insecure requests to be made! {% for rule, config in rule.items() %} {% if config.disable is not vyos_defined %} -{{ config.action }} {{ config.external_port_range }} {{ config.ip }} {{ config.internal_port_range }} +{{ config.action }} {{ config.external_port_range }} {{ config.ip }}{{ '/32' if '/' not in config.ip else '' }} {{ config.internal_port_range }} {% endif %} {% endfor %} {% endif %} diff --git a/interface-definitions/service-upnp.xml.in b/interface-definitions/service-upnp.xml.in index a129b7260..50cb47f39 100644 --- a/interface-definitions/service-upnp.xml.in +++ b/interface-definitions/service-upnp.xml.in @@ -197,10 +197,15 @@ The IP to which this rule applies (REQUIRE) ipv4 + The IPv4 address to which this rule applies + + + ipv4net The IPv4 to which this rule applies - + + -- cgit v1.2.3 From f92a23ef9ab8be59681e5b7ba627e399d89bce53 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 19 Aug 2022 18:55:24 +0200 Subject: ethernet: T4538: fix wrong systemd unit used for EAPoL When MACsec was bound to an ethernet interface and the underlaying source-interface got changed (even description only) this terminated the MACsec session running on top of it. The root cause is when EAPoL was implemented in commit d59354e52a8a7f we re-used the same systemd unit which is responsible for MACsec. That indeed lead to the fact that wpa_supplicant was always stopped when anything happened on the underlaying source-interface that was not related to EAPoL. --- src/conf_mode/interfaces-ethernet.py | 23 +++++++++++++--------- .../wpa_supplicant-wired@.service.d/override.conf | 11 +++++++++++ 2 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 src/etc/systemd/system/wpa_supplicant-wired@.service.d/override.conf diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 30e7a2af7..e02841831 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -153,11 +153,20 @@ def verify(ethernet): return None def generate(ethernet): - if 'eapol' in ethernet: - render(wpa_suppl_conf.format(**ethernet), - 'ethernet/wpa_supplicant.conf.j2', ethernet) + # render real configuration file once + wpa_supplicant_conf = wpa_suppl_conf.format(**ethernet) + + if 'deleted' in ethernet: + # delete configuration on interface removal + if os.path.isfile(wpa_supplicant_conf): + os.unlink(wpa_supplicant_conf) + return None + if 'eapol' in ethernet: ifname = ethernet['ifname'] + + render(wpa_supplicant_conf, 'ethernet/wpa_supplicant.conf.j2', ethernet) + cert_file_path = os.path.join(cfg_dir, f'{ifname}_cert.pem') cert_key_path = os.path.join(cfg_dir, f'{ifname}_cert.key') @@ -184,10 +193,6 @@ def generate(ethernet): write_file(ca_cert_file_path, '\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)): - os.unlink(wpa_suppl_conf.format(**ethernet)) return None @@ -203,9 +208,9 @@ def apply(ethernet): else: e.update(ethernet) if 'eapol' in ethernet: - eapol_action='restart' + eapol_action='reload-or-restart' - call(f'systemctl {eapol_action} wpa_supplicant-macsec@{ifname}') + call(f'systemctl {eapol_action} wpa_supplicant-wired@{ifname}') if __name__ == '__main__': try: diff --git a/src/etc/systemd/system/wpa_supplicant-wired@.service.d/override.conf b/src/etc/systemd/system/wpa_supplicant-wired@.service.d/override.conf new file mode 100644 index 000000000..030b89a2b --- /dev/null +++ b/src/etc/systemd/system/wpa_supplicant-wired@.service.d/override.conf @@ -0,0 +1,11 @@ +[Unit] +After= +After=vyos-router.service + +[Service] +WorkingDirectory= +WorkingDirectory=/run/wpa_supplicant +PIDFile=/run/wpa_supplicant/%I.pid +ExecStart= +ExecStart=/sbin/wpa_supplicant -c/run/wpa_supplicant/%I.conf -Dwired -P/run/wpa_supplicant/%I.pid -i%I +ExecReload=/bin/kill -HUP $MAINPID -- cgit v1.2.3 From c0f5d00d92667f2a45896180cd05747c3ba82782 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Sat, 20 Aug 2022 13:48:30 +0000 Subject: ocserv: T4597: Fix check bounded port by service itself We check listen port before commit service if is port available and not bounded, but when we start openconnect our own port starts be bounded by "ocserv-main" process and next commit will be fail as port is already bound To fix it, extend check if port already bonded and it is not our self process "ocserv-main" --- python/vyos/util.py | 23 +++++++++++++++++++++++ src/conf_mode/vpn_openconnect.py | 5 ++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/python/vyos/util.py b/python/vyos/util.py index b86b1949c..c1459f02a 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -471,6 +471,29 @@ def process_named_running(name): return p.pid return None +def is_listen_port_bind_service(port: int, service: str) -> bool: + """Check if listen port bound to expected program name + :param port: Bind port + :param service: Program name + :return: bool + + Example: + % is_listen_port_bind_service(443, 'nginx') + True + % is_listen_port_bind_service(443, 'ocservr-main') + False + """ + from psutil import net_connections as connections + from psutil import Process as process + for connection in connections(): + addr = connection.laddr + pid = connection.pid + pid_name = process(pid).name() + pid_port = addr.port + if service == pid_name and port == pid_port: + return True + return False + def seconds_to_human(s, separator=""): """ Converts number of seconds passed to a human-readable interval such as 1w4d18h35m59s diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py index a3e774678..240546817 100755 --- a/src/conf_mode/vpn_openconnect.py +++ b/src/conf_mode/vpn_openconnect.py @@ -25,6 +25,7 @@ from vyos.template import render from vyos.util import call from vyos.util import check_port_availability from vyos.util import is_systemd_service_running +from vyos.util import is_listen_port_bind_service from vyos.util import dict_search from vyos.xml import defaults from vyos import ConfigError @@ -77,8 +78,10 @@ def verify(ocserv): if ocserv is None: return None # Check if listen-ports not binded other services + # It can be only listen by 'ocserv-main' for proto, port in ocserv.get('listen_ports').items(): - if check_port_availability('0.0.0.0', int(port), proto) is not True: + if check_port_availability('0.0.0.0', int(port), proto) is not True and \ + not is_listen_port_bind_service(int(port), 'ocserv-main'): raise ConfigError(f'"{proto}" port "{port}" is used by another service') # Check authentication if "authentication" in ocserv: -- cgit v1.2.3 From b752c8779712ec4e1c3b653081768361359c57f7 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Sat, 20 Aug 2022 16:26:55 +0000 Subject: nat66: T4631: Add port and protocol to nat66 Ability to configure src/dst/translation port and protocol for SNAT and DNAT IPv6 --- data/templates/firewall/nftables-nat66.j2 | 41 ++++++++++++++++++++++++ interface-definitions/include/nat/protocol.xml.i | 34 ++++++++++++++++++++ interface-definitions/nat66.xml.in | 8 +++++ 3 files changed, 83 insertions(+) create mode 100644 interface-definitions/include/nat/protocol.xml.i diff --git a/data/templates/firewall/nftables-nat66.j2 b/data/templates/firewall/nftables-nat66.j2 index 2fe04b4ff..28714c7a7 100644 --- a/data/templates/firewall/nftables-nat66.j2 +++ b/data/templates/firewall/nftables-nat66.j2 @@ -7,6 +7,17 @@ {% set src_prefix = 'ip6 saddr ' ~ config.source.prefix.replace('!','!= ') if config.source.prefix is vyos_defined %} {% set source_address = 'ip6 saddr ' ~ config.source.address.replace('!','!= ') if config.source.address is vyos_defined %} {% set dest_address = 'ip6 daddr ' ~ config.destination.address.replace('!','!= ') if config.destination.address is vyos_defined %} +{# Port #} +{% if config.source.port is vyos_defined and config.source.port.startswith('!') %} +{% set src_port = 'sport != { ' ~ config.source.port.replace('!','') ~ ' }' %} +{% else %} +{% set src_port = 'sport { ' ~ config.source.port ~ ' }' if config.source.port is vyos_defined %} +{% endif %} +{% if config.destination.port is vyos_defined and config.destination.port.startswith('!') %} +{% set dst_port = 'dport != { ' ~ config.destination.port.replace('!','') ~ ' }' %} +{% else %} +{% set dst_port = 'dport { ' ~ config.destination.port ~ ' }' if config.destination.port is vyos_defined %} +{% endif %} {% if chain is vyos_defined('PREROUTING') %} {% set comment = 'DST-NAT66-' ~ rule %} {% set base_log = '[NAT66-DST-' ~ rule %} @@ -36,6 +47,14 @@ {% endif %} {% set interface = ' oifname "' ~ config.outbound_interface ~ '"' if config.outbound_interface is vyos_defined else '' %} {% endif %} +{% set trns_port = ':' ~ config.translation.port if config.translation.port is vyos_defined %} +{# protocol has a default value thus it is always present #} +{% if config.protocol is vyos_defined('tcp_udp') %} +{% set protocol = 'tcp' %} +{% set comment = comment ~ ' tcp_udp' %} +{% else %} +{% set protocol = config.protocol %} +{% endif %} {% if config.log is vyos_defined %} {% if config.translation.address is vyos_defined('masquerade') %} {% set log = base_log ~ '-MASQ]' %} @@ -43,6 +62,11 @@ {% set log = base_log ~ ']' %} {% endif %} {% endif %} +{% if config.exclude is vyos_defined %} +{# rule has been marked as 'exclude' thus we simply return here #} +{% set trns_addr = 'return' %} +{% set trns_port = '' %} +{% endif %} {% set output = 'add rule ip6 nat ' ~ chain ~ interface %} {# Count packets #} {% set output = output ~ ' counter' %} @@ -54,12 +78,18 @@ {% if src_prefix is vyos_defined %} {% set output = output ~ ' ' ~ src_prefix %} {% endif %} +{% if dst_port is vyos_defined %} +{% set output = output ~ ' ' ~ protocol ~ ' ' ~ dst_port %} +{% endif %} {% if dst_prefix is vyos_defined %} {% set output = output ~ ' ' ~ dst_prefix %} {% endif %} {% if source_address is vyos_defined %} {% set output = output ~ ' ' ~ source_address %} {% endif %} +{% if src_port is vyos_defined %} +{% set output = output ~ ' ' ~ protocol ~ ' ' ~ src_port %} +{% endif %} {% if dest_address is vyos_defined %} {% set output = output ~ ' ' ~ dest_address %} {% endif %} @@ -70,11 +100,22 @@ {% if trns_address is vyos_defined %} {% set output = output ~ ' ' ~ trns_address %} {% endif %} +{% if trns_port is vyos_defined %} +{# Do not add a whitespace here, translation port must be directly added after IP address #} +{# e.g. 2001:db8::1:3389 #} +{% set output = output ~ trns_port %} +{% endif %} {% if comment is vyos_defined %} {% set output = output ~ ' comment "' ~ comment ~ '"' %} {% endif %} {{ log_output if log_output is vyos_defined }} {{ output }} +{# Special handling if protocol is tcp_udp, we must repeat the entire rule with udp as protocol #} +{% if config.protocol is vyos_defined('tcp_udp') %} +{# Beware of trailing whitespace, without it the comment tcp_udp will be changed to udp_udp #} +{{ log_output | replace('tcp ', 'udp ') if log_output is vyos_defined }} +{{ output | replace('tcp ', 'udp ') }} +{% endif %} {% endmacro %} # Start with clean NAT table diff --git a/interface-definitions/include/nat/protocol.xml.i b/interface-definitions/include/nat/protocol.xml.i new file mode 100644 index 000000000..54e7ff00d --- /dev/null +++ b/interface-definitions/include/nat/protocol.xml.i @@ -0,0 +1,34 @@ + + + + Protocol to match (protocol name, number, or "all") + + + all tcp_udp + + + all + All IP protocols + + + tcp_udp + Both TCP and UDP + + + u32:0-255 + IP protocol number + + + <protocol> + IP protocol name + + + !<protocol> + IP protocol name + + + + + + + diff --git a/interface-definitions/nat66.xml.in b/interface-definitions/nat66.xml.in index bde1a6f8d..dab4543e0 100644 --- a/interface-definitions/nat66.xml.in +++ b/interface-definitions/nat66.xml.in @@ -50,6 +50,7 @@ + #include IPv6 destination prefix options @@ -72,6 +73,7 @@ + #include @@ -96,6 +98,7 @@ + #include @@ -128,6 +131,7 @@ + #include @@ -179,6 +183,7 @@ + #include IPv6 destination prefix options @@ -211,6 +216,7 @@ + #include @@ -245,6 +251,7 @@ + #include @@ -269,6 +276,7 @@ + #include -- cgit v1.2.3 From 5fa3468ff2d62a4900b8ff3d9ef396e06e2ae8d6 Mon Sep 17 00:00:00 2001 From: Sander Klein Date: Mon, 22 Aug 2022 13:23:41 +0200 Subject: BGP: T4634: Allow configuration of disable-connected-check --- data/templates/frr/bgpd.frr.j2 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2 index 2ab7c8596..808e9dbe7 100644 --- a/data/templates/frr/bgpd.frr.j2 +++ b/data/templates/frr/bgpd.frr.j2 @@ -38,6 +38,9 @@ {% if config.disable_capability_negotiation is vyos_defined %} neighbor {{ neighbor }} dont-capability-negotiate {% endif %} +{% if config.disable_connected_check is vyos_defined %} + neighbor {{ neighbor }} disable-connected-check +{% endif %} {% if config.ebgp_multihop is vyos_defined %} neighbor {{ neighbor }} ebgp-multihop {{ config.ebgp_multihop }} {% endif %} -- cgit v1.2.3 From f60d0e1ce029925b843f635b36154c90049b9577 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 22 Aug 2022 17:52:58 +0200 Subject: bridge: T4632: vlan aware bridge lacks CPU forwarding The VLAN aware bridge was forwarding traffic between member ports, but traffic destined torwards the CPU was dropped. This resulted in a gateway not reachable or DHCP leases that could not be handed out. Tested via: VyOS set interfaces bridge br0 enable-vlan set interfaces bridge br0 member interface eth1 allowed-vlan '10' set interfaces bridge br0 member interface eth1 allowed-vlan '20' set interfaces bridge br0 member interface eth1 allowed-vlan '30' set interfaces bridge br0 member interface eth1 allowed-vlan '40' set interfaces bridge br0 member interface eth1 native-vlan '40' set interfaces bridge br0 member interface eth2 allowed-vlan '30' set interfaces bridge br0 member interface eth2 allowed-vlan '20' set interfaces bridge br0 member interface eth2 allowed-vlan '10' set interfaces bridge br0 member interface eth2 allowed-vlan '40' set interfaces bridge br0 vif 10 address '10.0.10.1/24' set interfaces bridge br0 vif 20 address '10.0.20.1/24' set interfaces bridge br0 vif 30 address '10.0.30.1/24' set interfaces bridge br0 vif 40 address '10.0.40.1/24' Arista vEOS vlan 10,20,30,40 interface Ethernet1 switchport trunk allowed vlan 10,20,30,40 interface Vlan10 ip address 10.0.10.2/24 interface Vlan20 ip address 10.0.20.2/24 interface Vlan30 ip address 10.0.30.2/24 interface Vlan40 ip address 10.0.40.2/24 interface Ethernet1 switchport trunk allowed vlan 10,20,30,40 switchport mode trunk spanning-tree portfast Cisco vIOS interface GigabitEthernet0/0 ip address 10.0.40.3 255.255.255.0 duplex auto speed auto media-type rj45 ! interface GigabitEthernet0/0.10 encapsulation dot1Q 10 ip address 10.0.10.3 255.255.255.0 ! interface GigabitEthernet0/0.20 encapsulation dot1Q 20 ip address 10.0.20.3 255.255.255.0 ! interface GigabitEthernet0/0.30 encapsulation dot1Q 30 ip address 10.0.30.3 255.255.255.0 ! --- python/vyos/ifconfig/bridge.py | 20 +++- smoketest/scripts/cli/test_interfaces_bridge.py | 137 ++++++++++++------------ 2 files changed, 88 insertions(+), 69 deletions(-) diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index 758967fbc..aa818bc5f 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -295,8 +295,24 @@ class BridgeIf(Interface): self.del_port(member) # enable/disable Vlan Filter - vlan_filter = '1' if 'enable_vlan' in config else '0' - self.set_vlan_filter(vlan_filter) + tmp = '1' if 'enable_vlan' in config else '0' + self.set_vlan_filter(tmp) + + # add VLAN interfaces to local 'parent' bridge to allow forwarding + if 'enable_vlan' in config: + for vlan in config.get('vif_remove', {}): + # Remove old VLANs from the bridge + cmd = f'bridge vlan del dev {self.ifname} vid {vlan} self' + self._cmd(cmd) + + for vlan in config.get('vif', {}): + cmd = f'bridge vlan add dev {self.ifname} vid {vlan} self' + self._cmd(cmd) + + # VLAN of bridge parent interface is always 1. VLAN 1 is the default + # VLAN for all unlabeled packets + cmd = f'bridge vlan add dev {self.ifname} vid 1 pvid untagged self' + self._cmd(cmd) tmp = dict_search('member.interface', config) if tmp: diff --git a/smoketest/scripts/cli/test_interfaces_bridge.py b/smoketest/scripts/cli/test_interfaces_bridge.py index 8f711af20..6d7af78eb 100755 --- a/smoketest/scripts/cli/test_interfaces_bridge.py +++ b/smoketest/scripts/cli/test_interfaces_bridge.py @@ -19,6 +19,7 @@ import json import unittest from base_interfaces_test import BasicInterfaceTest +from copy import deepcopy from glob import glob from netifaces import interfaces @@ -224,85 +225,78 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase): super().test_vif_8021q_mtu_limits() def test_bridge_vlan_filter(self): - def _verify_members() -> None: - # check member interfaces are added on the bridge - for interface in self._interfaces: - bridge_members = [] - for tmp in glob(f'/sys/class/net/{interface}/lower_*'): - bridge_members.append(os.path.basename(tmp).replace('lower_', '')) - - # We can not use assertListEqual() b/c the position of the interface - # names within the list is not fixed - self.assertEqual(len(self._members), len(bridge_members)) - for member in self._members: - self.assertIn(member, bridge_members) - - def _check_vlan_filter() -> None: - for interface in self._interfaces: - tmp = cmd(f'bridge -j vlan show dev {interface}') - tmp = json.loads(tmp) - self.assertIsNotNone(tmp) - - for interface_status in tmp: - ifname = interface_status['ifname'] - for interface in self._members: - vlan_success = 0; - if interface == ifname: - vlans_status = interface_status['vlans'] - for vlan_status in vlans_status: - vlan_id = vlan_status['vlan'] - flag_num = 0 - if 'flags' in vlan_status: - flags = vlan_status['flags'] - for flag in flags: - flag_num = flag_num +1 - if vlan_id == 2: - if flag_num == 0: - vlan_success = vlan_success + 1 - else: - for id in range(4,10): - if vlan_id == id: - if flag_num == 0: - vlan_success = vlan_success + 1 - if vlan_id >= 101: - if flag_num == 2: - vlan_success = vlan_success + 1 - self.assertGreaterEqual(vlan_success, 7) - - vif_vlan = 2 + vifs = ['10', '20', '30', '40'] + native_vlan = '20' + # Add member interface to bridge and set VLAN filter for interface in self._interfaces: base = self._base_path + [interface] self.cli_set(base + ['enable-vlan']) self.cli_set(base + ['address', '192.0.2.1/24']) - self.cli_set(base + ['vif', str(vif_vlan), 'address', '192.0.3.1/24']) - self.cli_set(base + ['vif', str(vif_vlan), 'mtu', self._mtu]) - vlan_id = 101 - allowed_vlan = 2 - allowed_vlan_range = '4-9' - # assign members to bridge interface + for vif in vifs: + self.cli_set(base + ['vif', vif, 'address', f'192.0.{vif}.1/24']) + self.cli_set(base + ['vif', vif, 'mtu', self._mtu]) + for member in self._members: base_member = base + ['member', 'interface', member] - self.cli_set(base_member + ['allowed-vlan', str(allowed_vlan)]) - self.cli_set(base_member + ['allowed-vlan', allowed_vlan_range]) - self.cli_set(base_member + ['native-vlan', str(vlan_id)]) - vlan_id += 1 + self.cli_set(base_member + ['native-vlan', native_vlan]) + for vif in vifs: + self.cli_set(base_member + ['allowed-vlan', vif]) # commit config self.cli_commit() + def _verify_members(interface, members) -> None: + # check member interfaces are added on the bridge + bridge_members = [] + for tmp in glob(f'/sys/class/net/{interface}/lower_*'): + bridge_members.append(os.path.basename(tmp).replace('lower_', '')) + + self.assertListEqual(sorted(members), sorted(bridge_members)) + + def _check_vlan_filter(interface, vifs) -> None: + configured_vlan_ids = [] + + bridge_json = cmd(f'bridge -j vlan show dev {interface}') + bridge_json = json.loads(bridge_json) + self.assertIsNotNone(bridge_json) + + for tmp in bridge_json: + self.assertIn('vlans', tmp) + + for vlan in tmp['vlans']: + self.assertIn('vlan', vlan) + configured_vlan_ids.append(str(vlan['vlan'])) + + # Verify native VLAN ID has 'PVID' flag set on individual member ports + if not interface.startswith('br') and str(vlan['vlan']) == native_vlan: + self.assertIn('flags', vlan) + self.assertIn('PVID', vlan['flags']) + + self.assertListEqual(sorted(configured_vlan_ids), sorted(vifs)) + # Verify correct setting of VLAN filter function for interface in self._interfaces: tmp = read_file(f'/sys/class/net/{interface}/bridge/vlan_filtering') self.assertEqual(tmp, '1') - # Execute the program to obtain status information and verify proper - # VLAN filter setup - _check_vlan_filter() + # Obtain status information and verify proper VLAN filter setup. + # First check if all members are present, second check if all VLANs + # are assigned on the parend bridge interface, third verify all the + # VLANs are properly setup on the downstream "member" ports + for interface in self._interfaces: + # check member interfaces are added on the bridge + _verify_members(interface, self._members) - # check member interfaces are added on the bridge - _verify_members() + # Check if all VLAN ids are properly set up. Bridge interface always + # has native VLAN 1 + tmp = deepcopy(vifs) + tmp.append('1') + _check_vlan_filter(interface, tmp) + + for member in self._members: + _check_vlan_filter(member, vifs) # change member interface description to trigger config update, # VLANs must still exist (T4565) @@ -313,12 +307,22 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase): # commit config self.cli_commit() - # check member interfaces are added on the bridge - _verify_members() + # Obtain status information and verify proper VLAN filter setup. + # First check if all members are present, second check if all VLANs + # are assigned on the parend bridge interface, third verify all the + # VLANs are properly setup on the downstream "member" ports + for interface in self._interfaces: + # check member interfaces are added on the bridge + _verify_members(interface, self._members) + + # Check if all VLAN ids are properly set up. Bridge interface always + # has native VLAN 1 + tmp = deepcopy(vifs) + tmp.append('1') + _check_vlan_filter(interface, tmp) - # Execute the program to obtain status information and verify proper - # VLAN filter setup - _check_vlan_filter() + for member in self._members: + _check_vlan_filter(member, vifs) # delete all members for interface in self._interfaces: @@ -337,7 +341,6 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase): for member in self._members: self.assertNotIn(member, bridge_members) - def test_bridge_vif_members(self): # T2945: ensure that VIFs are not dropped from bridge vifs = ['300', '400'] -- cgit v1.2.3 From 8eede91cd25242370543c8af139b716e46fcbd41 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 22 Aug 2022 10:34:31 -0500 Subject: graphql: T3993: add missing sys.exit() --- src/services/vyos-http-api-server | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index af8837e1e..190f3409d 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -678,6 +678,7 @@ if __name__ == '__main__': server_config = load_server_config() except Exception as err: logger.critical(f"Failed to load the HTTP API server config: {err}") + sys.exit(1) config_session = ConfigSession(os.getpid()) -- cgit v1.2.3 From bf178babd96ee5b898f0dfa1f6e7d5a74fe34afd Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 22 Aug 2022 10:35:52 -0500 Subject: graphql: T4544: fix for directly running on system for testing --- src/services/api/graphql/utils/schema_from_op_mode.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/services/api/graphql/utils/schema_from_op_mode.py b/src/services/api/graphql/utils/schema_from_op_mode.py index d27586747..f990aae52 100755 --- a/src/services/api/graphql/utils/schema_from_op_mode.py +++ b/src/services/api/graphql/utils/schema_from_op_mode.py @@ -25,7 +25,10 @@ from inspect import signature, getmembers, isfunction from jinja2 import Template from vyos.defaults import directories -from . util import load_as_module, is_op_mode_function_name, is_show_function_name +if __package__ is None or __package__ == '': + from util import load_as_module, is_op_mode_function_name, is_show_function_name +else: + from . util import load_as_module, is_op_mode_function_name, is_show_function_name OP_MODE_PATH = directories['op_mode'] SCHEMA_PATH = directories['api_schema'] -- cgit v1.2.3 From ed5fb0645367e4bd099fe12decbb515af1e6dcc6 Mon Sep 17 00:00:00 2001 From: Sander Klein Date: Mon, 22 Aug 2022 21:47:30 +0200 Subject: keepalived: T4526: keepalived-fifo.py unable to load config keepalived-fifo.py cannot load the VyOS config because the script is started before the commit is completely finished. This change makes sure the script waits for the commit to be completed. It retries every 0.5 seconds. If the commit is still not completed it will continue as did the original implementation. --- src/system/keepalived-fifo.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/system/keepalived-fifo.py b/src/system/keepalived-fifo.py index a8df232ae..a0fccd1d0 100755 --- a/src/system/keepalived-fifo.py +++ b/src/system/keepalived-fifo.py @@ -30,6 +30,7 @@ from vyos.ifconfig.vrrp import VRRP from vyos.configquery import ConfigTreeQuery from vyos.util import cmd from vyos.util import dict_search +from vyos.util import commit_in_progress # configure logging logger = logging.getLogger(__name__) @@ -63,6 +64,17 @@ class KeepalivedFifo: # load configuration def _config_load(self): + # For VRRP configuration to be read, the commit must be finished + count = 1 + while commit_in_progress(): + if ( count <= 40 ): + logger.debug(f'commit in progress try: {count}') + else: + logger.error(f'commit still in progress after {count} continuing anyway') + break + count += 1 + time.sleep(0.5) + try: base = ['high-availability', 'vrrp'] conf = ConfigTreeQuery() -- cgit v1.2.3 From ecaafaa26f85ba4ae3f34b5382fe0ebbe38bf13b Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Tue, 23 Aug 2022 09:21:29 +0000 Subject: https: T4597: Verify bind port before apply HTTPS API service If Nginx address/port is already binded to another service (for exampmle openconnect default port 443) https api cannot start and we don't see any error in the output. Add this check before applying service/commit --- src/conf_mode/https.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py index 3057357fc..7cd7ea42e 100755 --- a/src/conf_mode/https.py +++ b/src/conf_mode/https.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 @@ -29,6 +29,8 @@ from vyos.pki import wrap_certificate from vyos.pki import wrap_private_key from vyos.template import render from vyos.util import call +from vyos.util import check_port_availability +from vyos.util import is_listen_port_bind_service from vyos.util import write_file from vyos import airbag @@ -107,6 +109,31 @@ def verify(https): raise ConfigError("At least one 'virtual-host server-name' " "matching the 'certbot domain-name' is required.") + server_block_list = [] + + # organize by vhosts + vhost_dict = https.get('virtual-host', {}) + + if not vhost_dict: + # no specified virtual hosts (server blocks); use default + server_block_list.append(default_server_block) + else: + for vhost in list(vhost_dict): + server_block = deepcopy(default_server_block) + data = vhost_dict.get(vhost, {}) + server_block['address'] = data.get('listen-address', '*') + server_block['port'] = data.get('listen-port', '443') + server_block_list.append(server_block) + + for entry in server_block_list: + _address = entry.get('address') + _address = '0.0.0.0' if _address == '*' else _address + _port = entry.get('port') + proto = 'tcp' + if check_port_availability(_address, int(_port), proto) is not True and \ + not is_listen_port_bind_service(int(_port), 'nginx'): + raise ConfigError(f'"{proto}" port "{_port}" is used by another service') + verify_vrf(https) return None -- cgit v1.2.3 From 9b3cdfb96af98d479cb804c69baa68c1ad50b48f Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Tue, 23 Aug 2022 11:33:20 +0000 Subject: conntrack: T4623: Add conntrack statistics for op-mode --- op-mode-definitions/show-conntrack.xml.in | 6 ++++++ src/op_mode/conntrack.py | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/op-mode-definitions/show-conntrack.xml.in b/op-mode-definitions/show-conntrack.xml.in index 8d921e6a5..4cdcffcdb 100644 --- a/op-mode-definitions/show-conntrack.xml.in +++ b/op-mode-definitions/show-conntrack.xml.in @@ -7,6 +7,12 @@ Show conntrack tables entries + + + Show conntrack statistics + + sudo ${vyos_op_scripts_dir}/conntrack.py show_statistics + Show conntrack entries for table diff --git a/src/op_mode/conntrack.py b/src/op_mode/conntrack.py index 036226418..b27aa6060 100755 --- a/src/op_mode/conntrack.py +++ b/src/op_mode/conntrack.py @@ -51,6 +51,21 @@ def _get_raw_data(family): return _xml_to_dict(xml) +def _get_raw_statistics(): + entries = [] + data = cmd('sudo conntrack -S') + data = data.replace(' \t', '').split('\n') + for entry in data: + entries.append(entry.split()) + return entries + + +def get_formatted_statistics(entries): + headers = ["CPU", "Found", "Invalid", "Insert", "Insert fail", "Drop", "Early drop", "Errors", "Search restart"] + output = tabulate(entries, headers, numalign="left") + return output + + def get_formatted_output(dict_data): """ :param xml: @@ -111,6 +126,14 @@ def show(raw: bool, family: str): return get_formatted_output(conntrack_data) +def show_statistics(raw: bool): + conntrack_statistics = _get_raw_statistics() + if raw: + return conntrack_statistics + else: + return get_formatted_statistics(conntrack_statistics) + + if __name__ == '__main__': try: res = vyos.opmode.run(sys.modules[__name__]) -- cgit v1.2.3 From f66ad001e153ee42bc46edbe7df55145b7971544 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 22 Aug 2022 12:03:56 -0500 Subject: graphql: T3993: reorganize/rename directory structure --- python/vyos/defaults.py | 2 +- src/services/api/graphql/graphql/mutations.py | 4 +- src/services/api/graphql/graphql/queries.py | 4 +- src/services/api/graphql/recipes/__init__.py | 0 .../api/graphql/recipes/queries/system_status.py | 38 ---- .../remove_firewall_address_group_members.py | 35 ---- src/services/api/graphql/recipes/session.py | 207 --------------------- .../recipes/templates/create_dhcp_server.tmpl | 9 - .../templates/create_firewall_address_group.tmpl | 4 - .../create_firewall_address_ipv_6_group.tmpl | 4 - .../templates/create_interface_ethernet.tmpl | 5 - .../remove_firewall_address_group_members.tmpl | 3 - ...emove_firewall_address_ipv_6_group_members.tmpl | 3 - .../update_firewall_address_group_members.tmpl | 3 - ...pdate_firewall_address_ipv_6_group_members.tmpl | 3 - src/services/api/graphql/session/__init__.py | 0 .../api/graphql/session/composite/system_status.py | 38 ++++ .../remove_firewall_address_group_members.py | 35 ++++ src/services/api/graphql/session/session.py | 207 +++++++++++++++++++++ .../session/templates/create_dhcp_server.tmpl | 9 + .../templates/create_firewall_address_group.tmpl | 4 + .../create_firewall_address_ipv_6_group.tmpl | 4 + .../templates/create_interface_ethernet.tmpl | 5 + .../remove_firewall_address_group_members.tmpl | 3 + ...emove_firewall_address_ipv_6_group_members.tmpl | 3 + .../update_firewall_address_group_members.tmpl | 3 + ...pdate_firewall_address_ipv_6_group_members.tmpl | 3 + 27 files changed, 319 insertions(+), 319 deletions(-) delete mode 100644 src/services/api/graphql/recipes/__init__.py delete mode 100755 src/services/api/graphql/recipes/queries/system_status.py delete mode 100644 src/services/api/graphql/recipes/remove_firewall_address_group_members.py delete mode 100644 src/services/api/graphql/recipes/session.py delete mode 100644 src/services/api/graphql/recipes/templates/create_dhcp_server.tmpl delete mode 100644 src/services/api/graphql/recipes/templates/create_firewall_address_group.tmpl delete mode 100644 src/services/api/graphql/recipes/templates/create_firewall_address_ipv_6_group.tmpl delete mode 100644 src/services/api/graphql/recipes/templates/create_interface_ethernet.tmpl delete mode 100644 src/services/api/graphql/recipes/templates/remove_firewall_address_group_members.tmpl delete mode 100644 src/services/api/graphql/recipes/templates/remove_firewall_address_ipv_6_group_members.tmpl delete mode 100644 src/services/api/graphql/recipes/templates/update_firewall_address_group_members.tmpl delete mode 100644 src/services/api/graphql/recipes/templates/update_firewall_address_ipv_6_group_members.tmpl create mode 100644 src/services/api/graphql/session/__init__.py create mode 100755 src/services/api/graphql/session/composite/system_status.py create mode 100644 src/services/api/graphql/session/override/remove_firewall_address_group_members.py create mode 100644 src/services/api/graphql/session/session.py create mode 100644 src/services/api/graphql/session/templates/create_dhcp_server.tmpl create mode 100644 src/services/api/graphql/session/templates/create_firewall_address_group.tmpl create mode 100644 src/services/api/graphql/session/templates/create_firewall_address_ipv_6_group.tmpl create mode 100644 src/services/api/graphql/session/templates/create_interface_ethernet.tmpl create mode 100644 src/services/api/graphql/session/templates/remove_firewall_address_group_members.tmpl create mode 100644 src/services/api/graphql/session/templates/remove_firewall_address_ipv_6_group_members.tmpl create mode 100644 src/services/api/graphql/session/templates/update_firewall_address_group_members.tmpl create mode 100644 src/services/api/graphql/session/templates/update_firewall_address_ipv_6_group_members.tmpl diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index 09ae73eac..6894fc4da 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -26,7 +26,7 @@ directories = { "templates": "/usr/share/vyos/templates/", "certbot": "/config/auth/letsencrypt", "api_schema": "/usr/libexec/vyos/services/api/graphql/graphql/schema/", - "api_templates": "/usr/libexec/vyos/services/api/graphql/recipes/templates/", + "api_templates": "/usr/libexec/vyos/services/api/graphql/session/templates/", "vyos_udev_dir": "/run/udev/vyos" } diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py index 3e89fb239..c8ae0f516 100644 --- a/src/services/api/graphql/graphql/mutations.py +++ b/src/services/api/graphql/graphql/mutations.py @@ -21,7 +21,7 @@ from makefun import with_signature from .. import state from .. import key_auth -from api.graphql.recipes.session import Session +from api.graphql.session.session import Session mutation = ObjectType("Mutation") @@ -71,7 +71,7 @@ def make_mutation_resolver(mutation_name, class_name, session_func): # one may override the session functions with a local subclass try: - mod = import_module(f'api.graphql.recipes.{func_base_name}') + mod = import_module(f'api.graphql.session.override.{func_base_name}') klass = getattr(mod, class_name) except ImportError: # otherwise, dynamically generate subclass to invoke subclass diff --git a/src/services/api/graphql/graphql/queries.py b/src/services/api/graphql/graphql/queries.py index f6544709e..921a66274 100644 --- a/src/services/api/graphql/graphql/queries.py +++ b/src/services/api/graphql/graphql/queries.py @@ -21,7 +21,7 @@ from makefun import with_signature from .. import state from .. import key_auth -from api.graphql.recipes.session import Session +from api.graphql.session.session import Session query = ObjectType("Query") @@ -71,7 +71,7 @@ def make_query_resolver(query_name, class_name, session_func): # one may override the session functions with a local subclass try: - mod = import_module(f'api.graphql.recipes.{func_base_name}') + mod = import_module(f'api.graphql.session.override.{func_base_name}') klass = getattr(mod, class_name) except ImportError: # otherwise, dynamically generate subclass to invoke subclass diff --git a/src/services/api/graphql/recipes/__init__.py b/src/services/api/graphql/recipes/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/services/api/graphql/recipes/queries/system_status.py b/src/services/api/graphql/recipes/queries/system_status.py deleted file mode 100755 index 8dadcc9f3..000000000 --- a/src/services/api/graphql/recipes/queries/system_status.py +++ /dev/null @@ -1,38 +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 sys -import json -import importlib.util - -from vyos.defaults import directories - -from api.graphql.utils.util import load_op_mode_as_module - -def get_system_version() -> dict: - show_version = load_op_mode_as_module('version.py') - return show_version.show(raw=True, funny=False) - -def get_system_uptime() -> dict: - show_uptime = load_op_mode_as_module('show_uptime.py') - return show_uptime.get_raw_data() - -def get_system_ram_usage() -> dict: - show_ram = load_op_mode_as_module('memory.py') - return show_ram.show(raw=True) diff --git a/src/services/api/graphql/recipes/remove_firewall_address_group_members.py b/src/services/api/graphql/recipes/remove_firewall_address_group_members.py deleted file mode 100644 index b91932e14..000000000 --- a/src/services/api/graphql/recipes/remove_firewall_address_group_members.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2021 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 . - -from . session import Session - -class RemoveFirewallAddressGroupMembers(Session): - def __init__(self, session, data): - super().__init__(session, data) - - # Define any custom processing of parameters here by overriding - # configure: - # - # def configure(self): - # self._data = transform_data(self._data) - # super().configure() - # self.clean_up() - - def configure(self): - super().configure() - - group_name = self._data['name'] - path = ['firewall', 'group', 'address-group', group_name] - self.delete_path_if_childless(path) diff --git a/src/services/api/graphql/recipes/session.py b/src/services/api/graphql/recipes/session.py deleted file mode 100644 index ac185beb7..000000000 --- a/src/services/api/graphql/recipes/session.py +++ /dev/null @@ -1,207 +0,0 @@ -# Copyright 2021-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 . - -import os -import json - -from ariadne import convert_camel_case_to_snake - -from vyos.config import Config -from vyos.configtree import ConfigTree -from vyos.defaults import directories -from vyos.template import render - -from api.graphql.utils.util import load_op_mode_as_module, split_compound_op_mode_name - -op_mode_include_file = os.path.join(directories['data'], 'op-mode-standardized.json') - -class Session: - """ - Wrapper for calling configsession functions based on GraphQL requests. - Non-nullable fields in the respective schema allow avoiding a key check - in 'data'. - """ - def __init__(self, session, data): - self._session = session - self._data = data - self._name = convert_camel_case_to_snake(type(self).__name__) - - try: - with open(op_mode_include_file) as f: - self._op_mode_list = json.loads(f.read()) - except Exception: - self._op_mode_list = None - - def configure(self): - session = self._session - data = self._data - func_base_name = self._name - - tmpl_file = f'{func_base_name}.tmpl' - cmd_file = f'/tmp/{func_base_name}.cmds' - tmpl_dir = directories['api_templates'] - - try: - render(cmd_file, tmpl_file, data, location=tmpl_dir) - commands = [] - with open(cmd_file) as f: - lines = f.readlines() - for line in lines: - commands.append(line.split()) - for cmd in commands: - if cmd[0] == 'set': - session.set(cmd[1:]) - elif cmd[0] == 'delete': - session.delete(cmd[1:]) - else: - raise ValueError('Operation must be "set" or "delete"') - session.commit() - except Exception as error: - raise error - - def delete_path_if_childless(self, path): - session = self._session - config = Config(session.get_session_env()) - if not config.list_nodes(path): - session.delete(path) - session.commit() - - def show_config(self): - session = self._session - data = self._data - out = '' - - try: - out = session.show_config(data['path']) - if data.get('config_format', '') == 'json': - config_tree = vyos.configtree.ConfigTree(out) - out = json.loads(config_tree.to_json()) - except Exception as error: - raise error - - return out - - def save(self): - session = self._session - data = self._data - if 'file_name' not in data or not data['file_name']: - data['file_name'] = '/config/config.boot' - - try: - session.save_config(data['file_name']) - except Exception as error: - raise error - - def load(self): - session = self._session - data = self._data - - try: - session.load_config(data['file_name']) - session.commit() - except Exception as error: - raise error - - def show(self): - session = self._session - data = self._data - out = '' - - try: - out = session.show(data['path']) - except Exception as error: - raise error - - return out - - def add(self): - session = self._session - data = self._data - - try: - res = session.install_image(data['location']) - except Exception as error: - raise error - - return res - - def delete(self): - session = self._session - data = self._data - - try: - res = session.remove_image(data['name']) - except Exception as error: - raise error - - return res - - def system_status(self): - import api.graphql.recipes.queries.system_status as system_status - - session = self._session - data = self._data - - status = {} - status['host_name'] = session.show(['host', 'name']).strip() - status['version'] = system_status.get_system_version() - status['uptime'] = system_status.get_system_uptime() - status['ram'] = system_status.get_system_ram_usage() - - return status - - def gen_op_query(self): - session = self._session - data = self._data - name = self._name - op_mode_list = self._op_mode_list - - # handle the case that the op-mode file contains underscores: - if op_mode_list is None: - raise FileNotFoundError(f"No op-mode file list at '{op_mode_include_file}'") - (func_name, scriptname) = split_compound_op_mode_name(name, op_mode_list) - if scriptname == '': - raise FileNotFoundError(f"No op-mode file named in string '{name}'") - - mod = load_op_mode_as_module(f'{scriptname}') - func = getattr(mod, func_name) - if len(list(data)) > 0: - res = func(True, **data) - else: - res = func(True) - - return res - - def gen_op_mutation(self): - session = self._session - data = self._data - name = self._name - op_mode_list = self._op_mode_list - - # handle the case that the op-mode file name contains underscores: - if op_mode_list is None: - raise FileNotFoundError(f"No op-mode file list at '{op_mode_include_file}'") - (func_name, scriptname) = split_compound_op_mode_name(name, op_mode_list) - if scriptname == '': - raise FileNotFoundError(f"No op-mode file named in string '{name}'") - - mod = load_op_mode_as_module(f'{scriptname}') - func = getattr(mod, func_name) - if len(list(data)) > 0: - res = func(**data) - else: - res = func() - - return res diff --git a/src/services/api/graphql/recipes/templates/create_dhcp_server.tmpl b/src/services/api/graphql/recipes/templates/create_dhcp_server.tmpl deleted file mode 100644 index 70de43183..000000000 --- a/src/services/api/graphql/recipes/templates/create_dhcp_server.tmpl +++ /dev/null @@ -1,9 +0,0 @@ -set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} default-router {{ default_router }} -set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} name-server {{ name_server }} -set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} domain-name {{ domain_name }} -set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} lease {{ lease }} -set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} range {{ range }} start {{ start }} -set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} range {{ range }} stop {{ stop }} -set service dns forwarding allow-from {{ dns_forwarding_allow_from }} -set service dns forwarding cache-size {{ dns_forwarding_cache_size }} -set service dns forwarding listen-address {{ dns_forwarding_listen_address }} diff --git a/src/services/api/graphql/recipes/templates/create_firewall_address_group.tmpl b/src/services/api/graphql/recipes/templates/create_firewall_address_group.tmpl deleted file mode 100644 index a890d0086..000000000 --- a/src/services/api/graphql/recipes/templates/create_firewall_address_group.tmpl +++ /dev/null @@ -1,4 +0,0 @@ -set firewall group address-group {{ name }} -{% for add in address %} -set firewall group address-group {{ name }} address {{ add }} -{% endfor %} diff --git a/src/services/api/graphql/recipes/templates/create_firewall_address_ipv_6_group.tmpl b/src/services/api/graphql/recipes/templates/create_firewall_address_ipv_6_group.tmpl deleted file mode 100644 index e9b660722..000000000 --- a/src/services/api/graphql/recipes/templates/create_firewall_address_ipv_6_group.tmpl +++ /dev/null @@ -1,4 +0,0 @@ -set firewall group ipv6-address-group {{ name }} -{% for add in address %} -set firewall group ipv6-address-group {{ name }} address {{ add }} -{% endfor %} diff --git a/src/services/api/graphql/recipes/templates/create_interface_ethernet.tmpl b/src/services/api/graphql/recipes/templates/create_interface_ethernet.tmpl deleted file mode 100644 index d9d7ed691..000000000 --- a/src/services/api/graphql/recipes/templates/create_interface_ethernet.tmpl +++ /dev/null @@ -1,5 +0,0 @@ -{% if replace %} -delete interfaces ethernet {{ interface }} address -{% endif %} -set interfaces ethernet {{ interface }} address {{ address }} -set interfaces ethernet {{ interface }} description {{ description }} diff --git a/src/services/api/graphql/recipes/templates/remove_firewall_address_group_members.tmpl b/src/services/api/graphql/recipes/templates/remove_firewall_address_group_members.tmpl deleted file mode 100644 index 458f3e5fc..000000000 --- a/src/services/api/graphql/recipes/templates/remove_firewall_address_group_members.tmpl +++ /dev/null @@ -1,3 +0,0 @@ -{% for add in address %} -delete firewall group address-group {{ name }} address {{ add }} -{% endfor %} diff --git a/src/services/api/graphql/recipes/templates/remove_firewall_address_ipv_6_group_members.tmpl b/src/services/api/graphql/recipes/templates/remove_firewall_address_ipv_6_group_members.tmpl deleted file mode 100644 index 0efa0b226..000000000 --- a/src/services/api/graphql/recipes/templates/remove_firewall_address_ipv_6_group_members.tmpl +++ /dev/null @@ -1,3 +0,0 @@ -{% for add in address %} -delete firewall group ipv6-address-group {{ name }} address {{ add }} -{% endfor %} diff --git a/src/services/api/graphql/recipes/templates/update_firewall_address_group_members.tmpl b/src/services/api/graphql/recipes/templates/update_firewall_address_group_members.tmpl deleted file mode 100644 index f56c61231..000000000 --- a/src/services/api/graphql/recipes/templates/update_firewall_address_group_members.tmpl +++ /dev/null @@ -1,3 +0,0 @@ -{% for add in address %} -set firewall group address-group {{ name }} address {{ add }} -{% endfor %} diff --git a/src/services/api/graphql/recipes/templates/update_firewall_address_ipv_6_group_members.tmpl b/src/services/api/graphql/recipes/templates/update_firewall_address_ipv_6_group_members.tmpl deleted file mode 100644 index f98a5517c..000000000 --- a/src/services/api/graphql/recipes/templates/update_firewall_address_ipv_6_group_members.tmpl +++ /dev/null @@ -1,3 +0,0 @@ -{% for add in address %} -set firewall group ipv6-address-group {{ name }} address {{ add }} -{% endfor %} diff --git a/src/services/api/graphql/session/__init__.py b/src/services/api/graphql/session/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/services/api/graphql/session/composite/system_status.py b/src/services/api/graphql/session/composite/system_status.py new file mode 100755 index 000000000..8dadcc9f3 --- /dev/null +++ b/src/services/api/graphql/session/composite/system_status.py @@ -0,0 +1,38 @@ +#!/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 sys +import json +import importlib.util + +from vyos.defaults import directories + +from api.graphql.utils.util import load_op_mode_as_module + +def get_system_version() -> dict: + show_version = load_op_mode_as_module('version.py') + return show_version.show(raw=True, funny=False) + +def get_system_uptime() -> dict: + show_uptime = load_op_mode_as_module('show_uptime.py') + return show_uptime.get_raw_data() + +def get_system_ram_usage() -> dict: + show_ram = load_op_mode_as_module('memory.py') + return show_ram.show(raw=True) diff --git a/src/services/api/graphql/session/override/remove_firewall_address_group_members.py b/src/services/api/graphql/session/override/remove_firewall_address_group_members.py new file mode 100644 index 000000000..b91932e14 --- /dev/null +++ b/src/services/api/graphql/session/override/remove_firewall_address_group_members.py @@ -0,0 +1,35 @@ +# Copyright 2021 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 . + +from . session import Session + +class RemoveFirewallAddressGroupMembers(Session): + def __init__(self, session, data): + super().__init__(session, data) + + # Define any custom processing of parameters here by overriding + # configure: + # + # def configure(self): + # self._data = transform_data(self._data) + # super().configure() + # self.clean_up() + + def configure(self): + super().configure() + + group_name = self._data['name'] + path = ['firewall', 'group', 'address-group', group_name] + self.delete_path_if_childless(path) diff --git a/src/services/api/graphql/session/session.py b/src/services/api/graphql/session/session.py new file mode 100644 index 000000000..23bc7154c --- /dev/null +++ b/src/services/api/graphql/session/session.py @@ -0,0 +1,207 @@ +# Copyright 2021-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 . + +import os +import json + +from ariadne import convert_camel_case_to_snake + +from vyos.config import Config +from vyos.configtree import ConfigTree +from vyos.defaults import directories +from vyos.template import render + +from api.graphql.utils.util import load_op_mode_as_module, split_compound_op_mode_name + +op_mode_include_file = os.path.join(directories['data'], 'op-mode-standardized.json') + +class Session: + """ + Wrapper for calling configsession functions based on GraphQL requests. + Non-nullable fields in the respective schema allow avoiding a key check + in 'data'. + """ + def __init__(self, session, data): + self._session = session + self._data = data + self._name = convert_camel_case_to_snake(type(self).__name__) + + try: + with open(op_mode_include_file) as f: + self._op_mode_list = json.loads(f.read()) + except Exception: + self._op_mode_list = None + + def configure(self): + session = self._session + data = self._data + func_base_name = self._name + + tmpl_file = f'{func_base_name}.tmpl' + cmd_file = f'/tmp/{func_base_name}.cmds' + tmpl_dir = directories['api_templates'] + + try: + render(cmd_file, tmpl_file, data, location=tmpl_dir) + commands = [] + with open(cmd_file) as f: + lines = f.readlines() + for line in lines: + commands.append(line.split()) + for cmd in commands: + if cmd[0] == 'set': + session.set(cmd[1:]) + elif cmd[0] == 'delete': + session.delete(cmd[1:]) + else: + raise ValueError('Operation must be "set" or "delete"') + session.commit() + except Exception as error: + raise error + + def delete_path_if_childless(self, path): + session = self._session + config = Config(session.get_session_env()) + if not config.list_nodes(path): + session.delete(path) + session.commit() + + def show_config(self): + session = self._session + data = self._data + out = '' + + try: + out = session.show_config(data['path']) + if data.get('config_format', '') == 'json': + config_tree = vyos.configtree.ConfigTree(out) + out = json.loads(config_tree.to_json()) + except Exception as error: + raise error + + return out + + def save(self): + session = self._session + data = self._data + if 'file_name' not in data or not data['file_name']: + data['file_name'] = '/config/config.boot' + + try: + session.save_config(data['file_name']) + except Exception as error: + raise error + + def load(self): + session = self._session + data = self._data + + try: + session.load_config(data['file_name']) + session.commit() + except Exception as error: + raise error + + def show(self): + session = self._session + data = self._data + out = '' + + try: + out = session.show(data['path']) + except Exception as error: + raise error + + return out + + def add(self): + session = self._session + data = self._data + + try: + res = session.install_image(data['location']) + except Exception as error: + raise error + + return res + + def delete(self): + session = self._session + data = self._data + + try: + res = session.remove_image(data['name']) + except Exception as error: + raise error + + return res + + def system_status(self): + import api.graphql.session.composite.system_status as system_status + + session = self._session + data = self._data + + status = {} + status['host_name'] = session.show(['host', 'name']).strip() + status['version'] = system_status.get_system_version() + status['uptime'] = system_status.get_system_uptime() + status['ram'] = system_status.get_system_ram_usage() + + return status + + def gen_op_query(self): + session = self._session + data = self._data + name = self._name + op_mode_list = self._op_mode_list + + # handle the case that the op-mode file contains underscores: + if op_mode_list is None: + raise FileNotFoundError(f"No op-mode file list at '{op_mode_include_file}'") + (func_name, scriptname) = split_compound_op_mode_name(name, op_mode_list) + if scriptname == '': + raise FileNotFoundError(f"No op-mode file named in string '{name}'") + + mod = load_op_mode_as_module(f'{scriptname}') + func = getattr(mod, func_name) + if len(list(data)) > 0: + res = func(True, **data) + else: + res = func(True) + + return res + + def gen_op_mutation(self): + session = self._session + data = self._data + name = self._name + op_mode_list = self._op_mode_list + + # handle the case that the op-mode file name contains underscores: + if op_mode_list is None: + raise FileNotFoundError(f"No op-mode file list at '{op_mode_include_file}'") + (func_name, scriptname) = split_compound_op_mode_name(name, op_mode_list) + if scriptname == '': + raise FileNotFoundError(f"No op-mode file named in string '{name}'") + + mod = load_op_mode_as_module(f'{scriptname}') + func = getattr(mod, func_name) + if len(list(data)) > 0: + res = func(**data) + else: + res = func() + + return res diff --git a/src/services/api/graphql/session/templates/create_dhcp_server.tmpl b/src/services/api/graphql/session/templates/create_dhcp_server.tmpl new file mode 100644 index 000000000..70de43183 --- /dev/null +++ b/src/services/api/graphql/session/templates/create_dhcp_server.tmpl @@ -0,0 +1,9 @@ +set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} default-router {{ default_router }} +set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} name-server {{ name_server }} +set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} domain-name {{ domain_name }} +set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} lease {{ lease }} +set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} range {{ range }} start {{ start }} +set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} range {{ range }} stop {{ stop }} +set service dns forwarding allow-from {{ dns_forwarding_allow_from }} +set service dns forwarding cache-size {{ dns_forwarding_cache_size }} +set service dns forwarding listen-address {{ dns_forwarding_listen_address }} diff --git a/src/services/api/graphql/session/templates/create_firewall_address_group.tmpl b/src/services/api/graphql/session/templates/create_firewall_address_group.tmpl new file mode 100644 index 000000000..a890d0086 --- /dev/null +++ b/src/services/api/graphql/session/templates/create_firewall_address_group.tmpl @@ -0,0 +1,4 @@ +set firewall group address-group {{ name }} +{% for add in address %} +set firewall group address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/services/api/graphql/session/templates/create_firewall_address_ipv_6_group.tmpl b/src/services/api/graphql/session/templates/create_firewall_address_ipv_6_group.tmpl new file mode 100644 index 000000000..e9b660722 --- /dev/null +++ b/src/services/api/graphql/session/templates/create_firewall_address_ipv_6_group.tmpl @@ -0,0 +1,4 @@ +set firewall group ipv6-address-group {{ name }} +{% for add in address %} +set firewall group ipv6-address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/services/api/graphql/session/templates/create_interface_ethernet.tmpl b/src/services/api/graphql/session/templates/create_interface_ethernet.tmpl new file mode 100644 index 000000000..d9d7ed691 --- /dev/null +++ b/src/services/api/graphql/session/templates/create_interface_ethernet.tmpl @@ -0,0 +1,5 @@ +{% if replace %} +delete interfaces ethernet {{ interface }} address +{% endif %} +set interfaces ethernet {{ interface }} address {{ address }} +set interfaces ethernet {{ interface }} description {{ description }} diff --git a/src/services/api/graphql/session/templates/remove_firewall_address_group_members.tmpl b/src/services/api/graphql/session/templates/remove_firewall_address_group_members.tmpl new file mode 100644 index 000000000..458f3e5fc --- /dev/null +++ b/src/services/api/graphql/session/templates/remove_firewall_address_group_members.tmpl @@ -0,0 +1,3 @@ +{% for add in address %} +delete firewall group address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/services/api/graphql/session/templates/remove_firewall_address_ipv_6_group_members.tmpl b/src/services/api/graphql/session/templates/remove_firewall_address_ipv_6_group_members.tmpl new file mode 100644 index 000000000..0efa0b226 --- /dev/null +++ b/src/services/api/graphql/session/templates/remove_firewall_address_ipv_6_group_members.tmpl @@ -0,0 +1,3 @@ +{% for add in address %} +delete firewall group ipv6-address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/services/api/graphql/session/templates/update_firewall_address_group_members.tmpl b/src/services/api/graphql/session/templates/update_firewall_address_group_members.tmpl new file mode 100644 index 000000000..f56c61231 --- /dev/null +++ b/src/services/api/graphql/session/templates/update_firewall_address_group_members.tmpl @@ -0,0 +1,3 @@ +{% for add in address %} +set firewall group address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/services/api/graphql/session/templates/update_firewall_address_ipv_6_group_members.tmpl b/src/services/api/graphql/session/templates/update_firewall_address_ipv_6_group_members.tmpl new file mode 100644 index 000000000..f98a5517c --- /dev/null +++ b/src/services/api/graphql/session/templates/update_firewall_address_ipv_6_group_members.tmpl @@ -0,0 +1,3 @@ +{% for add in address %} +set firewall group ipv6-address-group {{ name }} address {{ add }} +{% endfor %} -- cgit v1.2.3 From 38ab693dc9755f249283a6ded00c2e4d966b3380 Mon Sep 17 00:00:00 2001 From: aapostoliuk Date: Tue, 9 Aug 2022 17:21:12 +0300 Subject: opennhrp: T1070: Fixed removal all SAs in script Fixed removal all dmvpn SAs. Changed vici terminate by child-sa name on terminate by ike-id --- src/etc/opennhrp/opennhrp-script.py | 315 +++++++++++++++++++++++++++++------- 1 file changed, 253 insertions(+), 62 deletions(-) diff --git a/src/etc/opennhrp/opennhrp-script.py b/src/etc/opennhrp/opennhrp-script.py index 8274e6564..a5293c97e 100755 --- a/src/etc/opennhrp/opennhrp-script.py +++ b/src/etc/opennhrp/opennhrp-script.py @@ -18,44 +18,120 @@ import os import re import sys import vici + from json import loads +from pathlib import Path +from vyos.logger import getLogger from vyos.util import cmd from vyos.util import process_named_running -NHRP_CONFIG = "/run/opennhrp/opennhrp.conf" +NHRP_CONFIG: str = '/run/opennhrp/opennhrp.conf' + + +def vici_get_ipsec_uniqueid(conn: str, src_nbma: str, + dst_nbma: str) -> list[str]: + """ Find and return IKE SAs by src nbma and dst nbma + + Args: + conn (str): a connection name + src_nbma (str): an IP address of NBMA source + dst_nbma (str): an IP address of NBMA destination + + Returns: + list: a list of IKE connections that match a criteria + """ + if not conn or not src_nbma or not dst_nbma: + logger.error( + f'Incomplete input data for resolving IKE unique ids: ' + f'conn: {conn}, src_nbma: {src_nbma}, dst_nbma: {dst_nbma}') + return [] + + try: + logger.info( + f'Resolving IKE unique ids for: conn: {conn}, ' + f'src_nbma: {src_nbma}, dst_nbma: {dst_nbma}') + session: vici.Session = vici.Session() + list_ikeid: list[str] = [] + list_sa = session.list_sas({'ike': conn}) + for sa in list_sa: + if sa[conn]['local-host'].decode('ascii') == src_nbma \ + and sa[conn]['remote-host'].decode('ascii') == dst_nbma: + list_ikeid.append(sa[conn]['uniqueid'].decode('ascii')) + return list_ikeid + except Exception as err: + logger.error(f'Unable to find unique ids for IKE: {err}') + return [] + + +def vici_ike_terminate(list_ikeid: list[str]) -> bool: + """Terminating IKE SAs by list of IKE IDs + + Args: + list_ikeid (list[str]): a list of IKE ids to terminate + + Returns: + bool: result of termination action + """ + if not list: + logger.warning('An empty list for termination was provided') + return False + + try: + session = vici.Session() + for ikeid in list_ikeid: + logger.info(f'Terminating IKE SA with id {ikeid}') + session.terminate({'ike-id': ikeid, 'timeout': '-1'}) + return True + except Exception as err: + logger.error(f'Failed to terminate SA for IKE ids {list_ikeid}: {err}') + return False + +def parse_type_ipsec(interface: str) -> tuple[str, str]: + """Get DMVPN Type and NHRP Profile from the configuration -def parse_type_ipsec(interface): - with open(NHRP_CONFIG, 'r') as f: - lines = f.readlines() - match = rf'^interface {interface} #(hub|spoke)(?:\s([\w-]+))?$' - for line in lines: - m = re.match(match, line) - if m: - return m[1], m[2] - return None, None + Args: + interface (str): a name of interface + + Returns: + tuple[str, str]: `peer_type` and `profile_name` + """ + if not interface: + logger.error('Cannot find peer type - no input provided') + return '', '' + + config_file: str = Path(NHRP_CONFIG).read_text() + regex: str = rf'^interface {interface} #(?Phub|spoke) ?(?P[^\n]*)$' + match = re.search(regex, config_file, re.M) + if match: + return match.groupdict()['peer_type'], match.groupdict()[ + 'profile_name'] + return '', '' def add_peer_route(nbma_src: str, nbma_dst: str, mtu: str) -> None: """Add a route to a NBMA peer Args: - nmba_src (str): a local IP address + nbma_src (str): a local IP address nbma_dst (str): a remote IP address mtu (str): a MTU for a route """ + logger.info(f'Adding route from {nbma_src} to {nbma_dst} with MTU {mtu}') # Find routes to a peer - route_get_cmd = f'sudo ip -j route get {nbma_dst} from {nbma_src}' + route_get_cmd: str = f'sudo ip --json route get {nbma_dst} from {nbma_src}' try: route_info_data = loads(cmd(route_get_cmd)) except Exception as err: - print(f'Unable to find a route to {nbma_dst}: {err}') + logger.error(f'Unable to find a route to {nbma_dst}: {err}') + return # Check if an output has an expected format if not isinstance(route_info_data, list): - print(f'Garbage returned from the "{route_get_cmd}" command: \ - {route_info_data}') + logger.error( + f'Garbage returned from the "{route_get_cmd}" ' + f'command: {route_info_data}') return # Add static routes to a peer @@ -76,104 +152,217 @@ def add_peer_route(nbma_src: str, nbma_dst: str, mtu: str) -> None: try: cmd(route_add_cmd) except Exception as err: - print(f'Unable to add a route using command "{route_add_cmd}": \ - {err}') + logger.error( + f'Unable to add a route using command "{route_add_cmd}": ' + f'{err}') -def vici_initiate(conn, child_sa, src_addr, dest_addr): - try: - session = vici.Session() - logs = session.initiate({ - 'ike': conn, - 'child': child_sa, - 'timeout': '-1', - 'my-host': src_addr, - 'other-host': dest_addr - }) - for log in logs: - message = log['msg'].decode('ascii') - print('INIT LOG:', message) - return True - except: - return None +def vici_initiate(conn: str, child_sa: str, src_addr: str, + dest_addr: str) -> bool: + """Initiate IKE SA connection with specific peer + Args: + conn (str): an IKE connection name + child_sa (str): a child SA profile name + src_addr (str): NBMA local address + dest_addr (str): NBMA address of a peer -def vici_terminate(conn, child_sa, src_addr, dest_addr): + Returns: + bool: a result of initiation command + """ + logger.info( + f'Trying to initiate connection. Name: {conn}, child sa: {child_sa}, ' + f'src_addr: {src_addr}, dst_addr: {dest_addr}') try: session = vici.Session() - logs = session.terminate({ + session.initiate({ 'ike': conn, 'child': child_sa, 'timeout': '-1', 'my-host': src_addr, 'other-host': dest_addr }) - for log in logs: - message = log['msg'].decode('ascii') - print('TERM LOG:', message) return True - except: - return None + except Exception as err: + logger.error(f'Unable to initiate connection {err}') + return False + + +def vici_terminate(conn: str, src_addr: str, dest_addr: str) -> None: + """Find and terminate IKE SAs by local NBMA and remote NBMA addresses + + Args: + conn (str): IKE connection name + src_addr (str): NBMA local address + dest_addr (str): NBMA address of a peer + """ + logger.info( + f'Terminating IKE connection {conn} between {src_addr} ' + f'and {dest_addr}') + ikeid_list: list[str] = vici_get_ipsec_uniqueid(conn, src_addr, dest_addr) -def iface_up(interface): - cmd(f'sudo ip route flush proto 42 dev {interface}') - cmd(f'sudo ip neigh flush dev {interface}') + if not ikeid_list: + logger.warning( + f'No active sessions found for IKE profile {conn}, ' + f'local NBMA {src_addr}, remote NBMA {dest_addr}') + else: + vici_ike_terminate(ikeid_list) -def peer_up(dmvpn_type, conn): - # src_addr = os.getenv('NHRP_SRCADDR') +def iface_up(interface: str) -> None: + """Proceed tunnel interface UP event + + Args: + interface (str): an interface name + """ + if not interface: + logger.warning('No interface name provided for UP event') + + logger.info(f'Turning up interface {interface}') + try: + cmd(f'sudo ip route flush proto 42 dev {interface}') + cmd(f'sudo ip neigh flush dev {interface}') + except Exception as err: + logger.error( + f'Unable to flush route on interface "{interface}": {err}') + + +def peer_up(dmvpn_type: str, conn: str) -> None: + """Proceed NHRP peer UP event + + Args: + dmvpn_type (str): a type of peer + conn (str): an IKE profile name + """ + logger.info(f'Peer UP event for {dmvpn_type} using IKE profile {conn}') src_nbma = os.getenv('NHRP_SRCNBMA') - # dest_addr = os.getenv('NHRP_DESTADDR') dest_nbma = os.getenv('NHRP_DESTNBMA') dest_mtu = os.getenv('NHRP_DESTMTU') + if not src_nbma or not dest_nbma: + logger.error( + f'Can not get NHRP NBMA addresses: local {src_nbma}, ' + f'remote {dest_nbma}') + return + + logger.info(f'NBMA addresses: local {src_nbma}, remote {dest_nbma}') if dest_mtu: add_peer_route(src_nbma, dest_nbma, dest_mtu) - if conn and dmvpn_type == 'spoke' and process_named_running('charon'): - vici_terminate(conn, 'dmvpn', src_nbma, dest_nbma) + vici_terminate(conn, src_nbma, dest_nbma) vici_initiate(conn, 'dmvpn', src_nbma, dest_nbma) -def peer_down(dmvpn_type, conn): +def peer_down(dmvpn_type: str, conn: str) -> None: + """Proceed NHRP peer DOWN event + + Args: + dmvpn_type (str): a type of peer + conn (str): an IKE profile name + """ + logger.info(f'Peer DOWN event for {dmvpn_type} using IKE profile {conn}') + src_nbma = os.getenv('NHRP_SRCNBMA') dest_nbma = os.getenv('NHRP_DESTNBMA') + if not src_nbma or not dest_nbma: + logger.error( + f'Can not get NHRP NBMA addresses: local {src_nbma}, ' + f'remote {dest_nbma}') + return + + logger.info(f'NBMA addresses: local {src_nbma}, remote {dest_nbma}') if conn and dmvpn_type == 'spoke' and process_named_running('charon'): - vici_terminate(conn, 'dmvpn', src_nbma, dest_nbma) + vici_terminate(conn, src_nbma, dest_nbma) + try: + cmd(f'sudo ip route del {dest_nbma} src {src_nbma} proto 42') + except Exception as err: + logger.error( + f'Unable to del route from {src_nbma} to {dest_nbma}: {err}') - cmd(f'sudo ip route del {dest_nbma} src {src_nbma} proto 42') +def route_up(interface: str) -> None: + """Proceed NHRP route UP event + + Args: + interface (str): an interface name + """ + logger.info(f'Route UP event for interface {interface}') -def route_up(interface): dest_addr = os.getenv('NHRP_DESTADDR') dest_prefix = os.getenv('NHRP_DESTPREFIX') next_hop = os.getenv('NHRP_NEXTHOP') - cmd(f'sudo ip route replace {dest_addr}/{dest_prefix} proto 42 \ - via {next_hop} dev {interface}') - cmd('sudo ip route flush cache') + if not dest_addr or not dest_prefix or not next_hop: + logger.error( + f'Can not get route details: dest_addr {dest_addr}, ' + f'dest_prefix {dest_prefix}, next_hop {next_hop}') + return + + logger.info( + f'Route details: dest_addr {dest_addr}, dest_prefix {dest_prefix}, ' + f'next_hop {next_hop}') + try: + cmd(f'sudo ip route replace {dest_addr}/{dest_prefix} proto 42 \ + via {next_hop} dev {interface}') + cmd('sudo ip route flush cache') + except Exception as err: + logger.error( + f'Unable replace or flush route to {dest_addr}/{dest_prefix} ' + f'via {next_hop} dev {interface}: {err}') + + +def route_down(interface: str) -> None: + """Proceed NHRP route DOWN event + + Args: + interface (str): an interface name + """ + logger.info(f'Route DOWN event for interface {interface}') -def route_down(interface): dest_addr = os.getenv('NHRP_DESTADDR') dest_prefix = os.getenv('NHRP_DESTPREFIX') - cmd(f'sudo ip route del {dest_addr}/{dest_prefix} proto 42') - cmd('sudo ip route flush cache') + if not dest_addr or not dest_prefix: + logger.error( + f'Can not get route details: dest_addr {dest_addr}, ' + f'dest_prefix {dest_prefix}') + return + + logger.info( + f'Route details: dest_addr {dest_addr}, dest_prefix {dest_prefix}') + try: + cmd(f'sudo ip route del {dest_addr}/{dest_prefix} proto 42') + cmd('sudo ip route flush cache') + except Exception as err: + logger.error( + f'Unable delete or flush route to {dest_addr}/{dest_prefix}: ' + f'{err}') if __name__ == '__main__': + logger = getLogger('opennhrp-script', syslog=True) + logger.debug( + f'Running script with arguments: {sys.argv}, ' + f'environment: {os.environ}') + action = sys.argv[1] interface = os.getenv('NHRP_INTERFACE') - dmvpn_type, profile_name = parse_type_ipsec(interface) - dmvpn_conn = None + if not interface: + logger.error('Can not get NHRP interface name') + sys.exit(1) - if profile_name: - dmvpn_conn = f'dmvpn-{profile_name}-{interface}' + dmvpn_type, profile_name = parse_type_ipsec(interface) + if not dmvpn_type: + logger.info(f'Interface {interface} is not NHRP tunnel') + sys.exit() + dmvpn_conn: str = '' + if profile_name: + dmvpn_conn: str = f'dmvpn-{profile_name}-{interface}' if action == 'interface-up': iface_up(interface) elif action == 'peer-register': @@ -186,3 +375,5 @@ if __name__ == '__main__': route_up(interface) elif action == 'route-down': route_down(interface) + + sys.exit() -- cgit v1.2.3 From 8d4205a99a9f0475da2b286d51febb36c7350e6e Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Wed, 24 Aug 2022 11:34:38 +0000 Subject: nat66: T4626: Rewrite op-mode show nat66 rules Rewrite op-mode "show nat66 source|destination rules" to the new format use "show_rules --direction --family " Delete old script show_nat66_rules.py --- op-mode-definitions/nat.xml.in | 4 +- op-mode-definitions/nat66.xml.in | 4 +- src/op_mode/nat.py | 31 +++++++----- src/op_mode/show_nat66_rules.py | 102 --------------------------------------- 4 files changed, 22 insertions(+), 119 deletions(-) delete mode 100755 src/op_mode/show_nat66_rules.py diff --git a/op-mode-definitions/nat.xml.in b/op-mode-definitions/nat.xml.in index 7148c1128..e89c3801f 100644 --- a/op-mode-definitions/nat.xml.in +++ b/op-mode-definitions/nat.xml.in @@ -16,7 +16,7 @@ Show configured source NAT rules - ${vyos_op_scripts_dir}/nat.py show_rules --direction source + ${vyos_op_scripts_dir}/nat.py show_rules --direction source --family inet @@ -58,7 +58,7 @@ Show configured destination NAT rules - ${vyos_op_scripts_dir}/nat.py show_rules --direction destination + ${vyos_op_scripts_dir}/nat.py show_rules --direction destination --family inet diff --git a/op-mode-definitions/nat66.xml.in b/op-mode-definitions/nat66.xml.in index 1ec46eb11..aba2d6add 100644 --- a/op-mode-definitions/nat66.xml.in +++ b/op-mode-definitions/nat66.xml.in @@ -16,7 +16,7 @@ Show configured source NAT66 rules - ${vyos_op_scripts_dir}/show_nat66_rules.py --source + ${vyos_op_scripts_dir}/nat.py show_rules --direction source --family inet6 @@ -58,7 +58,7 @@ Show configured destination NAT66 rules - ${vyos_op_scripts_dir}/show_nat66_rules.py --destination + ${vyos_op_scripts_dir}/nat.py show_rules --direction destination --family inet6 diff --git a/src/op_mode/nat.py b/src/op_mode/nat.py index 12fc4c782..2dffc378b 100755 --- a/src/op_mode/nat.py +++ b/src/op_mode/nat.py @@ -27,7 +27,7 @@ from vyos.util import dict_search import vyos.opmode -def _get_json_data(direction): +def _get_json_data(direction, family): """ Get NAT format JSON """ @@ -35,14 +35,15 @@ def _get_json_data(direction): chain = 'POSTROUTING' if direction == 'destination': chain = 'PREROUTING' - return cmd(f'sudo nft --json list chain ip nat {chain}') + family = 'ip6' if family == 'inet6' else 'ip' + return cmd(f'sudo nft --json list chain {family} nat {chain}') -def _get_raw_data_rules(direction): +def _get_raw_data_rules(direction, family): """Get interested rules :returns dict """ - data = _get_json_data(direction) + data = _get_json_data(direction, family) data_dict = json.loads(data) rules = [] for rule in data_dict['nftables']: @@ -51,10 +52,12 @@ def _get_raw_data_rules(direction): return rules -def _get_formatted_output_rules(data, direction): +def _get_formatted_output_rules(data, direction, family): # Add default values before loop sport, dport, proto = 'any', 'any', 'any' - saddr, daddr = '0.0.0.0/0', '0.0.0.0/0' + saddr = '::/0' if family == 'inet6' else '0.0.0.0/0' + daddr = '::/0' if family == 'inet6' else '0.0.0.0/0' + data_entries = [] for rule in data: if 'comment' in rule['rule']: @@ -69,11 +72,13 @@ def _get_formatted_output_rules(data, direction): if 'prefix' in match['right'] or 'set' in match['right']: # Merge dict src/dst l3_l4 parameters my_dict = {**match['left']['payload'], **match['right']} + my_dict['op'] = match['op'] + op = '!' if my_dict.get('op') == '!=' else '' proto = my_dict.get('protocol').upper() if my_dict['field'] == 'saddr': - saddr = f'{my_dict["prefix"]["addr"]}/{my_dict["prefix"]["len"]}' + saddr = f'{op}{my_dict["prefix"]["addr"]}/{my_dict["prefix"]["len"]}' elif my_dict['field'] == 'daddr': - daddr = f'{my_dict["prefix"]["addr"]}/{my_dict["prefix"]["len"]}' + daddr = f'{op}{my_dict["prefix"]["addr"]}/{my_dict["prefix"]["len"]}' elif my_dict['field'] == 'sport': # Port range or single port if jmespath.search('set[*].range', my_dict): @@ -96,8 +101,8 @@ def _get_formatted_output_rules(data, direction): if jmespath.search('left.payload.field', match) == 'daddr': daddr = match.get('right') else: - saddr = '0.0.0.0/0' - daddr = '0.0.0.0/0' + saddr = '::/0' if family == 'inet6' else '0.0.0.0/0' + daddr = '::/0' if family == 'inet6' else '0.0.0.0/0' sport = 'any' dport = 'any' proto = 'any' @@ -175,12 +180,12 @@ def _get_formatted_output_statistics(data, direction): return output -def show_rules(raw: bool, direction: str): - nat_rules = _get_raw_data_rules(direction) +def show_rules(raw: bool, direction: str, family: str): + nat_rules = _get_raw_data_rules(direction, family) if raw: return nat_rules else: - return _get_formatted_output_rules(nat_rules, direction) + return _get_formatted_output_rules(nat_rules, direction, family) def show_statistics(raw: bool, direction: str): diff --git a/src/op_mode/show_nat66_rules.py b/src/op_mode/show_nat66_rules.py deleted file mode 100755 index 967ec9d37..000000000 --- a/src/op_mode/show_nat66_rules.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2021 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import jmespath -import json - -from argparse import ArgumentParser -from jinja2 import Template -from sys import exit -from vyos.util import cmd -from vyos.util import dict_search - -parser = ArgumentParser() -group = parser.add_mutually_exclusive_group() -group.add_argument("--source", help="Show statistics for configured source NAT rules", action="store_true") -group.add_argument("--destination", help="Show statistics for configured destination NAT rules", action="store_true") -args = parser.parse_args() - -if args.source or args.destination: - tmp = cmd('sudo nft -j list table ip6 nat') - tmp = json.loads(tmp) - - format_nat66_rule = '{0: <10} {1: <50} {2: <50} {3: <10}' - print(format_nat66_rule.format("Rule", "Source" if args.source else "Destination", "Translation", "Outbound Interface" if args.source else "Inbound Interface")) - print(format_nat66_rule.format("----", "------" if args.source else "-----------", "-----------", "------------------" if args.source else "-----------------")) - - data_json = jmespath.search('nftables[?rule].rule[?chain]', tmp) - for idx in range(0, len(data_json)): - data = data_json[idx] - - # The following key values must exist - # When the rule JSON does not have some keys, this is not a rule we can work with - continue_rule = False - for key in ['comment', 'chain', 'expr']: - if key not in data: - continue_rule = True - continue - if continue_rule: - continue - - comment = data['comment'] - - # Check the annotation to see if the annotation format is created by VYOS - continue_rule = True - for comment_prefix in ['SRC-NAT66-', 'DST-NAT66-']: - if comment_prefix in comment: - continue_rule = False - if continue_rule: - continue - - # When log is detected from the second index of expr, then this rule should be ignored - if 'log' in data['expr'][2]: - continue - - rule = comment.replace('SRC-NAT66-','') - rule = rule.replace('DST-NAT66-','') - chain = data['chain'] - if not ((args.source and chain == 'POSTROUTING') or (not args.source and chain == 'PREROUTING')): - continue - interface = dict_search('match.right', data['expr'][0]) - srcdest = dict_search('match.right.prefix.addr', data['expr'][2]) - if srcdest: - addr_tmp = dict_search('match.right.prefix.len', data['expr'][2]) - if addr_tmp: - srcdest = srcdest + '/' + str(addr_tmp) - else: - srcdest = dict_search('match.right', data['expr'][2]) - - tran_addr_json = dict_search('snat.addr' if args.source else 'dnat.addr', data['expr'][3]) - if tran_addr_json: - if isinstance(srcdest_json,str): - tran_addr = tran_addr_json - - if 'prefix' in tran_addr_json: - addr_tmp = dict_search('snat.addr.prefix.addr' if args.source else 'dnat.addr.prefix.addr', data['expr'][3]) - len_tmp = dict_search('snat.addr.prefix.len' if args.source else 'dnat.addr.prefix.len', data['expr'][3]) - if addr_tmp: - tran_addr = addr_tmp + '/' + str(len_tmp) - else: - if 'masquerade' in data['expr'][3]: - tran_addr = 'masquerade' - - print(format_nat66_rule.format(rule, srcdest, tran_addr, interface)) - - exit(0) -else: - parser.print_help() - exit(1) - -- cgit v1.2.3 From 079316a8bb33f48821d2b8f5822d66af9090a359 Mon Sep 17 00:00:00 2001 From: Nicolas Fort Date: Wed, 24 Aug 2022 11:51:19 +0000 Subject: Policy: T4641: allow only ipv4 prefixes on prefix-list --- interface-definitions/policy.xml.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in index cc1de609d..e794c4b90 100644 --- a/interface-definitions/policy.xml.in +++ b/interface-definitions/policy.xml.in @@ -392,7 +392,7 @@ Prefix to match against - + -- cgit v1.2.3 From f5360b98703e0a954066c099b48119daecd1c12b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 24 Aug 2022 19:30:53 +0200 Subject: ipsec: T2185: use systemd to start/stop service --- op-mode-definitions/monitor-log.xml.in | 13 +++++++++++++ op-mode-definitions/show-log.xml.in | 2 +- src/conf_mode/vpn_ipsec.py | 8 +++----- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in index 8a02e1f08..774acaa5c 100644 --- a/op-mode-definitions/monitor-log.xml.in +++ b/op-mode-definitions/monitor-log.xml.in @@ -224,6 +224,19 @@ journalctl --no-hostname --boot --follow --unit ssh.service + + + Show log for Virtual Private Network (VPN) + + + + + Monitor last lines of IPSec + + journalctl --no-hostname --boot --follow --unit strongswan-starter.service + + + diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in index 24a1b5f3e..455bd7c64 100644 --- a/op-mode-definitions/show-log.xml.in +++ b/op-mode-definitions/show-log.xml.in @@ -386,7 +386,7 @@ Show log for IPSec - cat $(printf "%s\n" /var/log/messages* | sort -nr) | grep -e charon + journalctl --no-hostname --boot --unit strongswan-starter.service diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index bad9cfbd8..5ca32d23e 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -595,13 +595,11 @@ def wait_for_vici_socket(timeout=5, sleep_interval=0.1): sleep(sleep_interval) def apply(ipsec): + systemd_service = 'strongswan-starter.service' if not ipsec: - call('sudo ipsec stop') + call(f'systemctl stop {systemd_service}') else: - call('sudo ipsec restart') - call('sudo ipsec rereadall') - call('sudo ipsec reload') - + call(f'systemctl reload-or-restart {systemd_service}') if wait_for_vici_socket(): call('sudo swanctl -q') -- cgit v1.2.3 From 254285bb5d701e950c1243dd2f4f592146ede2aa Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 24 Aug 2022 19:42:33 +0200 Subject: op-mode: extend "monitor log vpn" option support monitoring * all * l2tp * sstp * pptp --- op-mode-definitions/monitor-log.xml.in | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in index 774acaa5c..975d20465 100644 --- a/op-mode-definitions/monitor-log.xml.in +++ b/op-mode-definitions/monitor-log.xml.in @@ -229,12 +229,36 @@ Show log for Virtual Private Network (VPN) + + + Monitor last lines of ALL VPNs + + journalctl --no-hostname --boot --follow --unit strongswan-starter.service --unit accel-ppp@*.service + Monitor last lines of IPSec journalctl --no-hostname --boot --follow --unit strongswan-starter.service + + + Monitor last lines of L2TP + + journalctl --no-hostname --boot --follow --unit accel-ppp@l2tp.service + + + + Monitor last lines of PPTP + + journalctl --no-hostname --boot --follow --unit accel-ppp@pptp.service + + + + Monitor last lines of SSTP + + journalctl --no-hostname --boot --follow --unit accel-ppp@sstp.service + -- cgit v1.2.3 From b7feed29627c58e969b1d07ef08fc2811142194f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 24 Aug 2022 19:43:46 +0200 Subject: op-mode: T4390: migrate "show log vpn" to journalctl --- op-mode-definitions/show-log.xml.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in index 455bd7c64..ebd198215 100644 --- a/op-mode-definitions/show-log.xml.in +++ b/op-mode-definitions/show-log.xml.in @@ -380,7 +380,7 @@ Show log for ALL - cat $(printf "%s\n" /var/log/messages* | sort -nr) | grep -e charon -e accel -e pptpd -e ppp + journalctl --no-hostname --boot --unit strongswan-starter.service --unit accel-ppp@*.service @@ -392,7 +392,7 @@ Show log for L2TP - cat $(printf "%s\n" /var/log/messages* | sort -nr) | grep -e remote-access-aaa-win -e remote-access-zzz-mac -e accel-l2tp -e ppp + journalctl --no-hostname --boot --unit accel-ppp@l2tp.service -- cgit v1.2.3 From bfa13e367d0b77105ba350a34da8212859f07f59 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 24 Aug 2022 19:40:11 +0200 Subject: proxy: T4642: bugfix regex, add hyphen to allow list --- interface-definitions/system-proxy.xml.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface-definitions/system-proxy.xml.in b/interface-definitions/system-proxy.xml.in index 1c06b347f..4cb42901b 100644 --- a/interface-definitions/system-proxy.xml.in +++ b/interface-definitions/system-proxy.xml.in @@ -11,7 +11,7 @@ Proxy URL - http:\/\/[a-z0-9\.]+ + http:\/\/[a-z0-9-\.]+ -- cgit v1.2.3 From c4159ee846d4b8fbc5bb713553d9dd9c9652dce8 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 24 Aug 2022 19:41:40 +0200 Subject: smoketest: bgp: T4634: validate "disable-connected-check" option --- smoketest/scripts/cli/test_protocols_bgp.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index cefaad64a..6196ffe60 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -105,7 +105,8 @@ neighbor_config = { 'pfx_list_out' : prefix_list_out6, 'no_send_comm_ext' : '', 'peer_group' : 'foo-bar_baz', - 'graceful_rst_hlp' : '' + 'graceful_rst_hlp' : '', + 'disable_conn_chk' : '', }, } @@ -120,6 +121,7 @@ peer_group_config = { 'shutdown' : '', 'cap_over' : '', 'ttl_security' : '5', + 'disable_conn_chk' : '', }, 'bar' : { 'remote_as' : '111', @@ -251,6 +253,9 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' neighbor {peer} graceful-restart-disable', frrconfig) if 'graceful_rst_hlp' in peer_config: self.assertIn(f' neighbor {peer} graceful-restart-helper', frrconfig) + if 'disable_conn_chk' in peer_config: + self.assertIn(f' neighbor {peer} disable-connected-check', frrconfig) + def test_bgp_01_simple(self): router_id = '127.0.0.1' @@ -400,6 +405,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['neighbor', peer, 'graceful-restart', 'disable']) if 'graceful_rst_hlp' in peer_config: self.cli_set(base_path + ['neighbor', peer, 'graceful-restart', 'restart-helper']) + if 'disable_conn_chk' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'disable-connected-check']) # Conditional advertisement if 'advertise_map' in peer_config: @@ -488,6 +495,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['peer-group', peer_group, 'graceful-restart', 'disable']) if 'graceful_rst_hlp' in config: self.cli_set(base_path + ['peer-group', peer_group, 'graceful-restart', 'restart-helper']) + if 'disable_conn_chk' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'disable-connected-check']) # Conditional advertisement if 'advertise_map' in config: -- cgit v1.2.3 From eb4a7ee3afc0765671ce0fa379ab5e3518e9e49e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 24 Aug 2022 21:43:10 +0200 Subject: T4630: can not use same source-interface for macsec and pseudo-ethernet A macsec interface requires a dedicated source interface, it can not be shared with another macsec or a pseudo-ethernet interface. set interfaces macsec macsec10 address '192.168.2.1/30' set interfaces macsec macsec10 security cipher 'gcm-aes-256' set interfaces macsec macsec10 security encrypt set interfaces macsec macsec10 security mka cak '232e44b7fda6f8e2d88a07bf78a7aff4232e44b7fda6f8e2d88a07bf78a7aff4' set interfaces macsec macsec10 security mka ckn '09924585a6f3010208cf5222ef24c821405b0e34f4b4f63b1f0ced474b9bb6e6' set interfaces macsec macsec10 source-interface 'eth1' commit set interfaces pseudo-ethernet peth0 source-interface eth1 commit Reuslts in FileNotFoundError: [Errno 2] failed to run command: ip link add peth0 link eth1 type macvlan mode private returned: exit code: 2 noteworthy: cmd 'ip link add peth0 link eth1 type macvlan mode private' returned (out): returned (err): RTNETLINK answers: Device or resource busy [[interfaces pseudo-ethernet peth0]] failed Commit failed --- python/vyos/configdict.py | 11 +++++++++-- python/vyos/configverify.py | 6 ++++++ src/conf_mode/interfaces-macsec.py | 8 +------- src/conf_mode/interfaces-pseudo-ethernet.py | 7 ++++++- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 8f822a97d..912bc94f2 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -295,11 +295,18 @@ def is_source_interface(conf, interface, intftype=None): """ ret_val = None intftypes = ['macsec', 'pppoe', 'pseudo-ethernet', 'tunnel', 'vxlan'] - if intftype not in intftypes + [None]: + if not intftype: + intftype = intftypes + + if isinstance(intftype, str): + intftype = [intftype] + elif not isinstance(intftype, list): + raise ValueError(f'Interface type "{type(intftype)}" must be either str or list!') + + if not all(x in intftypes for x in intftype): raise ValueError(f'unknown interface type "{intftype}" or it can not ' 'have a source-interface') - intftype = intftypes if intftype == None else [intftype] for it in intftype: base = ['interfaces', it] for intf in conf.list_nodes(base): diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 2ab3cb408..447ec795c 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -295,6 +295,12 @@ def verify_source_interface(config): raise ConfigError(f'Invalid source-interface "{src_ifname}". Interface ' f'is already a member of bond "{bond_name}"!') + if 'is_source_interface' in config: + tmp = config['is_source_interface'] + src_ifname = config['source_interface'] + raise ConfigError(f'Can not use source-interface "{src_ifname}", it already ' \ + f'belongs to interface "{tmp}"!') + def verify_dhcpv6(config): """ Common helper function used by interface implementations to perform diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index 870049a88..649ea8d50 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -67,7 +67,7 @@ def get_config(config=None): macsec.update({'shutdown_required': {}}) if 'source_interface' in macsec: - tmp = is_source_interface(conf, macsec['source_interface'], 'macsec') + tmp = is_source_interface(conf, macsec['source_interface'], ['macsec', 'pseudo-ethernet']) if tmp and tmp != ifname: macsec.update({'is_source_interface' : tmp}) return macsec @@ -102,12 +102,6 @@ def verify(macsec): # gcm-aes-128 requires a 128bit long key - 64 characters (string) = 32byte = 256bit raise ConfigError('gcm-aes-128 requires a 256bit long key!') - if 'is_source_interface' in macsec: - tmp = macsec['is_source_interface'] - src_ifname = macsec['source_interface'] - raise ConfigError(f'Can not use source-interface "{src_ifname}", it already ' \ - f'belongs to interface "{tmp}"!') - if 'source_interface' in macsec: # MACsec adds a 40 byte overhead (32 byte MACsec + 8 bytes VLAN 802.1ad # and 802.1q) - we need to check the underlaying MTU if our configured diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index f26a50a0e..20f2b1975 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.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 @@ -19,6 +19,7 @@ from sys import exit from vyos.config import Config from vyos.configdict import get_interface_dict from vyos.configdict import is_node_changed +from vyos.configdict import is_source_interface from vyos.configverify import verify_vrf from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete @@ -51,6 +52,10 @@ def get_config(config=None): if 'source_interface' in peth: _, peth['parent'] = get_interface_dict(conf, ['interfaces', 'ethernet'], peth['source_interface']) + # test if source-interface is maybe already used by another interface + tmp = is_source_interface(conf, peth['source_interface'], ['macsec']) + if tmp and tmp != ifname: peth.update({'is_source_interface' : tmp}) + return peth def verify(peth): -- cgit v1.2.3 From fa91f567b7b5f009aaaed569b3f5e5db4b638d39 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Thu, 25 Aug 2022 07:05:16 +0000 Subject: smoketest: T4643: Change openconnect default port Change openconnect port as both ocserv and sstp bind by default the same port 443 --- smoketest/configs/pki-misc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/smoketest/configs/pki-misc b/smoketest/configs/pki-misc index c90226a2a..4db795565 100644 --- a/smoketest/configs/pki-misc +++ b/smoketest/configs/pki-misc @@ -59,6 +59,10 @@ vpn { } mode local } + listen-ports { + tcp 4443 + udp 4443 + } network-settings { client-ip-settings { subnet 192.168.160.0/24 -- cgit v1.2.3 From ac885f3e0912acebf0e3bc62582cc767dc9d5a6d Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Thu, 25 Aug 2022 10:18:08 +0000 Subject: sstp: T4644: Check SSTP bind port before commit By default SSTP bind port '443' and this port can be used by another service like 'service https' or 'vpn openconnect' Check if port bound to another service --- src/conf_mode/vpn_sstp.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py index 23e5162ba..2949ab290 100755 --- a/src/conf_mode/vpn_sstp.py +++ b/src/conf_mode/vpn_sstp.py @@ -26,7 +26,9 @@ from vyos.pki import wrap_certificate from vyos.pki import wrap_private_key from vyos.template import render from vyos.util import call +from vyos.util import check_port_availability from vyos.util import dict_search +from vyos.util import is_listen_port_bind_service from vyos.util import write_file from vyos import ConfigError from vyos import airbag @@ -62,6 +64,12 @@ def verify(sstp): if not sstp: return None + port = sstp.get('port') + proto = 'tcp' + if check_port_availability('0.0.0.0', int(port), proto) is not True and \ + not is_listen_port_bind_service(int(port), 'accel-pppd'): + raise ConfigError(f'"{proto}" port "{port}" is used by another service') + verify_accel_ppp_base_service(sstp) if 'client_ip_pool' not in sstp and 'client_ipv6_pool' not in sstp: -- cgit v1.2.3 From 53bc8022e3beb1f817aec0d263c4d3bb379dcaf7 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Thu, 25 Aug 2022 12:26:15 +0000 Subject: op-mode: T4645: Show nat source stat missing argument --family As we use in commit 8d4205a9 argument '--family' for the function '_get_raw_data_rules(direction, family)' we must use it and for 'nat.py show_statistics' as it get raw data from the same function --- op-mode-definitions/nat.xml.in | 2 +- src/op_mode/nat.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/op-mode-definitions/nat.xml.in b/op-mode-definitions/nat.xml.in index e89c3801f..dbc06b930 100644 --- a/op-mode-definitions/nat.xml.in +++ b/op-mode-definitions/nat.xml.in @@ -22,7 +22,7 @@ Show statistics for configured source NAT rules - ${vyos_op_scripts_dir}/nat.py show_statistics --direction source + ${vyos_op_scripts_dir}/nat.py show_statistics --direction source --family inet diff --git a/src/op_mode/nat.py b/src/op_mode/nat.py index 2dffc378b..dec04aa48 100755 --- a/src/op_mode/nat.py +++ b/src/op_mode/nat.py @@ -188,8 +188,8 @@ def show_rules(raw: bool, direction: str, family: str): return _get_formatted_output_rules(nat_rules, direction, family) -def show_statistics(raw: bool, direction: str): - nat_statistics = _get_raw_data_rules(direction) +def show_statistics(raw: bool, direction: str, family: str): + nat_statistics = _get_raw_data_rules(direction, family) if raw: return nat_statistics else: -- cgit v1.2.3 From 1b5b6d8b9d3e57ac2f2db3402b35b183972302e7 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 25 Aug 2022 18:51:15 +0200 Subject: telegraf: T4617: add VRF support --- data/templates/monitoring/override.conf.j2 | 7 -- data/templates/monitoring/syslog_telegraf.j2 | 5 - .../monitoring/systemd_vyos_telegraf_service.j2 | 16 --- data/templates/monitoring/telegraf.j2 | 122 --------------------- data/templates/telegraf/override.conf.j2 | 15 +++ data/templates/telegraf/syslog_telegraf.j2 | 5 + data/templates/telegraf/telegraf.j2 | 122 +++++++++++++++++++++ .../service-monitoring-telegraf.xml.in | 1 + .../cli/test_service_monitoring_telegraf.py | 2 +- src/conf_mode/service_monitoring_telegraf.py | 60 +++++----- src/systemd/telegraf.service | 15 +++ 11 files changed, 189 insertions(+), 181 deletions(-) delete mode 100644 data/templates/monitoring/override.conf.j2 delete mode 100644 data/templates/monitoring/syslog_telegraf.j2 delete mode 100644 data/templates/monitoring/systemd_vyos_telegraf_service.j2 delete mode 100644 data/templates/monitoring/telegraf.j2 create mode 100644 data/templates/telegraf/override.conf.j2 create mode 100644 data/templates/telegraf/syslog_telegraf.j2 create mode 100644 data/templates/telegraf/telegraf.j2 create mode 100644 src/systemd/telegraf.service diff --git a/data/templates/monitoring/override.conf.j2 b/data/templates/monitoring/override.conf.j2 deleted file mode 100644 index 9f1b4ebec..000000000 --- a/data/templates/monitoring/override.conf.j2 +++ /dev/null @@ -1,7 +0,0 @@ -[Unit] -After=vyos-router.service -ConditionPathExists=/run/telegraf/vyos-telegraf.conf -[Service] -Environment=INFLUX_TOKEN={{ influxdb.authentication.token }} -CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN CAP_SYS_ADMIN -AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN diff --git a/data/templates/monitoring/syslog_telegraf.j2 b/data/templates/monitoring/syslog_telegraf.j2 deleted file mode 100644 index cdcbd92a4..000000000 --- a/data/templates/monitoring/syslog_telegraf.j2 +++ /dev/null @@ -1,5 +0,0 @@ -# Generated by /usr/libexec/vyos/conf_mode/service_monitoring_telegraf.py - -$ModLoad omuxsock -$OMUxSockSocket /run/telegraf/telegraf_syslog.sock -*.notice :omuxsock: diff --git a/data/templates/monitoring/systemd_vyos_telegraf_service.j2 b/data/templates/monitoring/systemd_vyos_telegraf_service.j2 deleted file mode 100644 index 234ef5586..000000000 --- a/data/templates/monitoring/systemd_vyos_telegraf_service.j2 +++ /dev/null @@ -1,16 +0,0 @@ -[Unit] -Description=The plugin-driven server agent for reporting metrics into InfluxDB -Documentation=https://github.com/influxdata/telegraf -After=network.target - -[Service] -EnvironmentFile=-/etc/default/telegraf -User=telegraf -ExecStart=/usr/bin/telegraf -config /run/telegraf/vyos-telegraf.conf -config-directory /etc/telegraf/telegraf.d $TELEGRAF_OPTS -ExecReload=/bin/kill -HUP $MAINPID -Restart=on-failure -RestartForceExitStatus=SIGPIPE -KillMode=control-group - -[Install] -WantedBy=multi-user.target diff --git a/data/templates/monitoring/telegraf.j2 b/data/templates/monitoring/telegraf.j2 deleted file mode 100644 index 6b395692b..000000000 --- a/data/templates/monitoring/telegraf.j2 +++ /dev/null @@ -1,122 +0,0 @@ -# Generated by /usr/libexec/vyos/conf_mode/service_monitoring_telegraf.py - -[agent] - interval = "15s" - round_interval = true - metric_batch_size = 1000 - metric_buffer_limit = 10000 - collection_jitter = "5s" - flush_interval = "15s" - flush_jitter = "0s" - precision = "" - debug = false - quiet = false - logfile = "" - hostname = "" - omit_hostname = false -{% if azure_data_explorer is vyos_defined %} -### Azure Data Explorer ### -[[outputs.azure_data_explorer]] - ## The URI property of the Azure Data Explorer resource on Azure - endpoint_url = "{{ azure_data_explorer.url }}" - - ## The Azure Data Explorer database that the metrics will be ingested into. - ## The plugin will NOT generate this database automatically, it's expected that this database already exists before ingestion. - database = "{{ azure_data_explorer.database }}" - metrics_grouping_type = "{{ azure_data_explorer.group_metrics }}" - - ## Name of the single table to store all the metrics (Only needed if metrics_grouping_type is "SingleTable"). -{% if azure_data_explorer.table is vyos_defined and azure_data_explorer.group_metrics == 'SingleTable' %} - table_name = "{{ azure_data_explorer.table }}" -{% endif %} -### End Azure Data Explorer ### -{% endif %} -{% if influxdb is vyos_defined %} -### InfluxDB2 ### -[[outputs.influxdb_v2]] - urls = ["{{ influxdb.url }}:{{ influxdb.port }}"] - insecure_skip_verify = true - token = "$INFLUX_TOKEN" - organization = "{{ influxdb.authentication.organization }}" - bucket = "{{ influxdb.bucket }}" -### End InfluxDB2 ### -{% endif %} -{% if prometheus_client is vyos_defined %} -### Prometheus ### -[[outputs.prometheus_client]] - ## Address to listen on - listen = "{{ prometheus_client.listen_address if prometheus_client.listen_address is vyos_defined else '' }}:{{ prometheus_client.port }}" - metric_version = {{ prometheus_client.metric_version }} -{% if prometheus_client.authentication.username is vyos_defined and prometheus_client.authentication.password is vyos_defined %} - ## Use HTTP Basic Authentication - basic_username = "{{ prometheus_client.authentication.username }}" - basic_password = "{{ prometheus_client.authentication.password }}" -{% endif %} -{% if prometheus_client.allow_from is vyos_defined %} - ip_range = {{ prometheus_client.allow_from }} -{% endif %} -### End Prometheus ### -{% endif %} -{% if splunk is vyos_defined %} -### Splunk ### -[[outputs.http]] - ## URL is the address to send metrics to - url = "{{ splunk.url }}" - ## Timeout for HTTP message - # timeout = "5s" - ## Use TLS but skip chain & host verification -{% if splunk.authentication.insecure is vyos_defined %} - insecure_skip_verify = true -{% endif %} - ## Data format to output - data_format = "splunkmetric" - ## Provides time, index, source overrides for the HEC - splunkmetric_hec_routing = true - ## Additional HTTP headers - [outputs.http.headers] - # Should be set manually to "application/json" for json data_format - Content-Type = "application/json" - Authorization = "Splunk {{ splunk.authentication.token }}" - X-Splunk-Request-Channel = "{{ splunk.authentication.token }}" -### End Splunk ### -{% endif %} -[[inputs.cpu]] - percpu = true - totalcpu = true - collect_cpu_time = false - report_active = false -[[inputs.disk]] - ignore_fs = ["devtmpfs", "devfs"] -[[inputs.diskio]] -[[inputs.mem]] -[[inputs.net]] -[[inputs.system]] -[[inputs.netstat]] -[[inputs.processes]] -[[inputs.kernel]] -[[inputs.interrupts]] -[[inputs.linux_sysctl_fs]] -[[inputs.systemd_units]] -[[inputs.conntrack]] - files = ["ip_conntrack_count","ip_conntrack_max","nf_conntrack_count","nf_conntrack_max"] - dirs = ["/proc/sys/net/ipv4/netfilter","/proc/sys/net/netfilter"] -[[inputs.ethtool]] - interface_include = {{ interfaces_ethernet }} -[[inputs.ntpq]] - dns_lookup = true -[[inputs.internal]] -[[inputs.nstat]] -[[inputs.syslog]] - server = "unixgram:///run/telegraf/telegraf_syslog.sock" - best_effort = true - syslog_standard = "RFC3164" -{% if influxdb_configured is vyos_defined %} -[[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" - ] - timeout = "10s" - data_format = "influx" -{% endif %} diff --git a/data/templates/telegraf/override.conf.j2 b/data/templates/telegraf/override.conf.j2 new file mode 100644 index 000000000..d30bb19de --- /dev/null +++ b/data/templates/telegraf/override.conf.j2 @@ -0,0 +1,15 @@ +{% set vrf_command = 'ip vrf exec ' ~ vrf ~ ' ' if vrf is vyos_defined else '' %} +[Unit] +After= +After=vyos-router.service +ConditionPathExists=/run/telegraf/telegraf.conf + +[Service] +ExecStart= +ExecStart={{ vrf_command }}/usr/bin/telegraf --config /run/telegraf/telegraf.conf --config-directory /etc/telegraf/telegraf.d --pidfile /run/telegraf/telegraf.pid +PIDFile=/run/telegraf/telegraf.pid +EnvironmentFile= +Environment=INFLUX_TOKEN={{ influxdb.authentication.token }} +CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN CAP_SYS_ADMIN CAP_BPF CAP_DAC_OVERRIDE +AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN + diff --git a/data/templates/telegraf/syslog_telegraf.j2 b/data/templates/telegraf/syslog_telegraf.j2 new file mode 100644 index 000000000..cdcbd92a4 --- /dev/null +++ b/data/templates/telegraf/syslog_telegraf.j2 @@ -0,0 +1,5 @@ +# Generated by /usr/libexec/vyos/conf_mode/service_monitoring_telegraf.py + +$ModLoad omuxsock +$OMUxSockSocket /run/telegraf/telegraf_syslog.sock +*.notice :omuxsock: diff --git a/data/templates/telegraf/telegraf.j2 b/data/templates/telegraf/telegraf.j2 new file mode 100644 index 000000000..6b395692b --- /dev/null +++ b/data/templates/telegraf/telegraf.j2 @@ -0,0 +1,122 @@ +# Generated by /usr/libexec/vyos/conf_mode/service_monitoring_telegraf.py + +[agent] + interval = "15s" + round_interval = true + metric_batch_size = 1000 + metric_buffer_limit = 10000 + collection_jitter = "5s" + flush_interval = "15s" + flush_jitter = "0s" + precision = "" + debug = false + quiet = false + logfile = "" + hostname = "" + omit_hostname = false +{% if azure_data_explorer is vyos_defined %} +### Azure Data Explorer ### +[[outputs.azure_data_explorer]] + ## The URI property of the Azure Data Explorer resource on Azure + endpoint_url = "{{ azure_data_explorer.url }}" + + ## The Azure Data Explorer database that the metrics will be ingested into. + ## The plugin will NOT generate this database automatically, it's expected that this database already exists before ingestion. + database = "{{ azure_data_explorer.database }}" + metrics_grouping_type = "{{ azure_data_explorer.group_metrics }}" + + ## Name of the single table to store all the metrics (Only needed if metrics_grouping_type is "SingleTable"). +{% if azure_data_explorer.table is vyos_defined and azure_data_explorer.group_metrics == 'SingleTable' %} + table_name = "{{ azure_data_explorer.table }}" +{% endif %} +### End Azure Data Explorer ### +{% endif %} +{% if influxdb is vyos_defined %} +### InfluxDB2 ### +[[outputs.influxdb_v2]] + urls = ["{{ influxdb.url }}:{{ influxdb.port }}"] + insecure_skip_verify = true + token = "$INFLUX_TOKEN" + organization = "{{ influxdb.authentication.organization }}" + bucket = "{{ influxdb.bucket }}" +### End InfluxDB2 ### +{% endif %} +{% if prometheus_client is vyos_defined %} +### Prometheus ### +[[outputs.prometheus_client]] + ## Address to listen on + listen = "{{ prometheus_client.listen_address if prometheus_client.listen_address is vyos_defined else '' }}:{{ prometheus_client.port }}" + metric_version = {{ prometheus_client.metric_version }} +{% if prometheus_client.authentication.username is vyos_defined and prometheus_client.authentication.password is vyos_defined %} + ## Use HTTP Basic Authentication + basic_username = "{{ prometheus_client.authentication.username }}" + basic_password = "{{ prometheus_client.authentication.password }}" +{% endif %} +{% if prometheus_client.allow_from is vyos_defined %} + ip_range = {{ prometheus_client.allow_from }} +{% endif %} +### End Prometheus ### +{% endif %} +{% if splunk is vyos_defined %} +### Splunk ### +[[outputs.http]] + ## URL is the address to send metrics to + url = "{{ splunk.url }}" + ## Timeout for HTTP message + # timeout = "5s" + ## Use TLS but skip chain & host verification +{% if splunk.authentication.insecure is vyos_defined %} + insecure_skip_verify = true +{% endif %} + ## Data format to output + data_format = "splunkmetric" + ## Provides time, index, source overrides for the HEC + splunkmetric_hec_routing = true + ## Additional HTTP headers + [outputs.http.headers] + # Should be set manually to "application/json" for json data_format + Content-Type = "application/json" + Authorization = "Splunk {{ splunk.authentication.token }}" + X-Splunk-Request-Channel = "{{ splunk.authentication.token }}" +### End Splunk ### +{% endif %} +[[inputs.cpu]] + percpu = true + totalcpu = true + collect_cpu_time = false + report_active = false +[[inputs.disk]] + ignore_fs = ["devtmpfs", "devfs"] +[[inputs.diskio]] +[[inputs.mem]] +[[inputs.net]] +[[inputs.system]] +[[inputs.netstat]] +[[inputs.processes]] +[[inputs.kernel]] +[[inputs.interrupts]] +[[inputs.linux_sysctl_fs]] +[[inputs.systemd_units]] +[[inputs.conntrack]] + files = ["ip_conntrack_count","ip_conntrack_max","nf_conntrack_count","nf_conntrack_max"] + dirs = ["/proc/sys/net/ipv4/netfilter","/proc/sys/net/netfilter"] +[[inputs.ethtool]] + interface_include = {{ interfaces_ethernet }} +[[inputs.ntpq]] + dns_lookup = true +[[inputs.internal]] +[[inputs.nstat]] +[[inputs.syslog]] + server = "unixgram:///run/telegraf/telegraf_syslog.sock" + best_effort = true + syslog_standard = "RFC3164" +{% if influxdb_configured is vyos_defined %} +[[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" + ] + timeout = "10s" + data_format = "influx" +{% endif %} diff --git a/interface-definitions/service-monitoring-telegraf.xml.in b/interface-definitions/service-monitoring-telegraf.xml.in index 36f40a539..dc014ee16 100644 --- a/interface-definitions/service-monitoring-telegraf.xml.in +++ b/interface-definitions/service-monitoring-telegraf.xml.in @@ -306,6 +306,7 @@ + #include diff --git a/smoketest/scripts/cli/test_service_monitoring_telegraf.py b/smoketest/scripts/cli/test_service_monitoring_telegraf.py index 1c8cc9759..c1c4044e6 100755 --- a/smoketest/scripts/cli/test_service_monitoring_telegraf.py +++ b/smoketest/scripts/cli/test_service_monitoring_telegraf.py @@ -24,7 +24,7 @@ from vyos.util import process_named_running from vyos.util import read_file PROCESS_NAME = 'telegraf' -TELEGRAF_CONF = '/run/telegraf/vyos-telegraf.conf' +TELEGRAF_CONF = '/run/telegraf/telegraf.conf' base_path = ['service', 'monitoring', 'telegraf'] org = 'log@in.local' token = 'GuRJc12tIzfjnYdKRAIYbxdWd2aTpOT9PVYNddzDnFV4HkAcD7u7-kndTFXjGuXzJN6TTxmrvPODB4mnFcseDV==' diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py index 62f5e1ddf..18b32edab 100755 --- a/src/conf_mode/service_monitoring_telegraf.py +++ b/src/conf_mode/service_monitoring_telegraf.py @@ -22,6 +22,8 @@ from shutil import rmtree from vyos.config import Config from vyos.configdict import dict_merge +from vyos.configdict import is_node_changed +from vyos.configverify import verify_vrf from vyos.ifconfig import Section from vyos.template import render from vyos.util import call @@ -32,20 +34,14 @@ from vyos import ConfigError from vyos import airbag airbag.enable() - -base_dir = '/run/telegraf' cache_dir = f'/etc/telegraf/.cache' -config_telegraf = f'{base_dir}/vyos-telegraf.conf' +config_telegraf = f'/run/telegraf/telegraf.conf' custom_scripts_dir = '/etc/telegraf/custom_scripts' syslog_telegraf = '/etc/rsyslog.d/50-telegraf.conf' -systemd_telegraf_service = '/etc/systemd/system/vyos-telegraf.service' -systemd_telegraf_override_dir = '/etc/systemd/system/vyos-telegraf.service.d' -systemd_override = f'{systemd_telegraf_override_dir}/10-override.conf' - +systemd_override = '/etc/systemd/system/telegraf.service.d/10-override.conf' def get_interfaces(type='', vlan=True): """ - Get interfaces get_interfaces() ['dum0', 'eth0', 'eth1', 'eth1.5', 'lo', 'tun0'] @@ -62,9 +58,7 @@ def get_interfaces(type='', vlan=True): return interfaces def get_nft_filter_chains(): - """ - Get nft chains for table filter - """ + """ Get nft chains for table filter """ nft = cmd('nft --json list table ip filter') nft = json.loads(nft) chain_list = [] @@ -78,7 +72,6 @@ def get_nft_filter_chains(): def get_config(config=None): - if config: conf = config else: @@ -87,8 +80,12 @@ def get_config(config=None): if not conf.exists(base): return None - monitoring = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, - no_tag_node_value_mangle=True) + monitoring = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + tmp = is_node_changed(conf, base + ['vrf']) + if tmp: monitoring.update({'restart_required': {}}) # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. @@ -131,6 +128,8 @@ def verify(monitoring): if not monitoring: return None + verify_vrf(monitoring) + # Verify influxdb if 'influxdb' in monitoring: if 'authentication' not in monitoring['influxdb'] or \ @@ -173,7 +172,7 @@ def verify(monitoring): def generate(monitoring): if not monitoring: # Delete config and systemd files - config_files = [config_telegraf, systemd_telegraf_service, systemd_override, syslog_telegraf] + config_files = [config_telegraf, systemd_override, syslog_telegraf] for file in config_files: if os.path.isfile(file): os.unlink(file) @@ -190,33 +189,34 @@ def generate(monitoring): chown(cache_dir, 'telegraf', 'telegraf') - # Create systemd override dir - if not os.path.exists(systemd_telegraf_override_dir): - os.mkdir(systemd_telegraf_override_dir) - # Create custome scripts dir if not os.path.exists(custom_scripts_dir): os.mkdir(custom_scripts_dir) # Render telegraf configuration and systemd override - render(config_telegraf, 'monitoring/telegraf.j2', monitoring) - render(systemd_telegraf_service, 'monitoring/systemd_vyos_telegraf_service.j2', monitoring) - render(systemd_override, 'monitoring/override.conf.j2', monitoring, permission=0o640) - render(syslog_telegraf, 'monitoring/syslog_telegraf.j2', monitoring) - - chown(base_dir, 'telegraf', 'telegraf') + render(config_telegraf, 'telegraf/telegraf.j2', monitoring, user='telegraf', group='telegraf') + render(systemd_override, 'telegraf/override.conf.j2', monitoring) + render(syslog_telegraf, 'telegraf/syslog_telegraf.j2', monitoring) return None def apply(monitoring): # Reload systemd manager configuration + systemd_service = 'telegraf.service' call('systemctl daemon-reload') - if monitoring: - call('systemctl restart vyos-telegraf.service') - else: - call('systemctl stop vyos-telegraf.service') + if not monitoring: + call(f'systemctl stop {systemd_service}') + return + + # we need to restart the service if e.g. the VRF name changed + systemd_action = 'reload-or-restart' + if 'restart_required' in monitoring: + systemd_action = 'restart' + + call(f'systemctl {systemd_action} {systemd_service}') + # Telegraf include custom rsyslog config changes - call('systemctl restart rsyslog') + call('systemctl reload-or-restart rsyslog') if __name__ == '__main__': try: diff --git a/src/systemd/telegraf.service b/src/systemd/telegraf.service new file mode 100644 index 000000000..553942ac6 --- /dev/null +++ b/src/systemd/telegraf.service @@ -0,0 +1,15 @@ +[Unit] +Description=The plugin-driven server agent for reporting metrics into InfluxDB +Documentation=https://github.com/influxdata/telegraf +After=network.target + +[Service] +EnvironmentFile=-/etc/default/telegraf +ExecStart=/usr/bin/telegraf --config /run/telegraf/vyos-telegraf.conf --config-directory /etc/telegraf/telegraf.d +ExecReload=/bin/kill -HUP $MAINPID +Restart=on-failure +RestartForceExitStatus=SIGPIPE +KillMode=control-group + +[Install] +WantedBy=multi-user.target -- cgit v1.2.3 From 85e0d4ecbf694973dc20e48b1284529b8be01c19 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 25 Aug 2022 18:52:14 +0200 Subject: telegraf: T3872: re-use existing XML building blocks --- .../service-monitoring-telegraf.xml.in | 38 ++-------------------- 1 file changed, 3 insertions(+), 35 deletions(-) diff --git a/interface-definitions/service-monitoring-telegraf.xml.in b/interface-definitions/service-monitoring-telegraf.xml.in index dc014ee16..68215dba4 100644 --- a/interface-definitions/service-monitoring-telegraf.xml.in +++ b/interface-definitions/service-monitoring-telegraf.xml.in @@ -10,7 +10,7 @@ - Telegraf monitoring + Telegraf metric collector @@ -228,27 +228,7 @@ - - - Local IP addresses to listen on - - - - - ipv4 - IPv4 address to listen for incoming connections - - - ipv6 - IPv6 address to listen for incoming connections - - - - - - - - + #include Metric version control mapping from Telegraf to Prometheus format @@ -291,19 +271,7 @@ - - - Remote URL - - url - Remote URL to Splunk collector - - - ^(http(s?):\/\/.*):(\d*)\/?(.*) - - Incorrect URL format - - + #include #include -- cgit v1.2.3 From 7d83077102b56d984fe2ea73ab3cd45f60a27c41 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 25 Aug 2022 18:55:36 +0200 Subject: ntp: T2185: use reload-or-restart on configuration changes --- src/conf_mode/ntp.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/conf_mode/ntp.py b/src/conf_mode/ntp.py index 5490a794d..0ecb4d736 100755 --- a/src/conf_mode/ntp.py +++ b/src/conf_mode/ntp.py @@ -17,6 +17,7 @@ import os from vyos.config import Config +from vyos.configdict import is_node_changed from vyos.configverify import verify_vrf from vyos.configverify import verify_interface_exists from vyos.util import call @@ -40,6 +41,10 @@ def get_config(config=None): ntp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) ntp['config_file'] = config_file + + tmp = is_node_changed(conf, base + ['vrf']) + if tmp: ntp.update({'restart_required': {}}) + return ntp def verify(ntp): @@ -78,19 +83,25 @@ def generate(ntp): return None def apply(ntp): + systemd_service = 'ntp.service' + # Reload systemd manager configuration + call('systemctl daemon-reload') + if not ntp: # NTP support is removed in the commit - call('systemctl stop ntp.service') + call(f'systemctl stop {systemd_service}') if os.path.exists(config_file): os.unlink(config_file) if os.path.isfile(systemd_override): os.unlink(systemd_override) + return - # Reload systemd manager configuration - call('systemctl daemon-reload') - if ntp: - call('systemctl restart ntp.service') + # we need to restart the service if e.g. the VRF name changed + systemd_action = 'reload-or-restart' + if 'restart_required' in ntp: + systemd_action = 'restart' + call(f'systemctl {systemd_action} {systemd_service}') return None if __name__ == '__main__': -- cgit v1.2.3 From 02e3dbbe53ac15309eb3b809c78ce9f64da1205f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 25 Aug 2022 18:55:44 +0200 Subject: ssh: T2185: use reload-or-restart on configuration changes --- src/conf_mode/ssh.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py index 28669694b..2bbd7142a 100755 --- a/src/conf_mode/ssh.py +++ b/src/conf_mode/ssh.py @@ -22,6 +22,7 @@ from syslog import LOG_INFO from vyos.config import Config from vyos.configdict import dict_merge +from vyos.configdict import is_node_changed from vyos.configverify import verify_vrf from vyos.util import call from vyos.template import render @@ -50,6 +51,10 @@ def get_config(config=None): return None ssh = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + tmp = is_node_changed(conf, base + ['vrf']) + if tmp: ssh.update({'restart_required': {}}) + # 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) @@ -104,17 +109,25 @@ def generate(ssh): return None def apply(ssh): + systemd_service_ssh = 'ssh.service' + systemd_service_sshguard = 'sshguard.service' if not ssh: # SSH access is removed in the commit - call('systemctl stop ssh.service') - call('systemctl stop sshguard.service') + call(f'systemctl stop {systemd_service_ssh}') + call(f'systemctl stop {systemd_service_sshguard}') return None + if 'dynamic_protection' not in ssh: - call('systemctl stop sshguard.service') + call(f'systemctl stop {systemd_service_sshguard}') else: - call('systemctl restart sshguard.service') + call(f'systemctl reload-or-restart {systemd_service_sshguard}') + + # we need to restart the service if e.g. the VRF name changed + systemd_action = 'reload-or-restart' + if 'restart_required' in ssh: + systemd_action = 'restart' - call('systemctl restart ssh.service') + call(f'systemctl {systemd_action} {systemd_service_ssh}') return None if __name__ == '__main__': -- cgit v1.2.3 From cfde4b4986347d4b050b38c7dc50db9179894a81 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 25 Aug 2022 18:56:25 +0200 Subject: ifconfig: T2223: add vlan switch for Section.interfaces() Sometimes we are only interested in the parent interfaces without any VLAN subinterfaces. Extend the API with a vlan argument that defaults to True to keep the current behavior in place. --- python/vyos/ifconfig/section.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/python/vyos/ifconfig/section.py b/python/vyos/ifconfig/section.py index 91f667b65..5e98cd510 100644 --- a/python/vyos/ifconfig/section.py +++ b/python/vyos/ifconfig/section.py @@ -88,7 +88,7 @@ class Section: raise ValueError(f'No type found for interface name: {name}') @classmethod - def _intf_under_section (cls,section=''): + def _intf_under_section (cls,section='',vlan=True): """ return a generator with the name of the configured interface which are under a section @@ -103,6 +103,9 @@ class Section: if section and ifsection != section: continue + if vlan == False and '.' in ifname: + continue + yield ifname @classmethod @@ -135,13 +138,14 @@ class Section: return l @classmethod - def interfaces(cls, section=''): + def interfaces(cls, section='', vlan=True): """ return a list of the name of the configured interface which are under a section - if no section is provided, then it returns all configured interfaces + if no section is provided, then it returns all configured interfaces. + If vlan is True, also Vlan subinterfaces will be returned """ - return cls._sort_interfaces(cls._intf_under_section(section)) + return cls._sort_interfaces(cls._intf_under_section(section, vlan)) @classmethod def _intf_with_feature(cls, feature=''): -- cgit v1.2.3 From 73be77ec42d06a369974bfb1255839164f73c276 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 25 Aug 2022 18:59:00 +0200 Subject: proxy: T4642: allow https proxy transports --- interface-definitions/system-proxy.xml.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface-definitions/system-proxy.xml.in b/interface-definitions/system-proxy.xml.in index 4cb42901b..8fb6bfae5 100644 --- a/interface-definitions/system-proxy.xml.in +++ b/interface-definitions/system-proxy.xml.in @@ -11,7 +11,7 @@ Proxy URL - http:\/\/[a-z0-9-\.]+ + http(s)?:\/\/[a-z0-9-\.]+ -- cgit v1.2.3 From b4646149b4993578707833df96e972f76a298ef7 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Thu, 25 Aug 2022 13:36:43 -0500 Subject: graphql: T4640: add schema defs and resolver support for op-mode errors --- src/services/api/graphql/bindings.py | 3 +- src/services/api/graphql/graphql/errors.py | 8 ++++ src/services/api/graphql/graphql/mutations.py | 13 ++++++- src/services/api/graphql/graphql/queries.py | 13 ++++++- .../api/graphql/session/errors/op_mode_errors.py | 13 +++++++ src/services/api/graphql/session/session.py | 13 ++++--- .../api/graphql/utils/schema_from_op_mode.py | 43 +++++++++++++++++++++- 7 files changed, 96 insertions(+), 10 deletions(-) create mode 100644 src/services/api/graphql/graphql/errors.py create mode 100644 src/services/api/graphql/session/errors/op_mode_errors.py diff --git a/src/services/api/graphql/bindings.py b/src/services/api/graphql/bindings.py index 049d59de7..0b1260912 100644 --- a/src/services/api/graphql/bindings.py +++ b/src/services/api/graphql/bindings.py @@ -17,6 +17,7 @@ import vyos.defaults from . graphql.queries import query from . graphql.mutations import mutation from . graphql.directives import directives_dict +from . graphql.errors import op_mode_error from . utils.schema_from_op_mode import generate_op_mode_definitions from ariadne import make_executable_schema, load_schema_from_path, snake_case_fallback_resolvers @@ -27,6 +28,6 @@ def generate_schema(): type_defs = load_schema_from_path(api_schema_dir) - schema = make_executable_schema(type_defs, query, mutation, snake_case_fallback_resolvers, directives=directives_dict) + schema = make_executable_schema(type_defs, query, op_mode_error, mutation, snake_case_fallback_resolvers, directives=directives_dict) return schema diff --git a/src/services/api/graphql/graphql/errors.py b/src/services/api/graphql/graphql/errors.py new file mode 100644 index 000000000..1066300e0 --- /dev/null +++ b/src/services/api/graphql/graphql/errors.py @@ -0,0 +1,8 @@ + +from ariadne import InterfaceType + +op_mode_error = InterfaceType("OpModeError") + +@op_mode_error.type_resolver +def resolve_op_mode_error(obj, *_): + return obj['name'] diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py index c8ae0f516..1b77cff87 100644 --- a/src/services/api/graphql/graphql/mutations.py +++ b/src/services/api/graphql/graphql/mutations.py @@ -22,6 +22,8 @@ from makefun import with_signature from .. import state from .. import key_auth from api.graphql.session.session import Session +from api.graphql.session.errors.op_mode_errors import op_mode_err_msg, op_mode_err_code +from vyos.opmode import Error as OpModeError mutation = ObjectType("Mutation") @@ -86,10 +88,19 @@ def make_mutation_resolver(mutation_name, class_name, session_func): "success": True, "data": data } + except OpModeError as e: + typename = type(e).__name__ + return { + "success": False, + "errore": ['op_mode_error'], + "op_mode_error": {"name": f"{typename}", + "message": op_mode_err_msg.get(typename, "Unknown"), + "vyos_code": op_mode_err_code.get(typename, 9999)} + } except Exception as error: return { "success": False, - "errors": [str(error)] + "errors": [repr(error)] } return func_impl diff --git a/src/services/api/graphql/graphql/queries.py b/src/services/api/graphql/graphql/queries.py index 921a66274..8ae61b704 100644 --- a/src/services/api/graphql/graphql/queries.py +++ b/src/services/api/graphql/graphql/queries.py @@ -22,6 +22,8 @@ from makefun import with_signature from .. import state from .. import key_auth from api.graphql.session.session import Session +from api.graphql.session.errors.op_mode_errors import op_mode_err_msg, op_mode_err_code +from vyos.opmode import Error as OpModeError query = ObjectType("Query") @@ -86,10 +88,19 @@ def make_query_resolver(query_name, class_name, session_func): "success": True, "data": data } + except OpModeError as e: + typename = type(e).__name__ + return { + "success": False, + "errors": ['op_mode_error'], + "op_mode_error": {"name": f"{typename}", + "message": op_mode_err_msg.get(typename, "Unknown"), + "vyos_code": op_mode_err_code.get(typename, 9999)} + } except Exception as error: return { "success": False, - "errors": [str(error)] + "errors": [repr(error)] } return func_impl diff --git a/src/services/api/graphql/session/errors/op_mode_errors.py b/src/services/api/graphql/session/errors/op_mode_errors.py new file mode 100644 index 000000000..7ba75455d --- /dev/null +++ b/src/services/api/graphql/session/errors/op_mode_errors.py @@ -0,0 +1,13 @@ + + +op_mode_err_msg = { + "UnconfiguredSubsystem": "subsystem is not configured or not running", + "DataUnavailable": "data currently unavailable", + "PermissionDenied": "client does not have permission" +} + +op_mode_err_code = { + "UnconfiguredSubsystem": 2000, + "DataUnavailable": 2001, + "PermissionDenied": 1003 +} diff --git a/src/services/api/graphql/session/session.py b/src/services/api/graphql/session/session.py index 23bc7154c..93e1c328e 100644 --- a/src/services/api/graphql/session/session.py +++ b/src/services/api/graphql/session/session.py @@ -22,6 +22,7 @@ from vyos.config import Config from vyos.configtree import ConfigTree from vyos.defaults import directories from vyos.template import render +from vyos.opmode import Error as OpModeError from api.graphql.utils.util import load_op_mode_as_module, split_compound_op_mode_name @@ -177,10 +178,10 @@ class Session: mod = load_op_mode_as_module(f'{scriptname}') func = getattr(mod, func_name) - if len(list(data)) > 0: + try: res = func(True, **data) - else: - res = func(True) + except OpModeError as e: + raise e return res @@ -199,9 +200,9 @@ class Session: mod = load_op_mode_as_module(f'{scriptname}') func = getattr(mod, func_name) - if len(list(data)) > 0: + try: res = func(**data) - else: - res = func() + except OpModeError as e: + raise e return res diff --git a/src/services/api/graphql/utils/schema_from_op_mode.py b/src/services/api/graphql/utils/schema_from_op_mode.py index f990aae52..379d15250 100755 --- a/src/services/api/graphql/utils/schema_from_op_mode.py +++ b/src/services/api/graphql/utils/schema_from_op_mode.py @@ -21,7 +21,7 @@ import os import json import typing -from inspect import signature, getmembers, isfunction +from inspect import signature, getmembers, isfunction, isclass, getmro from jinja2 import Template from vyos.defaults import directories @@ -35,6 +35,7 @@ SCHEMA_PATH = directories['api_schema'] DATA_DIR = directories['data'] op_mode_include_file = os.path.join(DATA_DIR, 'op-mode-standardized.json') +op_mode_error_schema = 'op_mode_error.graphql' schema_data: dict = {'schema_name': '', 'schema_fields': []} @@ -53,6 +54,7 @@ type {{ schema_name }} { type {{ schema_name }}Result { data: {{ schema_name }} + op_mode_error: OpModeError success: Boolean! errors: [String] } @@ -76,6 +78,7 @@ type {{ schema_name }} { type {{ schema_name }}Result { data: {{ schema_name }} + op_mode_error: OpModeError success: Boolean! errors: [String] } @@ -85,6 +88,21 @@ extend type Mutation { } """ +error_template = """ +interface OpModeError { + name: String! + message: String! + vyos_code: Int! +} +{% for name in error_names %} +type {{ name }} implements OpModeError { + name: String! + message: String! + vyos_code: Int! +} +{%- endfor %} +""" + def _snake_to_pascal_case(name: str) -> str: res = ''.join(map(str.title, name.split('_'))) return res @@ -136,7 +154,30 @@ def create_schema(func_name: str, base_name: str, func: callable) -> str: return res +def create_error_schema(): + from vyos import opmode + + e = Exception + err_types = getmembers(opmode, isclass) + err_types = [k for k in err_types if issubclass(k[1], e)] + # drop base class, to be replaced by interface type. Find the class + # programmatically, in case the base class name changes. + for i in range(len(err_types)): + if err_types[i][1] in getmro(err_types[i-1][1]): + del err_types[i] + break + err_names = [k[0] for k in err_types] + error_data = {'error_names': err_names} + j2_template = Template(error_template) + res = j2_template.render(error_data) + + return res + def generate_op_mode_definitions(): + out = create_error_schema() + with open(f'{SCHEMA_PATH}/{op_mode_error_schema}', 'w') as f: + f.write(out) + with open(op_mode_include_file) as f: op_mode_files = json.load(f) -- cgit v1.2.3 From c2fc87c02dd556dd1569ff2fd81c9e2485a80459 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Fri, 26 Aug 2022 10:53:35 +0000 Subject: smoketest: T4643: Delete vpn sstp from config as we have HTTP HTTP and sstp cannot work together and in the test config 1.4-rolling-202106290839 we didnot have configurable port for such services So we shoud delete sstp from this smoketest config test In fact it is never working at all 'smoketest/configs/pki-misc' It commits without errors before but in the real life we get 3 services (https openconnect sstp) that bound the same port --- smoketest/configs/pki-misc | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/smoketest/configs/pki-misc b/smoketest/configs/pki-misc index 4db795565..a84723757 100644 --- a/smoketest/configs/pki-misc +++ b/smoketest/configs/pki-misc @@ -74,26 +74,6 @@ vpn { key-file /config/auth/ovpn_test_server.key } } - sstp { - authentication { - local-users { - username test { - password test - } - } - mode local - protocols mschap-v2 - } - client-ip-pool { - subnet 192.168.170.0/24 - } - gateway-address 192.168.150.1 - ssl { - ca-cert-file /config/auth/ovpn_test_ca.pem - cert-file /config/auth/ovpn_test_server.pem - key-file /config/auth/ovpn_test_server.key - } - } } -- cgit v1.2.3 From 829c67c4da173136071b6ed7deb36a99ea6eb030 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Fri, 26 Aug 2022 11:46:00 +0000 Subject: smoketest: T4631: Extend smoketes fot nat66 protocol --- smoketest/scripts/cli/test_nat66.py | 48 +++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/smoketest/scripts/cli/test_nat66.py b/smoketest/scripts/cli/test_nat66.py index 4b5625569..c5db066db 100755 --- a/smoketest/scripts/cli/test_nat66.py +++ b/smoketest/scripts/cli/test_nat66.py @@ -131,6 +131,30 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase): self.verify_nftables(nftables_search, 'ip6 nat') + def test_destination_nat66_protocol(self): + translation_address = '2001:db8:1111::1' + source_prefix = '2001:db8:2222::/64' + dport = '4545' + sport = '8080' + tport = '5555' + proto = 'tcp' + self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'eth1']) + self.cli_set(dst_path + ['rule', '1', 'destination', 'port', dport]) + self.cli_set(dst_path + ['rule', '1', 'source', 'address', source_prefix]) + self.cli_set(dst_path + ['rule', '1', 'source', 'port', sport]) + self.cli_set(dst_path + ['rule', '1', 'protocol', proto]) + self.cli_set(dst_path + ['rule', '1', 'translation', 'address', translation_address]) + self.cli_set(dst_path + ['rule', '1', 'translation', 'port', tport]) + + # check validate() - outbound-interface must be defined + self.cli_commit() + + nftables_search = [ + ['iifname "eth1"', 'tcp dport { 4545 } ip6 saddr 2001:db8:2222::/64 tcp sport { 8080 } dnat to 2001:db8:1111::1:5555'] + ] + + self.verify_nftables(nftables_search, 'ip6 nat') + def test_destination_nat66_prefix(self): destination_prefix = 'fc00::/64' translation_prefix = 'fc01::/64' @@ -176,6 +200,30 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase): self.cli_set(src_path + ['rule', rule, 'translation', 'address', 'masquerade']) self.cli_commit() + def test_source_nat66_protocol(self): + translation_address = '2001:db8:1111::1' + source_prefix = '2001:db8:2222::/64' + dport = '9999' + sport = '8080' + tport = '80' + proto = 'tcp' + self.cli_set(src_path + ['rule', '1', 'outbound-interface', 'eth1']) + self.cli_set(src_path + ['rule', '1', 'destination', 'port', dport]) + self.cli_set(src_path + ['rule', '1', 'source', 'prefix', source_prefix]) + self.cli_set(src_path + ['rule', '1', 'source', 'port', sport]) + self.cli_set(src_path + ['rule', '1', 'protocol', proto]) + self.cli_set(src_path + ['rule', '1', 'translation', 'address', translation_address]) + self.cli_set(src_path + ['rule', '1', 'translation', 'port', tport]) + + # check validate() - outbound-interface must be defined + self.cli_commit() + + nftables_search = [ + ['oifname "eth1"', 'ip6 saddr 2001:db8:2222::/64 tcp dport { 9999 } tcp sport { 8080 } snat to 2001:db8:1111::1:80'] + ] + + self.verify_nftables(nftables_search, 'ip6 nat') + def test_nat66_no_rules(self): # T3206: deleting all rules but keep the direction 'destination' or # 'source' resulteds in KeyError: 'rule'. -- cgit v1.2.3 From 141bf8d437b6c0c76fd0fc21659d10d4477c92a0 Mon Sep 17 00:00:00 2001 From: aapostoliuk Date: Thu, 25 Aug 2022 18:59:10 +0300 Subject: opennhrp: T1070: Fixed creating IPSEC tunnel to Hub Fixed creating IPSEC tunnel to Hub. Added continues of execution generator functions. --- src/etc/opennhrp/opennhrp-script.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/etc/opennhrp/opennhrp-script.py b/src/etc/opennhrp/opennhrp-script.py index a5293c97e..bf25a7331 100755 --- a/src/etc/opennhrp/opennhrp-script.py +++ b/src/etc/opennhrp/opennhrp-script.py @@ -81,7 +81,13 @@ def vici_ike_terminate(list_ikeid: list[str]) -> bool: session = vici.Session() for ikeid in list_ikeid: logger.info(f'Terminating IKE SA with id {ikeid}') - session.terminate({'ike-id': ikeid, 'timeout': '-1'}) + session_generator = session.terminate( + {'ike-id': ikeid, 'timeout': '-1'}) + # a dummy `for` loop is required because of requirements + # from vici. Without a full iteration on the output, the + # command to vici may not be executed completely + for _ in session_generator: + pass return True except Exception as err: logger.error(f'Failed to terminate SA for IKE ids {list_ikeid}: {err}') @@ -175,13 +181,18 @@ def vici_initiate(conn: str, child_sa: str, src_addr: str, f'src_addr: {src_addr}, dst_addr: {dest_addr}') try: session = vici.Session() - session.initiate({ + session_generator = session.initiate({ 'ike': conn, 'child': child_sa, 'timeout': '-1', 'my-host': src_addr, 'other-host': dest_addr }) + # a dummy `for` loop is required because of requirements + # from vici. Without a full iteration on the output, the + # command to vici may not be executed completely + for _ in session_generator: + pass return True except Exception as err: logger.error(f'Unable to initiate connection {err}') -- cgit v1.2.3 From 5d7a5d433a97c2a51b9cad79b99938f58a24f788 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Fri, 26 Aug 2022 15:52:04 +0000 Subject: nat: nat66: T4650: Rewrite op-mode nat translation Rewrite op-moe "show nat|nat66 translation" to vyos.opmode format Ability to get machine-readable format "raw" --- op-mode-definitions/nat.xml.in | 4 +- op-mode-definitions/nat66.xml.in | 4 +- src/op_mode/nat.py | 101 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 4 deletions(-) diff --git a/op-mode-definitions/nat.xml.in b/op-mode-definitions/nat.xml.in index dbc06b930..ce0544390 100644 --- a/op-mode-definitions/nat.xml.in +++ b/op-mode-definitions/nat.xml.in @@ -45,7 +45,7 @@ ${vyos_op_scripts_dir}/show_nat_translations.py --type=source --verbose - ${vyos_op_scripts_dir}/show_nat_translations.py --type=source + ${vyos_op_scripts_dir}/nat.py show_translations --direction source --family inet @@ -87,7 +87,7 @@ ${vyos_op_scripts_dir}/show_nat_translations.py --type=destination --verbose - ${vyos_op_scripts_dir}/show_nat_translations.py --type=destination + ${vyos_op_scripts_dir}/nat.py show_translations --direction destination --family inet diff --git a/op-mode-definitions/nat66.xml.in b/op-mode-definitions/nat66.xml.in index aba2d6add..25aa04d59 100644 --- a/op-mode-definitions/nat66.xml.in +++ b/op-mode-definitions/nat66.xml.in @@ -45,7 +45,7 @@ ${vyos_op_scripts_dir}/show_nat66_translations.py --type=source --verbose - ${vyos_op_scripts_dir}/show_nat66_translations.py --type=source + ${vyos_op_scripts_dir}/nat.py show_translations --direction source --family inet6 @@ -87,7 +87,7 @@ ${vyos_op_scripts_dir}/show_nat66_translations.py --type=destination --verbose - ${vyos_op_scripts_dir}/show_nat66_translations.py --type=destination + ${vyos_op_scripts_dir}/nat.py show_translations --direction destination --family inet6 diff --git a/src/op_mode/nat.py b/src/op_mode/nat.py index dec04aa48..1339d5b92 100755 --- a/src/op_mode/nat.py +++ b/src/op_mode/nat.py @@ -17,6 +17,7 @@ import jmespath import json import sys +import xmltodict from sys import exit from tabulate import tabulate @@ -27,6 +28,29 @@ from vyos.util import dict_search import vyos.opmode +def _get_xml_translation(direction, family): + """ + Get conntrack XML output --src-nat|--dst-nat + """ + if direction == 'source': + opt = '--src-nat' + if direction == 'destination': + opt = '--dst-nat' + return cmd(f'sudo conntrack --dump --family {family} {opt} --output xml') + + +def _xml_to_dict(xml): + """ + Convert XML to dictionary + Return: dictionary + """ + parse = xmltodict.parse(xml, attr_prefix='') + # If only one conntrack entry we must change dict + if 'meta' in parse['conntrack']['flow']: + return dict(conntrack={'flow': [parse['conntrack']['flow']]}) + return parse + + def _get_json_data(direction, family): """ Get NAT format JSON @@ -52,6 +76,22 @@ def _get_raw_data_rules(direction, family): return rules +def _get_raw_translation(direction, family): + """ + Return: dictionary + """ + xml = _get_xml_translation(direction, family) + if len(xml) == 0: + output = {'conntrack': + { + 'error': True, + 'reason': 'entries not found' + } + } + return output + return _xml_to_dict(xml) + + def _get_formatted_output_rules(data, direction, family): # Add default values before loop sport, dport, proto = 'any', 'any', 'any' @@ -180,6 +220,58 @@ def _get_formatted_output_statistics(data, direction): return output +def _get_formatted_translation(dict_data, nat_direction, family): + data_entries = [] + if 'error' in dict_data['conntrack']: + return 'Entries not found' + for entry in dict_data['conntrack']['flow']: + orig_src, orig_dst, orig_sport, orig_dport = {}, {}, {}, {} + reply_src, reply_dst, reply_sport, reply_dport = {}, {}, {}, {} + proto = {} + for meta in entry['meta']: + direction = meta['direction'] + if direction in ['original']: + if 'layer3' in meta: + orig_src = meta['layer3']['src'] + orig_dst = meta['layer3']['dst'] + if 'layer4' in meta: + if meta.get('layer4').get('sport'): + orig_sport = meta['layer4']['sport'] + if meta.get('layer4').get('dport'): + orig_dport = meta['layer4']['dport'] + proto = meta['layer4']['protoname'] + if direction in ['reply']: + if 'layer3' in meta: + reply_src = meta['layer3']['src'] + reply_dst = meta['layer3']['dst'] + if 'layer4' in meta: + if meta.get('layer4').get('sport'): + reply_sport = meta['layer4']['sport'] + if meta.get('layer4').get('dport'): + reply_dport = meta['layer4']['dport'] + proto = meta['layer4']['protoname'] + if direction == 'independent': + conn_id = meta['id'] + timeout = meta['timeout'] + orig_src = f'{orig_src}:{orig_sport}' if orig_sport else orig_src + orig_dst = f'{orig_dst}:{orig_dport}' if orig_dport else orig_dst + reply_src = f'{reply_src}:{reply_sport}' if reply_sport else reply_src + reply_dst = f'{reply_dst}:{reply_dport}' if reply_dport else reply_dst + state = meta['state'] if 'state' in meta else '' + mark = meta['mark'] + zone = meta['zone'] if 'zone' in meta else '' + if nat_direction == 'source': + data_entries.append( + [orig_src, reply_dst, proto, timeout, mark, zone]) + elif nat_direction == 'destination': + data_entries.append( + [orig_dst, reply_src, proto, timeout, mark, zone]) + + headers = ["Pre-NAT", "Post-NAT", "Proto", "Timeout", "Mark", "Zone"] + output = tabulate(data_entries, headers, numalign="left") + return output + + def show_rules(raw: bool, direction: str, family: str): nat_rules = _get_raw_data_rules(direction, family) if raw: @@ -196,6 +288,15 @@ def show_statistics(raw: bool, direction: str, family: str): return _get_formatted_output_statistics(nat_statistics, direction) +def show_translations(raw: bool, direction: str, family: str): + family = 'ipv6' if family == 'inet6' else 'ipv4' + nat_translation = _get_raw_translation(direction, family) + if raw: + return nat_translation + else: + return _get_formatted_translation(nat_translation, direction, family) + + if __name__ == '__main__': try: res = vyos.opmode.run(sys.modules[__name__]) -- cgit v1.2.3 From 8a3bc80ca7a3209b045f3e3defc47efe4dc8d1e0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 25 Aug 2022 19:01:21 +0200 Subject: telegraf: T3872: replace local get_interfaces() function with Section.interface() Commit cfde4b49 ("ifconfig: T2223: add vlan switch for Section.interfaces()") added the functionality of the local get_interfaces() function to the base class so all other parts in the system can query for interface names of a given type including or excluding their vlan sub-interfaces. --- src/conf_mode/service_monitoring_telegraf.py | 20 +------------------- .../custom_scripts/show_interfaces_input_filter.py | 16 +--------------- 2 files changed, 2 insertions(+), 34 deletions(-) diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py index 18b32edab..53df006a4 100755 --- a/src/conf_mode/service_monitoring_telegraf.py +++ b/src/conf_mode/service_monitoring_telegraf.py @@ -40,23 +40,6 @@ custom_scripts_dir = '/etc/telegraf/custom_scripts' syslog_telegraf = '/etc/rsyslog.d/50-telegraf.conf' systemd_override = '/etc/systemd/system/telegraf.service.d/10-override.conf' -def get_interfaces(type='', vlan=True): - """ - get_interfaces() - ['dum0', 'eth0', 'eth1', 'eth1.5', 'lo', 'tun0'] - - get_interfaces("dummy") - ['dum0'] - """ - interfaces = [] - ifaces = Section.interfaces(type) - for iface in ifaces: - if vlan == False and '.' in iface: - continue - interfaces.append(iface) - - return interfaces - def get_nft_filter_chains(): """ Get nft chains for table filter """ nft = cmd('nft --json list table ip filter') @@ -70,7 +53,6 @@ def get_nft_filter_chains(): return chain_list - def get_config(config=None): if config: conf = config @@ -93,7 +75,7 @@ def get_config(config=None): monitoring = dict_merge(default_values, monitoring) monitoring['custom_scripts_dir'] = custom_scripts_dir - monitoring['interfaces_ethernet'] = get_interfaces('ethernet', vlan=False) + monitoring['interfaces_ethernet'] = Section.interfaces('ethernet', vlan=False) monitoring['nft_chains'] = get_nft_filter_chains() # Redefine azure group-metrics 'single-table' and 'table-per-metric' diff --git a/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py b/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py index 0c7474156..6f14d6a8e 100755 --- a/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py +++ b/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py @@ -5,20 +5,6 @@ from vyos.ifconfig import Interface import time -def get_interfaces(type='', vlan=True): - """ - Get interfaces: - ['dum0', 'eth0', 'eth1', 'eth1.5', 'lo', 'tun0'] - """ - interfaces = [] - ifaces = Section.interfaces(type) - for iface in ifaces: - if vlan == False and '.' in iface: - continue - interfaces.append(iface) - - return interfaces - def get_interface_addresses(iface, link_local_v6=False): """ Get IP and IPv6 addresses from interface in one string @@ -77,7 +63,7 @@ def get_interface_oper_state(iface): return oper_state -interfaces = get_interfaces() +interfaces = Section.interfaces('') for iface in interfaces: print(f'show_interfaces,interface={iface} ' -- cgit v1.2.3 From b15e550fe7d7c7f0040b10b72b24d4ba55a0a4c4 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 27 Aug 2022 08:04:20 +0200 Subject: Revert "smoketest: T4643: Delete vpn sstp from config as we have HTTP" This reverts commit c2fc87c02dd556dd1569ff2fd81c9e2485a80459. --- smoketest/configs/pki-misc | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/smoketest/configs/pki-misc b/smoketest/configs/pki-misc index a84723757..4db795565 100644 --- a/smoketest/configs/pki-misc +++ b/smoketest/configs/pki-misc @@ -74,6 +74,26 @@ vpn { key-file /config/auth/ovpn_test_server.key } } + sstp { + authentication { + local-users { + username test { + password test + } + } + mode local + protocols mschap-v2 + } + client-ip-pool { + subnet 192.168.170.0/24 + } + gateway-address 192.168.150.1 + ssl { + ca-cert-file /config/auth/ovpn_test_ca.pem + cert-file /config/auth/ovpn_test_server.pem + key-file /config/auth/ovpn_test_server.key + } + } } -- cgit v1.2.3 From 8458e9b0efeb4b60b15b86c0599e4b890fa3fcc8 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 27 Aug 2022 08:04:24 +0200 Subject: Revert "smoketest: T4643: Change openconnect default port" This reverts commit fa91f567b7b5f009aaaed569b3f5e5db4b638d39. --- smoketest/configs/pki-misc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/smoketest/configs/pki-misc b/smoketest/configs/pki-misc index 4db795565..c90226a2a 100644 --- a/smoketest/configs/pki-misc +++ b/smoketest/configs/pki-misc @@ -59,10 +59,6 @@ vpn { } mode local } - listen-ports { - tcp 4443 - udp 4443 - } network-settings { client-ip-settings { subnet 192.168.160.0/24 -- cgit v1.2.3 From 94a1728ae31948e9c52ad516493087f0ec1a088b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 27 Aug 2022 08:07:41 +0200 Subject: smoketest: T4643: create individual configs fot https service and sstp vpn --- smoketest/configs/pki-misc | 98 ---------------------------------- smoketest/configs/service-https | 55 +++++++++++++++++++ smoketest/configs/vpn-openconnect-sstp | 89 ++++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+), 98 deletions(-) delete mode 100644 smoketest/configs/pki-misc create mode 100644 smoketest/configs/service-https create mode 100644 smoketest/configs/vpn-openconnect-sstp diff --git a/smoketest/configs/pki-misc b/smoketest/configs/pki-misc deleted file mode 100644 index c90226a2a..000000000 --- a/smoketest/configs/pki-misc +++ /dev/null @@ -1,98 +0,0 @@ -interfaces { - ethernet eth0 { - address 192.168.150.1/24 - } -} -service { - https { - certificates { - system-generated-certificate { - lifetime 365 - } - } - } -} -system { - config-management { - commit-revisions 100 - } - console { - device ttyS0 { - speed 115200 - } - } - host-name vyos - login { - user vyos { - authentication { - encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/ - plaintext-password "" - } - } - } - ntp { - server time1.vyos.net { - } - server time2.vyos.net { - } - server time3.vyos.net { - } - } - syslog { - global { - facility all { - level info - } - facility protocols { - level debug - } - } - } -} -vpn { - openconnect { - authentication { - local-users { - username test { - password test - } - } - mode local - } - network-settings { - client-ip-settings { - subnet 192.168.160.0/24 - } - } - ssl { - ca-cert-file /config/auth/ovpn_test_ca.pem - cert-file /config/auth/ovpn_test_server.pem - key-file /config/auth/ovpn_test_server.key - } - } - sstp { - authentication { - local-users { - username test { - password test - } - } - mode local - protocols mschap-v2 - } - client-ip-pool { - subnet 192.168.170.0/24 - } - gateway-address 192.168.150.1 - ssl { - ca-cert-file /config/auth/ovpn_test_ca.pem - cert-file /config/auth/ovpn_test_server.pem - key-file /config/auth/ovpn_test_server.key - } - } -} - - -// Warning: Do not remove the following line. -// vyos-config-version: "bgp@1:broadcast-relay@1:cluster@1:config-management@1:conntrack@2:conntrack-sync@2:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@6:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:nat66@1:ntp@1:policy@1:pppoe-server@5:pptp@2:qos@1:quagga@9:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrf@2:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" -// Release version: 1.4-rolling-202106290839 diff --git a/smoketest/configs/service-https b/smoketest/configs/service-https new file mode 100644 index 000000000..d478d5731 --- /dev/null +++ b/smoketest/configs/service-https @@ -0,0 +1,55 @@ +interfaces { + ethernet eth0 { + address 192.168.150.1/24 + } +} +service { + https { + certificates { + system-generated-certificate { + lifetime 365 + } + } + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/ + plaintext-password "" + } + } + } + ntp { + server time1.vyos.net { + } + server time2.vyos.net { + } + server time3.vyos.net { + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } +} + +// Warning: Do not remove the following line. +// vyos-config-version: "bgp@1:broadcast-relay@1:cluster@1:config-management@1:conntrack@2:conntrack-sync@2:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@6:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:nat66@1:ntp@1:policy@1:pppoe-server@5:pptp@2:qos@1:quagga@9:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrf@2:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.4-rolling-202106290839 diff --git a/smoketest/configs/vpn-openconnect-sstp b/smoketest/configs/vpn-openconnect-sstp new file mode 100644 index 000000000..45e6dd9b2 --- /dev/null +++ b/smoketest/configs/vpn-openconnect-sstp @@ -0,0 +1,89 @@ +interfaces { + ethernet eth0 { + address 192.168.150.1/24 + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/ + plaintext-password "" + } + } + } + ntp { + server time1.vyos.net { + } + server time2.vyos.net { + } + server time3.vyos.net { + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } +} +vpn { + openconnect { + authentication { + local-users { + username test { + password test + } + } + mode local + } + network-settings { + client-ip-settings { + subnet 192.168.160.0/24 + } + } + ssl { + ca-cert-file /config/auth/ovpn_test_ca.pem + cert-file /config/auth/ovpn_test_server.pem + key-file /config/auth/ovpn_test_server.key + } + } + sstp { + authentication { + local-users { + username test { + password test + } + } + mode local + protocols mschap-v2 + } + client-ip-pool { + subnet 192.168.170.0/24 + } + gateway-address 192.168.150.1 + ssl { + ca-cert-file /config/auth/ovpn_test_ca.pem + cert-file /config/auth/ovpn_test_server.pem + key-file /config/auth/ovpn_test_server.key + } + } +} + + +// Warning: Do not remove the following line. +// vyos-config-version: "bgp@1:broadcast-relay@1:cluster@1:config-management@1:conntrack@2:conntrack-sync@2:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@6:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:nat66@1:ntp@1:policy@1:pppoe-server@5:pptp@2:qos@1:quagga@9:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrf@2:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.4-rolling-202106290839 -- cgit v1.2.3 From 9126170f0b09285cf79f8c40584312bccd67c3e8 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 27 Aug 2022 08:49:04 +0200 Subject: pppoe: T4648: do not install IPv6 default route from RA is no-default-route is set Adds a sysctl parameter to ignore the default router obtained from router advertisements when pppoe no-default-route is set. --- python/vyos/ifconfig/pppoe.py | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/python/vyos/ifconfig/pppoe.py b/python/vyos/ifconfig/pppoe.py index 63ffc8069..437fe0cae 100644 --- a/python/vyos/ifconfig/pppoe.py +++ b/python/vyos/ifconfig/pppoe.py @@ -1,4 +1,4 @@ -# Copyright 2020-2021 VyOS maintainers and contributors +# Copyright 2020-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 @@ -14,6 +14,7 @@ # License along with this library. If not, see . from vyos.ifconfig.interface import Interface +from vyos.validate import assert_range from vyos.util import get_interface_config @Interface.register @@ -27,6 +28,21 @@ class PPPoEIf(Interface): }, } + _sysfs_get = { + **Interface._sysfs_get,**{ + 'accept_ra_defrtr': { + 'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_ra_defrtr', + } + } + } + + _sysfs_set = {**Interface._sysfs_set, **{ + 'accept_ra_defrtr': { + 'validate': lambda value: assert_range(value, 0, 2), + 'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_ra_defrtr', + }, + }} + def _remove_routes(self, vrf=None): # Always delete default routes when interface is removed vrf_cmd = '' @@ -70,6 +86,21 @@ class PPPoEIf(Interface): """ Get a synthetic MAC address. """ return self.get_mac_synthetic() + def set_accept_ra_defrtr(self, enable): + """ + Learn default router in Router Advertisement. + 1: enabled + 0: disable + + Example: + >>> from vyos.ifconfig import PPPoEIf + >>> PPPoEIf('pppoe1').set_accept_ra_defrtr(0) + """ + tmp = self.get_interface('accept_ra_defrtr') + if tmp == enable: + return None + self.set_interface('accept_ra_defrtr', enable) + def update(self, config): """ General helper function which works on a dictionary retrived by get_config_dict(). It's main intention is to consolidate the scattered @@ -107,6 +138,10 @@ class PPPoEIf(Interface): tmp = config['vrf'] vrf = f'-c "vrf {tmp}"' + # learn default router in Router Advertisement. + tmp = '0' if 'no_default_route' in config else '1' + self.set_accept_ra_defrtr(tmp) + if 'no_default_route' not in config: # Set default route(s) pointing to PPPoE interface distance = config['default_route_distance'] -- cgit v1.2.3 From d42991a4c4ec9c6920ae1a8990f7a63cde9bdbea Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 28 Aug 2022 08:39:39 +0200 Subject: smoketest: T4643: bind sstp service to port 8443 --- smoketest/configs/vpn-openconnect-sstp | 1 + 1 file changed, 1 insertion(+) diff --git a/smoketest/configs/vpn-openconnect-sstp b/smoketest/configs/vpn-openconnect-sstp index 45e6dd9b2..59a26f501 100644 --- a/smoketest/configs/vpn-openconnect-sstp +++ b/smoketest/configs/vpn-openconnect-sstp @@ -75,6 +75,7 @@ vpn { subnet 192.168.170.0/24 } gateway-address 192.168.150.1 + port 8443 ssl { ca-cert-file /config/auth/ovpn_test_ca.pem cert-file /config/auth/ovpn_test_server.pem -- cgit v1.2.3 From f3420a967ad5597c57093b5279a844dca4c516c0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 28 Aug 2022 08:40:19 +0200 Subject: smoketest: T4652: upgrade PowerDNS recursor to 4.7 series --- smoketest/scripts/cli/test_service_dns_forwarding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smoketest/scripts/cli/test_service_dns_forwarding.py b/smoketest/scripts/cli/test_service_dns_forwarding.py index 65b676451..fe2682d50 100755 --- a/smoketest/scripts/cli/test_service_dns_forwarding.py +++ b/smoketest/scripts/cli/test_service_dns_forwarding.py @@ -26,7 +26,7 @@ from vyos.util import process_named_running CONFIG_FILE = '/run/powerdns/recursor.conf' FORWARD_FILE = '/run/powerdns/recursor.forward-zones.conf' HOSTSD_FILE = '/run/powerdns/recursor.vyos-hostsd.conf.lua' -PROCESS_NAME= 'pdns-r/worker' +PROCESS_NAME= 'pdns_recursor' base_path = ['service', 'dns', 'forwarding'] -- cgit v1.2.3 From cc5a19dcbff071117437608d9ae93c734514010a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 29 Aug 2022 06:35:59 +0200 Subject: smoketest: config: drop almost empty https service test --- smoketest/configs/service-https | 55 ----------------------------------------- 1 file changed, 55 deletions(-) delete mode 100644 smoketest/configs/service-https diff --git a/smoketest/configs/service-https b/smoketest/configs/service-https deleted file mode 100644 index d478d5731..000000000 --- a/smoketest/configs/service-https +++ /dev/null @@ -1,55 +0,0 @@ -interfaces { - ethernet eth0 { - address 192.168.150.1/24 - } -} -service { - https { - certificates { - system-generated-certificate { - lifetime 365 - } - } - } -} -system { - config-management { - commit-revisions 100 - } - console { - device ttyS0 { - speed 115200 - } - } - host-name vyos - login { - user vyos { - authentication { - encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/ - plaintext-password "" - } - } - } - ntp { - server time1.vyos.net { - } - server time2.vyos.net { - } - server time3.vyos.net { - } - } - syslog { - global { - facility all { - level info - } - facility protocols { - level debug - } - } - } -} - -// Warning: Do not remove the following line. -// vyos-config-version: "bgp@1:broadcast-relay@1:cluster@1:config-management@1:conntrack@2:conntrack-sync@2:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@6:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:nat66@1:ntp@1:policy@1:pppoe-server@5:pptp@2:qos@1:quagga@9:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrf@2:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" -// Release version: 1.4-rolling-202106290839 -- cgit v1.2.3 From b806fd93cea8589177396c9d428eb8f3490f8a52 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Mon, 29 Aug 2022 07:34:57 +0000 Subject: rpki: T4654: Fix RPKI cache description Fix wrong descriptions for the RPKI server It was mentioned about the NTP server --- interface-definitions/protocols-rpki.xml.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface-definitions/protocols-rpki.xml.in b/interface-definitions/protocols-rpki.xml.in index 68762ff9a..4535d3990 100644 --- a/interface-definitions/protocols-rpki.xml.in +++ b/interface-definitions/protocols-rpki.xml.in @@ -12,15 +12,15 @@ RPKI cache server address ipv4 - IP address of NTP server + IP address of RPKI server ipv6 - IPv6 address of NTP server + IPv6 address of RPKI server hostname - Fully qualified domain name of NTP server + Fully qualified domain name of RPKI server -- cgit v1.2.3 From aa9633b4358c571e58710dba5330f72f7f893304 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Mon, 29 Aug 2022 11:36:16 +0000 Subject: nat: T4367: Move nat rules from /tmp to /run/nftables_nat.conf Move nftables nat configuration from /tmp to /run As we have for other services like firewall, conntrack Don't remove the config file '/run/nftables_nat.conf' after commit --- src/conf_mode/nat.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 85819a77e..a72e82a83 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -44,7 +44,7 @@ if LooseVersion(kernel_version()) > LooseVersion('5.1'): else: k_mod = ['nft_nat', 'nft_chain_nat_ipv4'] -nftables_nat_config = '/tmp/vyos-nat-rules.nft' +nftables_nat_config = '/run/nftables_nat.conf' def get_handler(json, chain, target): """ Get nftable rule handler number of given chain/target combination. @@ -186,16 +186,12 @@ def generate(nat): # dry-run newly generated configuration tmp = run(f'nft -c -f {nftables_nat_config}') if tmp > 0: - if os.path.exists(nftables_nat_config): - os.unlink(nftables_nat_config) raise ConfigError('Configuration file errors encountered!') return None def apply(nat): cmd(f'nft -f {nftables_nat_config}') - if os.path.isfile(nftables_nat_config): - os.unlink(nftables_nat_config) return None -- cgit v1.2.3 From 0cc7e0a49094be809cccff9fb44288d883e6ef05 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Mon, 29 Aug 2022 14:55:32 +0000 Subject: firewall: T4655: Fix default action 'drop' for the firewall For some reason after firewall rewriting we are having default action 'accept' for 1.4 and default action 'drop' for 1.3 Fix this issue, set default action 'drop' --- python/vyos/template.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/vyos/template.py b/python/vyos/template.py index eb7f06480..62303bd55 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -1,4 +1,4 @@ -# Copyright 2019-2020 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 @@ -550,7 +550,7 @@ def nft_rule(rule_conf, fw_name, rule_id, ip_name='ip'): @register_filter('nft_default_rule') def nft_default_rule(fw_conf, fw_name): output = ['counter'] - default_action = fw_conf.get('default_action', 'accept') + default_action = fw_conf.get('default_action', 'drop') if 'enable_default_log' in fw_conf: action_suffix = default_action[:1].upper() -- cgit v1.2.3 From b01f27b3bb3f4cbc6096011856d83009d0440313 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 29 Aug 2022 20:36:20 +0200 Subject: ethernet: T4653: bugfix copy-paste when processing NIC offloading Commit 31169fa8a763e ("vyos.ifconfig: T3619: only set offloading options if supported by NIC") added the new implementation which handles NIC offloading. Unfortunately every single implementation was copied from "gro" which resulted in a change to gro for each offloading option - thus options like lro, sg, tso had no effect at all. It all comes down to copy/paste errors ... one way or another. --- python/vyos/ifconfig/ethernet.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 1280fc238..b8deb3311 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -236,7 +236,7 @@ class EthernetIf(Interface): enabled, fixed = self.ethtool.get_large_receive_offload() if enabled != state: if not fixed: - return self.set_interface('gro', 'on' if state else 'off') + return self.set_interface('lro', 'on' if state else 'off') else: print('Adapter does not support changing large-receive-offload settings!') return False @@ -273,7 +273,7 @@ class EthernetIf(Interface): enabled, fixed = self.ethtool.get_scatter_gather() if enabled != state: if not fixed: - return self.set_interface('gro', 'on' if state else 'off') + return self.set_interface('sg', 'on' if state else 'off') else: print('Adapter does not support changing scatter-gather settings!') return False @@ -293,7 +293,7 @@ class EthernetIf(Interface): enabled, fixed = self.ethtool.get_tcp_segmentation_offload() if enabled != state: if not fixed: - return self.set_interface('gro', 'on' if state else 'off') + return self.set_interface('tso', 'on' if state else 'off') else: print('Adapter does not support changing tcp-segmentation-offload settings!') return False @@ -359,5 +359,5 @@ class EthernetIf(Interface): for rx_tx, size in config['ring_buffer'].items(): self.set_ring_buffer(rx_tx, size) - # call base class first + # call base class last super().update(config) -- cgit v1.2.3 From ad1236e8d72ff29e0e2215df175b6f032fba75eb Mon Sep 17 00:00:00 2001 From: zsdc Date: Tue, 30 Aug 2022 12:36:01 +0300 Subject: console: T4646: Fixed USB console issues * fixed the `systemctl restart` command that used a value from config instead converted to `ttyUSBX` * moved systemd units from `/etc/` to `/run/` --- src/conf_mode/system_console.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/conf_mode/system_console.py b/src/conf_mode/system_console.py index 86985d765..e922edc4e 100755 --- a/src/conf_mode/system_console.py +++ b/src/conf_mode/system_console.py @@ -16,6 +16,7 @@ import os import re +from pathlib import Path from vyos.config import Config from vyos.configdict import dict_merge @@ -68,18 +69,15 @@ def verify(console): # amount of connected devices. We will resolve the fixed device name # to its dynamic device file - and create a new dict entry for it. by_bus_device = f'{by_bus_dir}/{device}' - if os.path.isdir(by_bus_dir) and os.path.exists(by_bus_device): - device = os.path.basename(os.readlink(by_bus_device)) - - # If the device name still starts with usbXXX no matching tty was found - # and it can not be used as a serial interface - if device.startswith('usb'): - raise ConfigError(f'Device {device} does not support beeing used as tty') + # If the device name still starts with usbXXX no matching tty was found + # and it can not be used as a serial interface + if not os.path.isdir(by_bus_dir) or not os.path.exists(by_bus_device): + raise ConfigError(f'Device {device} does not support beeing used as tty') return None def generate(console): - base_dir = '/etc/systemd/system' + base_dir = '/run/systemd/system' # Remove all serial-getty configuration files in advance for root, dirs, files in os.walk(base_dir): for basename in files: @@ -90,7 +88,8 @@ def generate(console): if not console or 'device' not in console: return None - for device, device_config in console['device'].items(): + # replace keys in the config for ttyUSB items to use them in `apply()` later + for device in console['device'].copy(): if device.startswith('usb'): # It is much easiert to work with the native ttyUSBn name when using # getty, but that name may change across reboots - depending on the @@ -98,9 +97,17 @@ def generate(console): # to its dynamic device file - and create a new dict entry for it. by_bus_device = f'{by_bus_dir}/{device}' if os.path.isdir(by_bus_dir) and os.path.exists(by_bus_device): - device = os.path.basename(os.readlink(by_bus_device)) + device_updated = os.path.basename(os.readlink(by_bus_device)) + + # replace keys in the config to use them in `apply()` later + console['device'][device_updated] = console['device'][device] + del console['device'][device] + else: + raise ConfigError(f'Device {device} does not support beeing used as tty') + for device, device_config in console['device'].items(): config_file = base_dir + f'/serial-getty@{device}.service' + Path(f'{base_dir}/getty.target.wants').mkdir(exist_ok=True) getty_wants_symlink = base_dir + f'/getty.target.wants/serial-getty@{device}.service' render(config_file, 'getty/serial-getty.service.j2', device_config) -- cgit v1.2.3 From 8792e13f80f4150fbe2dae5d8eedf7f40703411e Mon Sep 17 00:00:00 2001 From: zsdc Date: Tue, 30 Aug 2022 15:10:36 +0300 Subject: opmode: T4657: fixed opmode with return type hints This commit excludes `return` from `typing.get_type_hints()` output, which allows generate argparse arguments for function properly. --- python/vyos/opmode.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py index 628f7b3a2..7e3545c87 100644 --- a/python/vyos/opmode.py +++ b/python/vyos/opmode.py @@ -105,6 +105,8 @@ def run(module): subparser = subparsers.add_parser(function_name, help=functions[function_name].__doc__) type_hints = typing.get_type_hints(functions[function_name]) + if 'return' in type_hints: + del type_hints['return'] for opt in type_hints: th = type_hints[opt] -- cgit v1.2.3 From 74994f9b10588fce2cbd1acc9ec09fdbaf5ae8ad Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 30 Aug 2022 17:18:17 +0200 Subject: firewall: T3568: rename XML building blocks to match CLI node name --- interface-definitions/firewall.xml.in | 8 +++---- .../include/firewall/default-action.xml.i | 25 ++++++++++++++++++++++ .../include/firewall/enable-default-log.xml.i | 8 +++++++ .../include/firewall/name-default-action.xml.i | 25 ---------------------- .../include/firewall/name-default-log.xml.i | 8 ------- interface-definitions/policy-route.xml.in | 4 ++-- interface-definitions/zone-policy.xml.in | 2 +- 7 files changed, 40 insertions(+), 40 deletions(-) create mode 100644 interface-definitions/include/firewall/default-action.xml.i create mode 100644 interface-definitions/include/firewall/enable-default-log.xml.i delete mode 100644 interface-definitions/include/firewall/name-default-action.xml.i delete mode 100644 interface-definitions/include/firewall/name-default-log.xml.i diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in index 2e9452dfd..d28abccd6 100644 --- a/interface-definitions/firewall.xml.in +++ b/interface-definitions/firewall.xml.in @@ -342,8 +342,8 @@ - #include - #include + #include + #include #include @@ -530,8 +530,8 @@ - #include - #include + #include + #include #include diff --git a/interface-definitions/include/firewall/default-action.xml.i b/interface-definitions/include/firewall/default-action.xml.i new file mode 100644 index 000000000..b11dfd2e8 --- /dev/null +++ b/interface-definitions/include/firewall/default-action.xml.i @@ -0,0 +1,25 @@ + + + + Default-action for rule-set + + drop reject accept + + + drop + Drop if no prior rules are hit + + + reject + Drop and notify source if no prior rules are hit + + + accept + Accept if no prior rules are hit + + + (drop|reject|accept) + + + + diff --git a/interface-definitions/include/firewall/enable-default-log.xml.i b/interface-definitions/include/firewall/enable-default-log.xml.i new file mode 100644 index 000000000..1e64edc6e --- /dev/null +++ b/interface-definitions/include/firewall/enable-default-log.xml.i @@ -0,0 +1,8 @@ + + + + Option to log packets hitting default-action + + + + \ No newline at end of file diff --git a/interface-definitions/include/firewall/name-default-action.xml.i b/interface-definitions/include/firewall/name-default-action.xml.i deleted file mode 100644 index 512b0296f..000000000 --- a/interface-definitions/include/firewall/name-default-action.xml.i +++ /dev/null @@ -1,25 +0,0 @@ - - - - Default-action for rule-set - - drop reject accept - - - drop - Drop if no prior rules are hit - - - reject - Drop and notify source if no prior rules are hit - - - accept - Accept if no prior rules are hit - - - (drop|reject|accept) - - - - diff --git a/interface-definitions/include/firewall/name-default-log.xml.i b/interface-definitions/include/firewall/name-default-log.xml.i deleted file mode 100644 index 1d0ff9497..000000000 --- a/interface-definitions/include/firewall/name-default-log.xml.i +++ /dev/null @@ -1,8 +0,0 @@ - - - - Option to log packets hitting default-action - - - - \ No newline at end of file diff --git a/interface-definitions/policy-route.xml.in b/interface-definitions/policy-route.xml.in index a10c9b08f..c2a9a8d94 100644 --- a/interface-definitions/policy-route.xml.in +++ b/interface-definitions/policy-route.xml.in @@ -12,7 +12,7 @@ #include - #include + #include Policy rule number @@ -61,7 +61,7 @@ #include - #include + #include Policy rule number diff --git a/interface-definitions/zone-policy.xml.in b/interface-definitions/zone-policy.xml.in index dca4c59d1..dc3408c3d 100644 --- a/interface-definitions/zone-policy.xml.in +++ b/interface-definitions/zone-policy.xml.in @@ -19,7 +19,7 @@ #include - #include + #include Default-action for traffic coming into this zone -- cgit v1.2.3 From 7d5b11508cf84693e7b75e5219d0f0a64df6b8da Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 30 Aug 2022 17:18:40 +0200 Subject: firewall: T3568: cleanup XML help node - remove information passed via valueHelp --- interface-definitions/firewall.xml.in | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in index d28abccd6..9488ddcdc 100644 --- a/interface-definitions/firewall.xml.in +++ b/interface-definitions/firewall.xml.in @@ -433,7 +433,7 @@ - ICMPv6 code (0-255) + ICMPv6 code u32:0-255 ICMPv6 code (0-255) @@ -445,7 +445,7 @@ - ICMPv6 type (0-255) + ICMPv6 type u32:0-255 ICMPv6 type (0-255) @@ -578,7 +578,7 @@ - ICMP code (0-255) + ICMP code u32:0-255 ICMP code (0-255) @@ -590,7 +590,7 @@ - ICMP type (0-255) + ICMP type u32:0-255 ICMP type (0-255) -- cgit v1.2.3 From a7fba75a06ef6a9551a1246cc3168a1999347e13 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 30 Aug 2022 17:26:14 +0200 Subject: dns: op-mode: T2488: drop invalid "monitor dns forwarding" command The CLI command was a duplicate of the "show dns forwarding" command and did not follow or re-trigger the commadn to watch it. It produced 1:1 the same output as "show dns forwarding". --- op-mode-definitions/dns-forwarding.xml.in | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/op-mode-definitions/dns-forwarding.xml.in b/op-mode-definitions/dns-forwarding.xml.in index 5dea5b91b..c8ca117be 100644 --- a/op-mode-definitions/dns-forwarding.xml.in +++ b/op-mode-definitions/dns-forwarding.xml.in @@ -19,26 +19,6 @@ - - - Show DNS information - - - - - Show DNS forwarding information - - - - - Show DNS forwarding statistics - - sudo ${vyos_op_scripts_dir}/dns_forwarding_statistics.py - - - - - -- cgit v1.2.3 From 69f79beee2070906b68f2b910296c362e7216278 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 30 Aug 2022 17:36:19 +0200 Subject: firewall: T4655: implement XML defaultValue for name and ipv6-name This extends the implementation of commit 0cc7e0a49094 ("firewall: T4655: Fix default action 'drop' for the firewall") in a way that we can now also use the XML node under "firewall name" and "firewall ipv6-name". This is a much cleaner approach which also adds the default value automatically to the CLIs completion helper ("?"). --- .../include/firewall/default-action.xml.i | 1 + python/vyos/template.py | 2 +- src/conf_mode/firewall.py | 30 +++++++++++++++++++--- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/interface-definitions/include/firewall/default-action.xml.i b/interface-definitions/include/firewall/default-action.xml.i index b11dfd2e8..92a2fcaaf 100644 --- a/interface-definitions/include/firewall/default-action.xml.i +++ b/interface-definitions/include/firewall/default-action.xml.i @@ -21,5 +21,6 @@ (drop|reject|accept) + drop diff --git a/python/vyos/template.py b/python/vyos/template.py index 62303bd55..9804308c1 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -550,7 +550,7 @@ def nft_rule(rule_conf, fw_name, rule_id, ip_name='ip'): @register_filter('nft_default_rule') def nft_default_rule(fw_conf, fw_name): output = ['counter'] - default_action = fw_conf.get('default_action', 'drop') + default_action = fw_conf['default_action'] if 'enable_default_log' in fw_conf: action_suffix = default_action[:1].upper() diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 07eca722f..f0ea1a1e5 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -206,9 +206,31 @@ def get_config(config=None): firewall = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) + # We have gathered the dict representation of the CLI, but there are + # default options which we need to update into the dictionary retrived. + # XXX: T2665: we currently have no nice way for defaults under tag + # nodes, thus we load the defaults "by hand" default_values = defaults(base) + for tmp in ['name', 'ipv6_name']: + if tmp in default_values: + del default_values[tmp] + firewall = dict_merge(default_values, firewall) + # Merge in defaults for IPv4 ruleset + if 'name' in firewall: + default_values = defaults(base + ['name']) + for name in firewall['name']: + firewall['name'][name] = dict_merge(default_values, + firewall['name'][name]) + + # Merge in defaults for IPv6 ruleset + if 'ipv6_name' in firewall: + default_values = defaults(base + ['ipv6-name']) + for ipv6_name in firewall['ipv6_name']: + firewall['ipv6_name'][ipv6_name] = dict_merge(default_values, + firewall['ipv6_name'][ipv6_name]) + firewall['policy_resync'] = bool('group' in firewall or node_changed(conf, base + ['group'])) firewall['interfaces'] = get_firewall_interfaces(conf) firewall['zone_policy'] = get_firewall_zones(conf) @@ -315,7 +337,7 @@ def verify_nested_group(group_name, group, groups, seen): if g in seen: raise ConfigError(f'Group "{group_name}" has a circular reference') - + seen.append(g) if 'include' in groups[g]: @@ -378,7 +400,7 @@ def cleanup_commands(firewall): if firewall['geoip_updated']: geoip_key = 'deleted_ipv6_name' if table == 'ip6 filter' else 'deleted_name' geoip_list = dict_search_args(firewall, 'geoip_updated', geoip_key) or [] - + json_str = cmd(f'nft -t -j list table {table}') obj = loads(json_str) @@ -420,7 +442,7 @@ def cleanup_commands(firewall): if set_name.startswith('GEOIP_CC_') and set_name in geoip_list: commands_sets.append(f'delete set {table} {set_name}') continue - + if set_name.startswith("RECENT_"): commands_sets.append(f'delete set {table} {set_name}') continue @@ -520,7 +542,7 @@ def apply(firewall): if install_result == 1: raise ConfigError('Failed to apply firewall') - # set fireall group domain-group xxx + # set firewall group domain-group xxx if 'group' in firewall: if 'domain_group' in firewall['group']: # T970 Enable a resolver (systemd daemon) that checks -- cgit v1.2.3