From ca6b7340714c6161337f508978b9834722be58dc Mon Sep 17 00:00:00 2001 From: Rain <6818611+Rain@users.noreply.github.com> Date: Sat, 8 Oct 2022 18:04:01 -0400 Subject: firewall: T4612: Support arbitrary netmasks Add support for arbitrary netmasks on source/destination addresses in firewall rules. This is particularly useful with DHCPv6-PD when the delegated prefix changes periodically. --- interface-definitions/firewall.xml.in | 4 ++++ .../include/firewall/address-mask-ipv6.xml.i | 14 ++++++++++++++ interface-definitions/include/firewall/address-mask.xml.i | 14 ++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 interface-definitions/include/firewall/address-mask-ipv6.xml.i create mode 100644 interface-definitions/include/firewall/address-mask.xml.i (limited to 'interface-definitions') diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in index 773e86f00..2ac9ca31b 100644 --- a/interface-definitions/firewall.xml.in +++ b/interface-definitions/firewall.xml.in @@ -411,6 +411,7 @@ #include #include #include + #include @@ -422,6 +423,7 @@ #include #include #include + #include #include @@ -575,6 +577,7 @@ #include #include #include + #include @@ -586,6 +589,7 @@ #include #include #include + #include #include diff --git a/interface-definitions/include/firewall/address-mask-ipv6.xml.i b/interface-definitions/include/firewall/address-mask-ipv6.xml.i new file mode 100644 index 000000000..8c0483209 --- /dev/null +++ b/interface-definitions/include/firewall/address-mask-ipv6.xml.i @@ -0,0 +1,14 @@ + + + + IP mask + + ipv6 + IP mask to apply + + + + + + + diff --git a/interface-definitions/include/firewall/address-mask.xml.i b/interface-definitions/include/firewall/address-mask.xml.i new file mode 100644 index 000000000..7f6f17d1e --- /dev/null +++ b/interface-definitions/include/firewall/address-mask.xml.i @@ -0,0 +1,14 @@ + + + + IP mask + + ipv4 + IPv4 mask to apply + + + + + + + -- cgit v1.2.3 From 85f04237160a6ea98eea4ec58f1ccab9f6bfc31a Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Mon, 17 Oct 2022 12:15:22 +0000 Subject: ssh: T4720: Ability to configure SSH-server HostKeyAlgorithms Ability to configure SSH-server HostKeyAlgorithms. Specifies the host key signature algorithms that the server offers. Can accept multiple values. --- data/templates/ssh/sshd_config.j2 | 5 +++++ interface-definitions/ssh.xml.in | 13 +++++++++++++ 2 files changed, 18 insertions(+) (limited to 'interface-definitions') diff --git a/data/templates/ssh/sshd_config.j2 b/data/templates/ssh/sshd_config.j2 index 5bbfdeb88..93735020c 100644 --- a/data/templates/ssh/sshd_config.j2 +++ b/data/templates/ssh/sshd_config.j2 @@ -62,6 +62,11 @@ ListenAddress {{ address }} Ciphers {{ ciphers | join(',') }} {% endif %} +{% if hostkey_algorithm is vyos_defined %} +# Specifies the available Host Key signature algorithms +HostKeyAlgorithms {{ hostkey_algorithm | join(',') }} +{% endif %} + {% if mac is vyos_defined %} # Specifies the available MAC (message authentication code) algorithms MACs {{ mac | join(',') }} diff --git a/interface-definitions/ssh.xml.in b/interface-definitions/ssh.xml.in index f3c731fe5..2bcce2cf0 100644 --- a/interface-definitions/ssh.xml.in +++ b/interface-definitions/ssh.xml.in @@ -133,6 +133,19 @@ + + + Allowed host key signature algorithms + + + ssh-ed25519 ssh-ed25519-cert-v01@openssh.com sk-ssh-ed25519@openssh.com sk-ssh-ed25519-cert-v01@openssh.com ssh-rsa rsa-sha2-256 rsa-sha2-512 ssh-dss ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 sk-ecdsa-sha2-nistp256@openssh.com webauthn-sk-ecdsa-sha2-nistp256@openssh.com ssh-rsa-cert-v01@openssh.com rsa-sha2-256-cert-v01@openssh.com rsa-sha2-512-cert-v01@openssh.com ssh-dss-cert-v01@openssh.com ecdsa-sha2-nistp256-cert-v01@openssh.com ecdsa-sha2-nistp384-cert-v01@openssh.com ecdsa-sha2-nistp521-cert-v01@openssh.com sk-ecdsa-sha2-nistp256-cert-v01@openssh.com + + + + (ssh-ed25519|ssh-ed25519-cert-v01@openssh.com|sk-ssh-ed25519@openssh.com|sk-ssh-ed25519-cert-v01@openssh.com|ssh-rsa|rsa-sha2-256|rsa-sha2-512|ssh-dss|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|sk-ecdsa-sha2-nistp256@openssh.com|webauthn-sk-ecdsa-sha2-nistp256@openssh.com|ssh-rsa-cert-v01@openssh.com|rsa-sha2-256-cert-v01@openssh.com|rsa-sha2-512-cert-v01@openssh.com|ssh-dss-cert-v01@openssh.com|ecdsa-sha2-nistp256-cert-v01@openssh.com|ecdsa-sha2-nistp384-cert-v01@openssh.com|ecdsa-sha2-nistp521-cert-v01@openssh.com|sk-ecdsa-sha2-nistp256-cert-v01@openssh.com) + + + Allowed key exchange (KEX) algorithms -- cgit v1.2.3 From 89fbe73b9fb9ad178a2a35bdf9c7c477dc72f054 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 21 Oct 2022 08:41:26 -0500 Subject: graphql: T4768: change name of api child node from 'gql' to 'graphql' --- interface-definitions/https.xml.in | 2 +- .../include/version/https-version.xml.i | 2 +- smoketest/scripts/cli/test_service_https.py | 10 ++-- src/conf_mode/http-api.py | 2 +- src/migration-scripts/https/3-to-4 | 53 ++++++++++++++++++++++ src/services/vyos-http-api-server | 10 ++-- 6 files changed, 66 insertions(+), 13 deletions(-) create mode 100755 src/migration-scripts/https/3-to-4 (limited to 'interface-definitions') diff --git a/interface-definitions/https.xml.in b/interface-definitions/https.xml.in index d096c4ff1..28656b594 100644 --- a/interface-definitions/https.xml.in +++ b/interface-definitions/https.xml.in @@ -107,7 +107,7 @@ - + GraphQL support diff --git a/interface-definitions/include/version/https-version.xml.i b/interface-definitions/include/version/https-version.xml.i index 586083649..111076974 100644 --- a/interface-definitions/include/version/https-version.xml.i +++ b/interface-definitions/include/version/https-version.xml.i @@ -1,3 +1,3 @@ - + diff --git a/smoketest/scripts/cli/test_service_https.py b/smoketest/scripts/cli/test_service_https.py index 72c1d4e43..719125f0f 100755 --- a/smoketest/scripts/cli/test_service_https.py +++ b/smoketest/scripts/cli/test_service_https.py @@ -143,10 +143,10 @@ class TestHTTPSService(VyOSUnitTestSHIM.TestCase): # caught by the resolver, and returns success 'False', so one must # check the return value. - self.cli_set(base_path + ['api', 'gql']) + self.cli_set(base_path + ['api', 'graphql']) self.cli_commit() - gql_url = f'https://{address}/graphql' + graphql_url = f'https://{address}/graphql' query_valid_key = f""" {{ @@ -160,7 +160,7 @@ class TestHTTPSService(VyOSUnitTestSHIM.TestCase): }} """ - r = request('POST', gql_url, verify=False, headers=headers, json={'query': query_valid_key}) + r = request('POST', graphql_url, verify=False, headers=headers, json={'query': query_valid_key}) success = r.json()['data']['SystemStatus']['success'] self.assertTrue(success) @@ -176,7 +176,7 @@ class TestHTTPSService(VyOSUnitTestSHIM.TestCase): } """ - r = request('POST', gql_url, verify=False, headers=headers, json={'query': query_invalid_key}) + r = request('POST', graphql_url, verify=False, headers=headers, json={'query': query_invalid_key}) success = r.json()['data']['SystemStatus']['success'] self.assertFalse(success) @@ -192,7 +192,7 @@ class TestHTTPSService(VyOSUnitTestSHIM.TestCase): } """ - r = request('POST', gql_url, verify=False, headers=headers, json={'query': query_no_key}) + r = request('POST', graphql_url, verify=False, headers=headers, json={'query': query_no_key}) self.assertEqual(r.status_code, 400) if __name__ == '__main__': diff --git a/src/conf_mode/http-api.py b/src/conf_mode/http-api.py index c196e272b..be80613c6 100755 --- a/src/conf_mode/http-api.py +++ b/src/conf_mode/http-api.py @@ -86,7 +86,7 @@ def get_config(config=None): if 'api_keys' in api_dict: keys_added = True - if 'gql' in api_dict: + if 'graphql' in api_dict: api_dict = dict_merge(defaults(base), api_dict) http_api.update(api_dict) diff --git a/src/migration-scripts/https/3-to-4 b/src/migration-scripts/https/3-to-4 new file mode 100755 index 000000000..5ee528b31 --- /dev/null +++ b/src/migration-scripts/https/3-to-4 @@ -0,0 +1,53 @@ +#!/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 . + +# T4768 rename node 'gql' to 'graphql'. + +import sys + +from vyos.configtree import ConfigTree + +if (len(sys.argv) < 2): + print("Must specify file name!") + sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +config = ConfigTree(config_file) + +old_base = ['service', 'https', 'api', 'gql'] +if not config.exists(old_base): + # Nothing to do + sys.exit(0) + +new_base = ['service', 'https', 'api', 'graphql'] +config.set(new_base) + +nodes = config.list_nodes(old_base) +for node in nodes: + config.copy(old_base + [node], new_base + [node]) + +config.delete(old_base) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print("Failed to save the modified config: {}".format(e)) + sys.exit(1) diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index 4ace981ca..632c1e87d 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -688,16 +688,16 @@ if __name__ == '__main__': app.state.vyos_debug = server_config['debug'] app.state.vyos_strict = server_config['strict'] app.state.vyos_origins = server_config.get('cors', {}).get('allow_origin', []) - if 'gql' in server_config: - app.state.vyos_gql = True - if isinstance(server_config['gql'], dict) and 'introspection' in server_config['gql']: + if 'graphql' in server_config: + app.state.vyos_graphql = True + if isinstance(server_config['graphql'], dict) and 'introspection' in server_config['graphql']: app.state.vyos_introspection = True else: app.state.vyos_introspection = False else: - app.state.vyos_gql = False + app.state.vyos_graphql = False - if app.state.vyos_gql: + if app.state.vyos_graphql: graphql_init(app) try: -- cgit v1.2.3 From cbb72ad6d3f5f08ad23c40e29b9463087ca5cade Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Sun, 23 Oct 2022 11:06:52 -0500 Subject: graphql: T4574: add interface definitions for authentication settings --- interface-definitions/https.xml.in | 53 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'interface-definitions') diff --git a/interface-definitions/https.xml.in b/interface-definitions/https.xml.in index 28656b594..6adb07598 100644 --- a/interface-definitions/https.xml.in +++ b/interface-definitions/https.xml.in @@ -118,6 +118,59 @@ + + + GraphQL authentication + + + + + Authentication type + + key token + + + key + Use API keys + + + token + Use JWT token + + + (key|token) + + + key + + + + Token time to expire in seconds + + u32:60-31536000 + Token lifetime in seconds + + + + + + 3600 + + + + Length of shared secret in bytes + + u32:16-65535 + Byte length of generated shared secret + + + + + + 32 + + + -- cgit v1.2.3 From 07afb79785ac5005a02df60df1ea427bdabe7de7 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 29 Oct 2022 20:58:04 +0200 Subject: static: T4784: add description node for static route/route6 tagNodes --- interface-definitions/include/static/static-route.xml.i | 1 + interface-definitions/include/static/static-route6.xml.i | 1 + 2 files changed, 2 insertions(+) (limited to 'interface-definitions') diff --git a/interface-definitions/include/static/static-route.xml.i b/interface-definitions/include/static/static-route.xml.i index 2de5dc58f..04ee999c7 100644 --- a/interface-definitions/include/static/static-route.xml.i +++ b/interface-definitions/include/static/static-route.xml.i @@ -14,6 +14,7 @@ #include #include #include + #include Next-hop IPv4 router interface diff --git a/interface-definitions/include/static/static-route6.xml.i b/interface-definitions/include/static/static-route6.xml.i index 35feef41c..6131ac7fe 100644 --- a/interface-definitions/include/static/static-route6.xml.i +++ b/interface-definitions/include/static/static-route6.xml.i @@ -13,6 +13,7 @@ #include #include + #include IPv6 gateway interface name -- cgit v1.2.3 From dda62226353ebc198b4dbbd319412bb5d1d1ece2 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 29 Oct 2022 20:58:37 +0200 Subject: snmp: T4785: allow ! in community name --- interface-definitions/snmp.xml.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'interface-definitions') diff --git a/interface-definitions/snmp.xml.in b/interface-definitions/snmp.xml.in index b4f72589e..91c2715a0 100644 --- a/interface-definitions/snmp.xml.in +++ b/interface-definitions/snmp.xml.in @@ -13,7 +13,7 @@ Community name - [a-zA-Z0-9\-_]{1,100} + [a-zA-Z0-9\-_!]{1,100} Community string is limited to alphanumerical characters only with a total lenght of 100 -- cgit v1.2.3 From 3f91033927d80748b70e1ef58b2941643d1aca33 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 30 Oct 2022 12:50:36 +0100 Subject: snmp: T4785: allow @, * and # in SNMP community name --- interface-definitions/snmp.xml.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'interface-definitions') diff --git a/interface-definitions/snmp.xml.in b/interface-definitions/snmp.xml.in index 91c2715a0..7ec60b2e7 100644 --- a/interface-definitions/snmp.xml.in +++ b/interface-definitions/snmp.xml.in @@ -13,9 +13,9 @@ Community name - [a-zA-Z0-9\-_!]{1,100} + [a-zA-Z0-9\-_!@*#]{1,100} - Community string is limited to alphanumerical characters only with a total lenght of 100 + Community string is limited to alphanumerical characters, !, @, * and # with a total lenght of 100 -- cgit v1.2.3 From 22c3dcbb01d731f0dab0ffefa2e5a0be7009baf1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 31 Oct 2022 15:09:58 +0100 Subject: ipsec: T4787: add support for road-warrior/remote-access RADIUS timeout This enabled users to also use 2FA/MFA authentication with a radius backend as there is enough time to enter the second factor. --- data/templates/ipsec/charon/eap-radius.conf.j2 | 4 +++- interface-definitions/include/radius-timeout.xml.i | 16 ++++++++++++++++ interface-definitions/vpn-ipsec.xml.in | 1 + interface-definitions/vpn-openconnect.xml.in | 15 +-------------- src/conf_mode/vpn_ipsec.py | 17 +++++++++++++++-- 5 files changed, 36 insertions(+), 17 deletions(-) create mode 100644 interface-definitions/include/radius-timeout.xml.i (limited to 'interface-definitions') diff --git a/data/templates/ipsec/charon/eap-radius.conf.j2 b/data/templates/ipsec/charon/eap-radius.conf.j2 index 8495011fe..364377473 100644 --- a/data/templates/ipsec/charon/eap-radius.conf.j2 +++ b/data/templates/ipsec/charon/eap-radius.conf.j2 @@ -49,8 +49,10 @@ eap-radius { # Base to use for calculating exponential back off. # retransmit_base = 1.4 +{% if remote_access.radius.timeout is vyos_defined %} # Timeout in seconds before sending first retransmit. - # retransmit_timeout = 2.0 + retransmit_timeout = {{ remote_access.radius.timeout | float }} +{% endif %} # Number of times to retransmit a packet before giving up. # retransmit_tries = 4 diff --git a/interface-definitions/include/radius-timeout.xml.i b/interface-definitions/include/radius-timeout.xml.i new file mode 100644 index 000000000..22bb6d312 --- /dev/null +++ b/interface-definitions/include/radius-timeout.xml.i @@ -0,0 +1,16 @@ + + + + Session timeout + + u32:1-240 + Session timeout in seconds (default: 2) + + + + + Timeout must be between 1 and 240 seconds + + 2 + + diff --git a/interface-definitions/vpn-ipsec.xml.in b/interface-definitions/vpn-ipsec.xml.in index 4776c53dc..64966b540 100644 --- a/interface-definitions/vpn-ipsec.xml.in +++ b/interface-definitions/vpn-ipsec.xml.in @@ -888,6 +888,7 @@ #include + #include #include diff --git a/interface-definitions/vpn-openconnect.xml.in b/interface-definitions/vpn-openconnect.xml.in index 3b3a83bd4..8b60f2e6e 100644 --- a/interface-definitions/vpn-openconnect.xml.in +++ b/interface-definitions/vpn-openconnect.xml.in @@ -140,20 +140,7 @@ #include - - - Session timeout - - u32:1-240 - Session timeout in seconds (default: 2) - - - - - Timeout must be between 1 and 240 seconds - - 2 - + #include If the groupconfig option is set, then config-per-user will be overriden, and all configuration will be read from RADIUS. diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index 77a425f8b..cfefcfbe8 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -117,13 +117,26 @@ def get_config(config=None): ipsec['ike_group'][group]['proposal'][proposal] = dict_merge(default_values, ipsec['ike_group'][group]['proposal'][proposal]) - if 'remote_access' in ipsec and 'connection' in ipsec['remote_access']: + # XXX: T2665: we can not safely rely on the defaults() when there are + # tagNodes in place, it is better to blend in the defaults manually. + if dict_search('remote_access.connection', ipsec): default_values = defaults(base + ['remote-access', 'connection']) for rw in ipsec['remote_access']['connection']: ipsec['remote_access']['connection'][rw] = dict_merge(default_values, ipsec['remote_access']['connection'][rw]) - if 'remote_access' in ipsec and 'radius' in ipsec['remote_access'] and 'server' in ipsec['remote_access']['radius']: + # XXX: T2665: we can not safely rely on the defaults() when there are + # tagNodes in place, it is better to blend in the defaults manually. + if dict_search('remote_access.radius.server', ipsec): + # Fist handle the "base" stuff like RADIUS timeout + default_values = defaults(base + ['remote-access', 'radius']) + if 'server' in default_values: + del default_values['server'] + ipsec['remote_access']['radius'] = dict_merge(default_values, + ipsec['remote_access']['radius']) + + # Take care about individual RADIUS servers implemented as tagNodes - this + # requires special treatment default_values = defaults(base + ['remote-access', 'radius', 'server']) for server in ipsec['remote_access']['radius']['server']: ipsec['remote_access']['radius']['server'][server] = dict_merge(default_values, -- cgit v1.2.3 From f50f7b043a8636a57fc61330d94550734d2826b5 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 1 Nov 2022 09:03:40 +0100 Subject: login: T4750: add ecdsa-sk and ed25519-sk as supported public key type --- interface-definitions/system-login.xml.in | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) (limited to 'interface-definitions') diff --git a/interface-definitions/system-login.xml.in b/interface-definitions/system-login.xml.in index def42544a..027d3f587 100644 --- a/interface-definitions/system-login.xml.in +++ b/interface-definitions/system-login.xml.in @@ -127,32 +127,44 @@ - Public key type + SSH public key type - ssh-dss ssh-rsa ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 ssh-ed25519 + ssh-dss ssh-rsa ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 ssh-ed25519 ecdsa-sk ed25519-sk ssh-dss - + Digital Signature Algorithm (DSA) key support ssh-rsa - + Key pair based on RSA algorithm ecdsa-sha2-nistp256 - + Elliptic Curve DSA with NIST P-256 curve ecdsa-sha2-nistp384 - + Elliptic Curve DSA with NIST P-384 curve + + + ecdsa-sha2-nistp521 + Elliptic Curve DSA with NIST P-521 curve ssh-ed25519 - + Edwards-curve DSA with elliptic curve 25519 + + + ecdsa-sk + Elliptic Curve DSA security key + + + ed25519-sk + Elliptic curve 25519 security key - (ssh-dss|ssh-rsa|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|ssh-ed25519) + (ssh-dss|ssh-rsa|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|ssh-ed25519|ecdsa-sk|ed25519-sk) -- cgit v1.2.3 From 4ae434d50337b6a1543176b0b86e938fc0663626 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 3 Nov 2022 17:39:19 +0100 Subject: xml: T4795: provide common and re-usable XML definitions for policy Remove duplicated code and move to single-source of truth. --- interface-definitions/firewall.xml.in | 2 +- .../include/firewall/common-rule.xml.i | 17 +- .../include/firewall/mac-address.xml.i | 18 + .../include/policy/route-common-rule-ipv6.xml.i | 557 --------------------- .../include/policy/route-common-rule.xml.i | 406 --------------- .../include/policy/route-common.xml.i | 348 +++++++++++++ .../include/policy/route-ipv4.xml.i | 45 ++ .../include/policy/route-ipv6.xml.i | 196 ++++++++ interface-definitions/policy-route.xml.in | 6 +- 9 files changed, 613 insertions(+), 982 deletions(-) create mode 100644 interface-definitions/include/firewall/mac-address.xml.i delete mode 100644 interface-definitions/include/policy/route-common-rule-ipv6.xml.i delete mode 100644 interface-definitions/include/policy/route-common-rule.xml.i create mode 100644 interface-definitions/include/policy/route-common.xml.i create mode 100644 interface-definitions/include/policy/route-ipv4.xml.i create mode 100644 interface-definitions/include/policy/route-ipv6.xml.i (limited to 'interface-definitions') diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in index 673461036..c8685a187 100644 --- a/interface-definitions/firewall.xml.in +++ b/interface-definitions/firewall.xml.in @@ -218,7 +218,7 @@ Mac-group member - <MAC address> + macaddr MAC address to match diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i index a4f66f5cb..75ad427f9 100644 --- a/interface-definitions/include/firewall/common-rule.xml.i +++ b/interface-definitions/include/firewall/common-rule.xml.i @@ -219,22 +219,7 @@ #include #include - - - Source MAC address - - <MAC address> - MAC address to match - - - !<MAC address> - Match everything except the specified MAC address - - - - - - + #include #include diff --git a/interface-definitions/include/firewall/mac-address.xml.i b/interface-definitions/include/firewall/mac-address.xml.i new file mode 100644 index 000000000..83aaf1ce1 --- /dev/null +++ b/interface-definitions/include/firewall/mac-address.xml.i @@ -0,0 +1,18 @@ + + + + MAC address + + macaddr; + MAC address to match + + + !macaddr + Match everything except the specified MAC address + + + + + + + diff --git a/interface-definitions/include/policy/route-common-rule-ipv6.xml.i b/interface-definitions/include/policy/route-common-rule-ipv6.xml.i deleted file mode 100644 index 662206336..000000000 --- a/interface-definitions/include/policy/route-common-rule-ipv6.xml.i +++ /dev/null @@ -1,557 +0,0 @@ - -#include -#include - - - Option to disable firewall rule - - - - - - IP fragment match - - - - - Second and further fragments of fragmented packets - - - - - - Head fragments or unfragmented packets - - - - - - - - Inbound IPsec packets - - - - - Inbound IPsec packets - - - - - - Inbound non-IPsec packets - - - - - - - - Rate limit using a token bucket filter - - - - - Maximum number of packets to allow in excess of rate - - u32:0-4294967295 - Maximum number of packets to allow in excess of rate - - - - - - - - - Maximum average matching rate - - u32:0-4294967295 - Maximum average matching rate - - - - - - - - - - - Option to log packets matching rule - - enable disable - - - enable - Enable log - - - disable - Disable log - - - (enable|disable) - - - - - - Protocol to match (protocol name, number, or "all") - - - - - all - All IP protocols - - - tcp_udp - Both TCP and UDP - - - 0-255 - IP protocol number - - - !<protocol> - IP protocol number - - - - - - all - - - - Parameters for matching recently seen sources - - - - - Source addresses seen more than N times - - u32:1-255 - Source addresses seen more than N times - - - - - - - - - Source addresses seen in the last N seconds - - u32:0-4294967295 - Source addresses seen in the last N seconds - - - - - - - - - - - Packet modifications - - - - - Packet Differentiated Services Codepoint (DSCP) - - u32:0-63 - DSCP number - - - - - - - - - Packet marking - - u32:1-2147483647 - Packet marking - - - - - - - - - Routing table to forward packet with - - u32:1-200 - Table number - - - main - Main table - - - - (main) - - - main - protocols static table - - - - - - TCP Maximum Segment Size - - u32:500-1460 - Explicitly set TCP MSS value - - - - - - - - - - - Source parameters - - - #include - #include - - - Source MAC address - - <MAC address> - MAC address to match - - - !<MAC address> - Match everything except the specified MAC address - - - - - - - #include - - - - - Session state - - - - - Established state - - enable disable - - - enable - Enable - - - disable - Disable - - - (enable|disable) - - - - - - Invalid state - - enable disable - - - enable - Enable - - - disable - Disable - - - (enable|disable) - - - - - - New state - - enable disable - - - enable - Enable - - - disable - Disable - - - (enable|disable) - - - - - - Related state - - enable disable - - - enable - Enable - - - disable - Disable - - - (enable|disable) - - - - - -#include - - - Time to match rule - - - - - Monthdays to match rule on - - - - - Date to start matching rule - - - - - Time of day to start matching rule - - - - - Date to stop matching rule - - - - - Time of day to stop matching rule - - - - - Interpret times for startdate, stopdate, starttime and stoptime to be UTC - - - - - - Weekdays to match rule on - - - - - - - ICMPv6 type and code information - - - - - ICMP type-name - - any echo-reply pong destination-unreachable network-unreachable host-unreachable protocol-unreachable port-unreachable fragmentation-needed source-route-failed network-unknown host-unknown network-prohibited host-prohibited TOS-network-unreachable TOS-host-unreachable communication-prohibited host-precedence-violation precedence-cutoff source-quench redirect network-redirect host-redirect TOS-network-redirect TOS host-redirect echo-request ping router-advertisement router-solicitation time-exceeded ttl-exceeded ttl-zero-during-transit ttl-zero-during-reassembly parameter-problem ip-header-bad required-option-missing timestamp-request timestamp-reply address-mask-request address-mask-reply packet-too-big - - - any - Any ICMP type/code - - - echo-reply - ICMP type/code name - - - pong - ICMP type/code name - - - destination-unreachable - ICMP type/code name - - - network-unreachable - ICMP type/code name - - - host-unreachable - ICMP type/code name - - - protocol-unreachable - ICMP type/code name - - - port-unreachable - ICMP type/code name - - - fragmentation-needed - ICMP type/code name - - - source-route-failed - ICMP type/code name - - - network-unknown - ICMP type/code name - - - host-unknown - ICMP type/code name - - - network-prohibited - ICMP type/code name - - - host-prohibited - ICMP type/code name - - - TOS-network-unreachable - ICMP type/code name - - - TOS-host-unreachable - ICMP type/code name - - - communication-prohibited - ICMP type/code name - - - host-precedence-violation - ICMP type/code name - - - precedence-cutoff - ICMP type/code name - - - source-quench - ICMP type/code name - - - redirect - ICMP type/code name - - - network-redirect - ICMP type/code name - - - host-redirect - ICMP type/code name - - - TOS-network-redirect - ICMP type/code name - - - TOS host-redirect - ICMP type/code name - - - echo-request - ICMP type/code name - - - ping - ICMP type/code name - - - router-advertisement - ICMP type/code name - - - router-solicitation - ICMP type/code name - - - time-exceeded - ICMP type/code name - - - ttl-exceeded - ICMP type/code name - - - ttl-zero-during-transit - ICMP type/code name - - - ttl-zero-during-reassembly - ICMP type/code name - - - parameter-problem - ICMP type/code name - - - ip-header-bad - ICMP type/code name - - - required-option-missing - ICMP type/code name - - - timestamp-request - ICMP type/code name - - - timestamp-reply - ICMP type/code name - - - address-mask-request - ICMP type/code name - - - address-mask-reply - ICMP type/code name - - - packet-too-big - ICMP type/code name - - - (any|echo-reply|pong|destination-unreachable|network-unreachable|host-unreachable|protocol-unreachable|port-unreachable|fragmentation-needed|source-route-failed|network-unknown|host-unknown|network-prohibited|host-prohibited|TOS-network-unreachable|TOS-host-unreachable|communication-prohibited|host-precedence-violation|precedence-cutoff|source-quench|redirect|network-redirect|host-redirect|TOS-network-redirect|TOS host-redirect|echo-request|ping|router-advertisement|router-solicitation|time-exceeded|ttl-exceeded|ttl-zero-during-transit|ttl-zero-during-reassembly|parameter-problem|ip-header-bad|required-option-missing|timestamp-request|timestamp-reply|address-mask-request|address-mask-reply|packet-too-big) - - - - - - - diff --git a/interface-definitions/include/policy/route-common-rule.xml.i b/interface-definitions/include/policy/route-common-rule.xml.i deleted file mode 100644 index 35fccca50..000000000 --- a/interface-definitions/include/policy/route-common-rule.xml.i +++ /dev/null @@ -1,406 +0,0 @@ - -#include -#include - - - Option to disable firewall rule - - - - - - IP fragment match - - - - - Second and further fragments of fragmented packets - - - - - - Head fragments or unfragmented packets - - - - - - - - Inbound IPsec packets - - - - - Inbound IPsec packets - - - - - - Inbound non-IPsec packets - - - - - - - - Rate limit using a token bucket filter - - - - - Maximum number of packets to allow in excess of rate - - u32:0-4294967295 - Maximum number of packets to allow in excess of rate - - - - - - - - - Maximum average matching rate - - u32:0-4294967295 - Maximum average matching rate - - - - - - - - - - - Option to log packets matching rule - - enable disable - - - enable - Enable log - - - disable - Disable log - - - (enable|disable) - - - - - - Protocol to match (protocol name, number, or "all") - - - - - all - All IP protocols - - - tcp_udp - Both TCP and UDP - - - 0-255 - IP protocol number - - - !<protocol> - IP protocol number - - - - - - all - - - - Parameters for matching recently seen sources - - - - - Source addresses seen more than N times - - u32:1-255 - Source addresses seen more than N times - - - - - - - - - Source addresses seen in the last N seconds - - u32:0-4294967295 - Source addresses seen in the last N seconds - - - - - - - - - - - Packet modifications - - - - - Packet Differentiated Services Codepoint (DSCP) - - u32:0-63 - DSCP number - - - - - - - - - Packet marking - - u32:1-2147483647 - Packet marking - - - - - - - - - Routing table to forward packet with - - u32:1-200 - Table number - - - main - Main table - - - - (main) - - - main - protocols static table - - - - - - TCP Maximum Segment Size - - u32:500-1460 - Explicitly set TCP MSS value - - - - - - - - - - - Source parameters - - - #include - #include - - - Source MAC address - - <MAC address> - MAC address to match - - - !<MAC address> - Match everything except the specified MAC address - - - - - - - #include - - - - - Session state - - - - - Established state - - enable disable - - - enable - Enable - - - disable - Disable - - - (enable|disable) - - - - - - Invalid state - - enable disable - - - enable - Enable - - - disable - Disable - - - (enable|disable) - - - - - - New state - - enable disable - - - enable - Enable - - - disable - Disable - - - (enable|disable) - - - - - - Related state - - enable disable - - - enable - Enable - - - disable - Disable - - - (enable|disable) - - - - - -#include - - - Time to match rule - - - - - Monthdays to match rule on - - - - - Date to start matching rule - - - - - Time of day to start matching rule - - - - - Date to stop matching rule - - - - - Time of day to stop matching rule - - - - - Interpret times for startdate, stopdate, starttime and stoptime to be UTC - - - - - - Weekdays to match rule on - - - - - - - ICMP type and code information - - - - - ICMP code (0-255) - - u32:0-255 - ICMP code (0-255) - - - - - - - - - ICMP type (0-255) - - u32:0-255 - ICMP type (0-255) - - - - - - - #include - - - diff --git a/interface-definitions/include/policy/route-common.xml.i b/interface-definitions/include/policy/route-common.xml.i new file mode 100644 index 000000000..8b959c2a4 --- /dev/null +++ b/interface-definitions/include/policy/route-common.xml.i @@ -0,0 +1,348 @@ + +#include +#include + + + Option to disable firewall rule + + + + + + IP fragment match + + + + + Second and further fragments of fragmented packets + + + + + + Head fragments or unfragmented packets + + + + + + + + Inbound IPsec packets + + + + + Inbound IPsec packets + + + + + + Inbound non-IPsec packets + + + + + + + + Rate limit using a token bucket filter + + + + + Maximum number of packets to allow in excess of rate + + u32:0-4294967295 + Maximum number of packets to allow in excess of rate + + + + + + + + + Maximum average matching rate + + u32:0-4294967295 + Maximum average matching rate + + + + + + + + + + + Option to log packets matching rule + + enable disable + + + enable + Enable log + + + disable + Disable log + + + (enable|disable) + + + + + + Protocol to match (protocol name, number, or "all") + + + + + all + All IP protocols + + + tcp_udp + Both TCP and UDP + + + 0-255 + IP protocol number + + + !<protocol> + IP protocol number + + + + + + all + + + + Parameters for matching recently seen sources + + + + + Source addresses seen more than N times + + u32:1-255 + Source addresses seen more than N times + + + + + + + + + Source addresses seen in the last N seconds + + u32:0-4294967295 + Source addresses seen in the last N seconds + + + + + + + + + + + Packet modifications + + + + + Packet Differentiated Services Codepoint (DSCP) + + u32:0-63 + DSCP number + + + + + + + + + Packet marking + + u32:1-2147483647 + Packet marking + + + + + + + + + Routing table to forward packet with + + u32:1-200 + Table number + + + main + Main table + + + + (main) + + + main + protocols static table + + + + + + TCP Maximum Segment Size + + u32:500-1460 + Explicitly set TCP MSS value + + + + + + + + + + + Session state + + + + + Established state + + enable disable + + + enable + Enable + + + disable + Disable + + + (enable|disable) + + + + + + Invalid state + + enable disable + + + enable + Enable + + + disable + Disable + + + (enable|disable) + + + + + + New state + + enable disable + + + enable + Enable + + + disable + Disable + + + (enable|disable) + + + + + + Related state + + enable disable + + + enable + Enable + + + disable + Disable + + + (enable|disable) + + + + + +#include + + + Time to match rule + + + + + Monthdays to match rule on + + + + + Date to start matching rule + + + + + Time of day to start matching rule + + + + + Date to stop matching rule + + + + + Time of day to stop matching rule + + + + + Interpret times for startdate, stopdate, starttime and stoptime to be UTC + + + + + + Weekdays to match rule on + + + + + diff --git a/interface-definitions/include/policy/route-ipv4.xml.i b/interface-definitions/include/policy/route-ipv4.xml.i new file mode 100644 index 000000000..1f717a1a4 --- /dev/null +++ b/interface-definitions/include/policy/route-ipv4.xml.i @@ -0,0 +1,45 @@ + + + + Source parameters + + + #include + #include + #include + #include + + + + + ICMP type and code information + + + + + ICMP code (0-255) + + u32:0-255 + ICMP code (0-255) + + + + + + + + + ICMP type (0-255) + + u32:0-255 + ICMP type (0-255) + + + + + + + #include + + + diff --git a/interface-definitions/include/policy/route-ipv6.xml.i b/interface-definitions/include/policy/route-ipv6.xml.i new file mode 100644 index 000000000..d636a654b --- /dev/null +++ b/interface-definitions/include/policy/route-ipv6.xml.i @@ -0,0 +1,196 @@ + + + + Source parameters + + + #include + #include + #include + #include + + + + + ICMPv6 type and code information + + + + + ICMP type-name + + any echo-reply pong destination-unreachable network-unreachable host-unreachable protocol-unreachable port-unreachable fragmentation-needed source-route-failed network-unknown host-unknown network-prohibited host-prohibited TOS-network-unreachable TOS-host-unreachable communication-prohibited host-precedence-violation precedence-cutoff source-quench redirect network-redirect host-redirect TOS-network-redirect TOS host-redirect echo-request ping router-advertisement router-solicitation time-exceeded ttl-exceeded ttl-zero-during-transit ttl-zero-during-reassembly parameter-problem ip-header-bad required-option-missing timestamp-request timestamp-reply address-mask-request address-mask-reply packet-too-big + + + any + Any ICMP type/code + + + echo-reply + ICMP type/code name + + + pong + ICMP type/code name + + + destination-unreachable + ICMP type/code name + + + network-unreachable + ICMP type/code name + + + host-unreachable + ICMP type/code name + + + protocol-unreachable + ICMP type/code name + + + port-unreachable + ICMP type/code name + + + fragmentation-needed + ICMP type/code name + + + source-route-failed + ICMP type/code name + + + network-unknown + ICMP type/code name + + + host-unknown + ICMP type/code name + + + network-prohibited + ICMP type/code name + + + host-prohibited + ICMP type/code name + + + TOS-network-unreachable + ICMP type/code name + + + TOS-host-unreachable + ICMP type/code name + + + communication-prohibited + ICMP type/code name + + + host-precedence-violation + ICMP type/code name + + + precedence-cutoff + ICMP type/code name + + + source-quench + ICMP type/code name + + + redirect + ICMP type/code name + + + network-redirect + ICMP type/code name + + + host-redirect + ICMP type/code name + + + TOS-network-redirect + ICMP type/code name + + + TOS host-redirect + ICMP type/code name + + + echo-request + ICMP type/code name + + + ping + ICMP type/code name + + + router-advertisement + ICMP type/code name + + + router-solicitation + ICMP type/code name + + + time-exceeded + ICMP type/code name + + + ttl-exceeded + ICMP type/code name + + + ttl-zero-during-transit + ICMP type/code name + + + ttl-zero-during-reassembly + ICMP type/code name + + + parameter-problem + ICMP type/code name + + + ip-header-bad + ICMP type/code name + + + required-option-missing + ICMP type/code name + + + timestamp-request + ICMP type/code name + + + timestamp-reply + ICMP type/code name + + + address-mask-request + ICMP type/code name + + + address-mask-reply + ICMP type/code name + + + packet-too-big + ICMP type/code name + + + (any|echo-reply|pong|destination-unreachable|network-unreachable|host-unreachable|protocol-unreachable|port-unreachable|fragmentation-needed|source-route-failed|network-unknown|host-unknown|network-prohibited|host-prohibited|TOS-network-unreachable|TOS-host-unreachable|communication-prohibited|host-precedence-violation|precedence-cutoff|source-quench|redirect|network-redirect|host-redirect|TOS-network-redirect|TOS host-redirect|echo-request|ping|router-advertisement|router-solicitation|time-exceeded|ttl-exceeded|ttl-zero-during-transit|ttl-zero-during-reassembly|parameter-problem|ip-header-bad|required-option-missing|timestamp-request|timestamp-reply|address-mask-request|address-mask-reply|packet-too-big) + + + + + + + diff --git a/interface-definitions/policy-route.xml.in b/interface-definitions/policy-route.xml.in index f480f3bd5..44b96c2e6 100644 --- a/interface-definitions/policy-route.xml.in +++ b/interface-definitions/policy-route.xml.in @@ -46,7 +46,8 @@ #include - #include + #include + #include #include #include #include @@ -98,7 +99,8 @@ #include - #include + #include + #include #include #include #include -- cgit v1.2.3 From 6f37744ad45a5f4cd585fa7c981833a9ebe5e437 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 3 Nov 2022 17:40:12 +0100 Subject: xml: T4795: superseed allowed-vlan validator by numeric range validator Reduce CPU time when spawning the python interpreter. Same can be done by the numeric validator. --- interface-definitions/interfaces-bridge.xml.in | 2 +- src/validators/allowed-vlan | 19 ------------------- 2 files changed, 1 insertion(+), 20 deletions(-) delete mode 100755 src/validators/allowed-vlan (limited to 'interface-definitions') diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in index 1e11cd4c6..d633077d9 100644 --- a/interface-definitions/interfaces-bridge.xml.in +++ b/interface-definitions/interfaces-bridge.xml.in @@ -151,7 +151,7 @@ VLAN id range allowed on this interface (use '-' as delimiter) - + not a valid VLAN ID value or range diff --git a/src/validators/allowed-vlan b/src/validators/allowed-vlan deleted file mode 100755 index 11389390b..000000000 --- a/src/validators/allowed-vlan +++ /dev/null @@ -1,19 +0,0 @@ -#! /usr/bin/python3 - -import sys -import re - -if __name__ == '__main__': - if len(sys.argv)>1: - allowed_vlan = sys.argv[1] - if re.search('[0-9]{1,4}-[0-9]{1,4}', allowed_vlan): - for tmp in allowed_vlan.split('-'): - if int(tmp) not in range(1, 4095): - sys.exit(1) - else: - if int(allowed_vlan) not in range(1, 4095): - sys.exit(1) - else: - sys.exit(2) - - sys.exit(0) -- cgit v1.2.3 From 3f5464d0ee857d204dc58867065380340008f79b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 3 Nov 2022 17:47:55 +0100 Subject: validators: T4795: migrate mac-address python validator to validate-value Instead of spawning the Python interpreter for every mac-address to validate, rather use the base validate-value OCaml implementation which is much faster. This removes redundant code and also makes the CLI more responsive. Validator is moved out to a dedicated file instead of using XML inlined for the reason of re-usability. So if that regex needs to be touched again - it can all happen in one single file. --- .../include/firewall/mac-address.xml.i | 5 ++-- src/validators/mac-address | 29 ++-------------------- src/validators/mac-address-exclude | 2 ++ src/validators/mac-address-firewall | 27 -------------------- 4 files changed, 7 insertions(+), 56 deletions(-) create mode 100755 src/validators/mac-address-exclude delete mode 100755 src/validators/mac-address-firewall (limited to 'interface-definitions') diff --git a/interface-definitions/include/firewall/mac-address.xml.i b/interface-definitions/include/firewall/mac-address.xml.i index 83aaf1ce1..db3e1e312 100644 --- a/interface-definitions/include/firewall/mac-address.xml.i +++ b/interface-definitions/include/firewall/mac-address.xml.i @@ -3,7 +3,7 @@ MAC address - macaddr; + macaddr MAC address to match @@ -11,7 +11,8 @@ Match everything except the specified MAC address - + + diff --git a/src/validators/mac-address b/src/validators/mac-address index 7d020f387..bb859a603 100755 --- a/src/validators/mac-address +++ b/src/validators/mac-address @@ -1,27 +1,2 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018-2020 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import re -import sys - -pattern = "^([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})$" - -if __name__ == '__main__': - if len(sys.argv) != 2: - sys.exit(1) - if not re.match(pattern, sys.argv[1]): - sys.exit(1) - sys.exit(0) +#!/usr/bin/env sh +${vyos_libexec_dir}/validate-value --regex "([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})" --value "$1" diff --git a/src/validators/mac-address-exclude b/src/validators/mac-address-exclude new file mode 100755 index 000000000..c44913023 --- /dev/null +++ b/src/validators/mac-address-exclude @@ -0,0 +1,2 @@ +#!/usr/bin/env sh +${vyos_libexec_dir}/validate-value --regex "!([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})" --value "$1" diff --git a/src/validators/mac-address-firewall b/src/validators/mac-address-firewall deleted file mode 100755 index 70551f86d..000000000 --- a/src/validators/mac-address-firewall +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018-2022 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# 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 re -import sys - -pattern = "^!?([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})$" - -if __name__ == '__main__': - if len(sys.argv) != 2: - sys.exit(1) - if not re.match(pattern, sys.argv[1]): - sys.exit(1) - sys.exit(0) -- cgit v1.2.3 From 051e063fdf2e459a0716a35778b33ea6bb2fdcb6 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Mon, 31 Oct 2022 14:26:51 +0100 Subject: firewall: T970: Refactor domain resolver, add firewall source/destination `fqdn` node --- data/templates/firewall/nftables-defines.j2 | 8 + data/templates/firewall/nftables.j2 | 14 +- interface-definitions/firewall.xml.in | 25 ++- interface-definitions/include/firewall/fqdn.xml.i | 14 ++ .../firewall/source-destination-group-ipv6.xml.i | 8 + python/vyos/firewall.py | 90 ++++------ smoketest/scripts/cli/test_firewall.py | 16 ++ src/conf_mode/firewall.py | 60 +++---- src/helpers/vyos-domain-group-resolve.py | 60 ------- src/helpers/vyos-domain-resolver.py | 182 +++++++++++++++++++++ src/systemd/vyos-domain-group-resolve.service | 11 -- src/systemd/vyos-domain-resolver.service | 13 ++ 12 files changed, 328 insertions(+), 173 deletions(-) create mode 100644 interface-definitions/include/firewall/fqdn.xml.i delete mode 100755 src/helpers/vyos-domain-group-resolve.py create mode 100755 src/helpers/vyos-domain-resolver.py delete mode 100644 src/systemd/vyos-domain-group-resolve.service create mode 100644 src/systemd/vyos-domain-resolver.service (limited to 'interface-definitions') diff --git a/data/templates/firewall/nftables-defines.j2 b/data/templates/firewall/nftables-defines.j2 index 5336f7ee6..dd06dee28 100644 --- a/data/templates/firewall/nftables-defines.j2 +++ b/data/templates/firewall/nftables-defines.j2 @@ -27,6 +27,14 @@ } {% endfor %} {% endif %} +{% if group.domain_group is vyos_defined %} +{% for name, name_config in group.domain_group.items() %} + set D_{{ name }} { + type {{ ip_type }} + flags interval + } +{% endfor %} +{% endif %} {% if group.mac_group is vyos_defined %} {% for group_name, group_conf in group.mac_group.items() %} {% set includes = group_conf.include if group_conf.include is vyos_defined else [] %} diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2 index a0f0b8c11..2c7115134 100644 --- a/data/templates/firewall/nftables.j2 +++ b/data/templates/firewall/nftables.j2 @@ -67,14 +67,12 @@ table ip vyos_filter { {{ conf | nft_default_rule(name_text) }} } {% endfor %} -{% if group is vyos_defined and group.domain_group is vyos_defined %} -{% for name, name_config in group.domain_group.items() %} - set D_{{ name }} { +{% for set_name in ip_fqdn %} + set FQDN_{{ set_name }} { type ipv4_addr flags interval } -{% endfor %} -{% endif %} +{% endfor %} {% for set_name in ns.sets %} set RECENT_{{ set_name }} { type ipv4_addr @@ -178,6 +176,12 @@ table ip6 vyos_filter { {{ conf | nft_default_rule(name_text, ipv6=True) }} } {% endfor %} +{% for set_name in ip6_fqdn %} + set FQDN_{{ set_name }} { + type ipv6_addr + flags interval + } +{% endfor %} {% for set_name in ns.sets %} set RECENT6_{{ set_name }} { type ipv6_addr diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in index 673461036..2d8f17351 100644 --- a/interface-definitions/firewall.xml.in +++ b/interface-definitions/firewall.xml.in @@ -126,7 +126,7 @@ Domain address to match - [a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,99}?(\/.*)? + @@ -408,6 +408,7 @@ #include + #include #include #include #include @@ -419,6 +420,7 @@ #include + #include #include #include #include @@ -572,6 +574,7 @@ #include + #include #include #include #include @@ -583,6 +586,7 @@ #include + #include #include #include #include @@ -656,6 +660,25 @@ disable + + + Retains last successful value if domain resolution fails + + + + + + Domain resolver update interval + + u32:10-3600 + Interval (seconds) + + + + + + 300 + Policy for sending IPv4 ICMP redirect messages diff --git a/interface-definitions/include/firewall/fqdn.xml.i b/interface-definitions/include/firewall/fqdn.xml.i new file mode 100644 index 000000000..9eb3925b5 --- /dev/null +++ b/interface-definitions/include/firewall/fqdn.xml.i @@ -0,0 +1,14 @@ + + + + Fully qualified domain name + + <fqdn> + Fully qualified domain name + + + + + + + diff --git a/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i b/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i index c2cc7edb3..2a42d236c 100644 --- a/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i +++ b/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i @@ -12,6 +12,14 @@ + + + Group of domains + + firewall group domain-group + + + #include diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index 4075e55b0..db4878c9d 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -20,6 +20,9 @@ import os import re from pathlib import Path +from socket import AF_INET +from socket import AF_INET6 +from socket import getaddrinfo from time import strftime from vyos.remote import download @@ -31,65 +34,29 @@ from vyos.util import dict_search_args from vyos.util import dict_search_recursive from vyos.util import run +def fqdn_config_parse(firewall): + firewall['ip_fqdn'] = {} + firewall['ip6_fqdn'] = {} + + for domain, path in dict_search_recursive(firewall, 'fqdn'): + fw_name = path[1] # name/ipv6-name + rule = path[3] # rule id + suffix = path[4][0] # source/destination (1 char) + set_name = f'{fw_name}_{rule}_{suffix}' + + if path[0] == 'name': + firewall['ip_fqdn'][set_name] = domain + elif path[0] == 'ipv6_name': + firewall['ip6_fqdn'][set_name] = domain + +def fqdn_resolve(fqdn, ipv6=False): + try: + res = getaddrinfo(fqdn, None, AF_INET6 if ipv6 else AF_INET) + return set(item[4][0] for item in res) + except: + return None -# Functions for firewall group domain-groups -def get_ips_domains_dict(list_domains): - """ - Get list of IPv4 addresses by list of domains - Ex: get_ips_domains_dict(['ex1.com', 'ex2.com']) - {'ex1.com': ['192.0.2.1'], 'ex2.com': ['192.0.2.2', '192.0.2.3']} - """ - from socket import gethostbyname_ex - from socket import gaierror - - ip_dict = {} - for domain in list_domains: - try: - _, _, ips = gethostbyname_ex(domain) - ip_dict[domain] = ips - except gaierror: - pass - - return ip_dict - -def nft_init_set(group_name, table="vyos_filter", family="ip"): - """ - table ip vyos_filter { - set GROUP_NAME - type ipv4_addr - flags interval - } - """ - return call(f'nft add set ip {table} {group_name} {{ type ipv4_addr\\; flags interval\\; }}') - - -def nft_add_set_elements(group_name, elements, table="vyos_filter", family="ip"): - """ - table ip vyos_filter { - set GROUP_NAME { - type ipv4_addr - flags interval - elements = { 192.0.2.1, 192.0.2.2 } - } - """ - elements = ", ".join(elements) - return call(f'nft add element {family} {table} {group_name} {{ {elements} }} ') - -def nft_flush_set(group_name, table="vyos_filter", family="ip"): - """ - Flush elements of nft set - """ - return call(f'nft flush set {family} {table} {group_name}') - -def nft_update_set_elements(group_name, elements, table="vyos_filter", family="ip"): - """ - Update elements of nft set - """ - flush_set = nft_flush_set(group_name, table="vyos_filter", family="ip") - nft_add_set = nft_add_set_elements(group_name, elements, table="vyos_filter", family="ip") - return flush_set, nft_add_set - -# END firewall group domain-group (sets) +# End Domain Resolver def find_nftables_rule(table, chain, rule_matches=[]): # Find rule in table/chain that matches all criteria and return the handle @@ -151,6 +118,13 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): suffix = f'!= {suffix[1:]}' output.append(f'{ip_name} {prefix}addr {suffix}') + if 'fqdn' in side_conf: + fqdn = side_conf['fqdn'] + operator = '' + if fqdn[0] == '!': + operator = '!=' + output.append(f'{ip_name} {prefix}addr {operator} @FQDN_{fw_name}_{rule_id}_{prefix}') + if dict_search_args(side_conf, 'geoip', 'country_code'): operator = '' if dict_search_args(side_conf, 'geoip', 'inverse_match') != None: diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index 821925bcd..e172e086d 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -17,11 +17,13 @@ import unittest from glob import glob +from time import sleep from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.util import cmd +from vyos.util import run sysfs_config = { 'all_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_all', 'default': '0', 'test_value': 'disable'}, @@ -76,6 +78,17 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): break self.assertTrue(not matched if inverse else matched, msg=search) + def wait_for_domain_resolver(self, table, set_name, element, max_wait=10): + # Resolver no longer blocks commit, need to wait for daemon to populate set + count = 0 + while count < max_wait: + code = run(f'sudo nft get element {table} {set_name} {{ {element} }}') + if code == 0: + return True + count += 1 + sleep(1) + return False + def test_geoip(self): self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'action', 'drop']) self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'source', 'geoip', 'country-code', 'se']) @@ -125,6 +138,9 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'interface', 'eth0', 'in', 'name', 'smoketest']) self.cli_commit() + + self.wait_for_domain_resolver('ip vyos_filter', 'D_smoketest_domain', '192.0.2.5') + nftables_search = [ ['iifname "eth0"', 'jump NAME_smoketest'], ['ip saddr @N_smoketest_network', 'ip daddr 172.16.10.10', 'th dport @P_smoketest_port', 'return'], diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index cbd9cbe90..2bb765e65 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -27,12 +27,8 @@ from vyos.configdict import dict_merge from vyos.configdict import node_changed from vyos.configdiff import get_config_diff, Diff # from vyos.configverify import verify_interface_exists +from vyos.firewall import fqdn_config_parse from vyos.firewall import geoip_update -from vyos.firewall import get_ips_domains_dict -from vyos.firewall import nft_add_set_elements -from vyos.firewall import nft_flush_set -from vyos.firewall import nft_init_set -from vyos.firewall import nft_update_set_elements from vyos.template import render from vyos.util import call from vyos.util import cmd @@ -173,6 +169,8 @@ def get_config(config=None): firewall['geoip_updated'] = geoip_updated(conf, firewall) + fqdn_config_parse(firewall) + return firewall def verify_rule(firewall, rule_conf, ipv6): @@ -232,29 +230,28 @@ def verify_rule(firewall, rule_conf, ipv6): if side in rule_conf: side_conf = rule_conf[side] - if dict_search_args(side_conf, 'geoip', 'country_code'): - if 'address' in side_conf: - raise ConfigError('Address and GeoIP cannot both be defined') - - if dict_search_args(side_conf, 'group', 'address_group'): - raise ConfigError('Address-group and GeoIP cannot both be defined') - - if dict_search_args(side_conf, 'group', 'network_group'): - raise ConfigError('Network-group and GeoIP cannot both be defined') + if len({'address', 'fqdn', 'geoip'} & set(side_conf)) > 1: + raise ConfigError('Only one of address, fqdn or geoip can be specified') if 'group' in side_conf: - if {'address_group', 'network_group'} <= set(side_conf['group']): - raise ConfigError('Only one address-group or network-group can be specified') + if len({'address_group', 'network_group', 'domain_group'} & set(side_conf['group'])) > 1: + raise ConfigError('Only one address-group, network-group or domain-group can be specified') for group in valid_groups: if group in side_conf['group']: group_name = side_conf['group'][group] + fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group + error_group = fw_group.replace("_", "-") + + if group in ['address_group', 'network_group', 'domain_group']: + types = [t for t in ['address', 'fqdn', 'geoip'] if t in side_conf] + if types: + raise ConfigError(f'{error_group} and {types[0]} cannot both be defined') + if group_name and group_name[0] == '!': group_name = group_name[1:] - fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group - error_group = fw_group.replace("_", "-") group_obj = dict_search_args(firewall, 'group', fw_group, group_name) if group_obj is None: @@ -477,26 +474,13 @@ def apply(firewall): if install_result == 1: raise ConfigError(f'Failed to apply firewall: {output}') - # set firewall group domain-group xxx - if 'group' in firewall: - if 'domain_group' in firewall['group']: - # T970 Enable a resolver (systemd daemon) that checks - # domain-group addresses and update entries for domains by timeout - # If router loaded without internet connection or for synchronization - call('systemctl restart vyos-domain-group-resolve.service') - for group, group_config in firewall['group']['domain_group'].items(): - domains = [] - if group_config.get('address') is not None: - for address in group_config.get('address'): - domains.append(address) - # Add elements to domain-group, try to resolve domain => ip - # and add elements to nft set - ip_dict = get_ips_domains_dict(domains) - elements = sum(ip_dict.values(), []) - nft_init_set(f'D_{group}') - nft_add_set_elements(f'D_{group}', elements) - else: - call('systemctl stop vyos-domain-group-resolve.service') + # T970 Enable a resolver (systemd daemon) that checks + # domain-group addresses and update entries for domains by timeout + # If router loaded without internet connection or for synchronization + domain_action = 'stop' + if dict_search_args(firewall, 'group', 'domain_group') or firewall['ip_fqdn'] or firewall['ip6_fqdn']: + domain_action = 'restart' + call(f'systemctl {domain_action} vyos-domain-resolver.service') apply_sysfs(firewall) diff --git a/src/helpers/vyos-domain-group-resolve.py b/src/helpers/vyos-domain-group-resolve.py deleted file mode 100755 index 6b677670b..000000000 --- a/src/helpers/vyos-domain-group-resolve.py +++ /dev/null @@ -1,60 +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 time - -from vyos.configquery import ConfigTreeQuery -from vyos.firewall import get_ips_domains_dict -from vyos.firewall import nft_add_set_elements -from vyos.firewall import nft_flush_set -from vyos.firewall import nft_init_set -from vyos.firewall import nft_update_set_elements -from vyos.util import call - - -base = ['firewall', 'group', 'domain-group'] -check_required = True -# count_failed = 0 -# Timeout in sec between checks -timeout = 300 - -domain_state = {} - -if __name__ == '__main__': - - while check_required: - config = ConfigTreeQuery() - if config.exists(base): - domain_groups = config.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - for set_name, domain_config in domain_groups.items(): - list_domains = domain_config['address'] - elements = [] - ip_dict = get_ips_domains_dict(list_domains) - - for domain in list_domains: - # Resolution succeeded, update domain state - if domain in ip_dict: - domain_state[domain] = ip_dict[domain] - elements += ip_dict[domain] - # Resolution failed, use previous domain state - elif domain in domain_state: - elements += domain_state[domain] - - # Resolve successful - if elements: - nft_update_set_elements(f'D_{set_name}', elements) - time.sleep(timeout) diff --git a/src/helpers/vyos-domain-resolver.py b/src/helpers/vyos-domain-resolver.py new file mode 100755 index 000000000..2f71f15db --- /dev/null +++ b/src/helpers/vyos-domain-resolver.py @@ -0,0 +1,182 @@ +#!/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 json +import os +import time + +from vyos.configdict import dict_merge +from vyos.configquery import ConfigTreeQuery +from vyos.firewall import fqdn_config_parse +from vyos.firewall import fqdn_resolve +from vyos.util import cmd +from vyos.util import commit_in_progress +from vyos.util import dict_search_args +from vyos.util import run +from vyos.xml import defaults + +base = ['firewall'] +timeout = 300 +cache = False + +domain_state = {} + +ipv4_tables = { + 'ip mangle', + 'ip vyos_filter', +} + +ipv6_tables = { + 'ip6 mangle', + 'ip6 vyos_filter' +} + +def get_config(conf): + firewall = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) + + default_values = defaults(base) + for tmp in ['name', 'ipv6_name']: + if tmp in default_values: + del default_values[tmp] + + if 'zone' in default_values: + del default_values['zone'] + + firewall = dict_merge(default_values, firewall) + + global timeout, cache + + if 'resolver_interval' in firewall: + timeout = int(firewall['resolver_interval']) + + if 'resolver_cache' in firewall: + cache = True + + fqdn_config_parse(firewall) + + return firewall + +def resolve(domains, ipv6=False): + global domain_state + + ip_list = set() + + for domain in domains: + resolved = fqdn_resolve(domain, ipv6=ipv6) + + if resolved and cache: + domain_state[domain] = resolved + elif not resolved: + if domain not in domain_state: + continue + resolved = domain_state[domain] + + ip_list = ip_list | resolved + return ip_list + +def nft_output(table, set_name, ip_list): + output = [f'flush set {table} {set_name}'] + if ip_list: + ip_str = ','.join(ip_list) + output.append(f'add element {table} {set_name} {{ {ip_str} }}') + return output + +def nft_valid_sets(): + try: + valid_sets = [] + sets_json = cmd('nft -j list sets') + sets_obj = json.loads(sets_json) + + for obj in sets_obj['nftables']: + if 'set' in obj: + family = obj['set']['family'] + table = obj['set']['table'] + name = obj['set']['name'] + valid_sets.append((f'{family} {table}', name)) + + return valid_sets + except: + return [] + +def update(firewall): + conf_lines = [] + count = 0 + + valid_sets = nft_valid_sets() + + domain_groups = dict_search_args(firewall, 'group', 'domain_group') + if domain_groups: + for set_name, domain_config in domain_groups.items(): + if 'address' not in domain_config: + continue + + nft_set_name = f'D_{set_name}' + domains = domain_config['address'] + + ip_list = resolve(domains, ipv6=False) + for table in ipv4_tables: + if (table, nft_set_name) in valid_sets: + conf_lines += nft_output(table, nft_set_name, ip_list) + + ip6_list = resolve(domains, ipv6=True) + for table in ipv6_tables: + if (table, nft_set_name) in valid_sets: + conf_lines += nft_output(table, nft_set_name, ip6_list) + count += 1 + + for set_name, domain in firewall['ip_fqdn'].items(): + table = 'ip vyos_filter' + nft_set_name = f'FQDN_{set_name}' + + ip_list = resolve([domain], ipv6=False) + + if (table, nft_set_name) in valid_sets: + conf_lines += nft_output(table, nft_set_name, ip_list) + count += 1 + + for set_name, domain in firewall['ip6_fqdn'].items(): + table = 'ip6 vyos_filter' + nft_set_name = f'FQDN_{set_name}' + + ip_list = resolve([domain], ipv6=True) + if (table, nft_set_name) in valid_sets: + conf_lines += nft_output(table, nft_set_name, ip_list) + count += 1 + + nft_conf_str = "\n".join(conf_lines) + "\n" + code = run(f'nft -f -', input=nft_conf_str) + + print(f'Updated {count} sets - result: {code}') + +if __name__ == '__main__': + print(f'VyOS domain resolver') + + count = 1 + while commit_in_progress(): + if ( count % 60 == 0 ): + print(f'Commit still in progress after {count}s - waiting') + count += 1 + time.sleep(1) + + conf = ConfigTreeQuery() + firewall = get_config(conf) + + print(f'interval: {timeout}s - cache: {cache}') + + while True: + update(firewall) + time.sleep(timeout) diff --git a/src/systemd/vyos-domain-group-resolve.service b/src/systemd/vyos-domain-group-resolve.service deleted file mode 100644 index 29628fddb..000000000 --- a/src/systemd/vyos-domain-group-resolve.service +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=VyOS firewall domain-group resolver -After=vyos-router.service - -[Service] -Type=simple -Restart=always -ExecStart=/usr/bin/python3 /usr/libexec/vyos/vyos-domain-group-resolve.py - -[Install] -WantedBy=multi-user.target diff --git a/src/systemd/vyos-domain-resolver.service b/src/systemd/vyos-domain-resolver.service new file mode 100644 index 000000000..c56b51f0c --- /dev/null +++ b/src/systemd/vyos-domain-resolver.service @@ -0,0 +1,13 @@ +[Unit] +Description=VyOS firewall domain resolver +After=vyos-router.service + +[Service] +Type=simple +Restart=always +ExecStart=/usr/bin/python3 -u /usr/libexec/vyos/vyos-domain-resolver.py +StandardError=journal +StandardOutput=journal + +[Install] +WantedBy=multi-user.target -- cgit v1.2.3 From b4b491d424fba6f3d417135adc1865e338a480a1 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Mon, 31 Oct 2022 21:08:42 +0100 Subject: nat: T1877: T970: Add firewall groups to NAT --- data/templates/firewall/nftables-nat.j2 | 4 ++ interface-definitions/include/nat-rule.xml.i | 2 + python/vyos/firewall.py | 2 + python/vyos/nat.py | 56 +++++++++++++++++++-- smoketest/scripts/cli/test_nat.py | 35 +++++++++++++ src/conf_mode/firewall.py | 22 ++++++--- src/conf_mode/nat.py | 73 +++++++++++++++++++++++----- src/helpers/vyos-domain-resolver.py | 1 + 8 files changed, 174 insertions(+), 21 deletions(-) (limited to 'interface-definitions') diff --git a/data/templates/firewall/nftables-nat.j2 b/data/templates/firewall/nftables-nat.j2 index c5c0a2c86..f0be3cf5d 100644 --- a/data/templates/firewall/nftables-nat.j2 +++ b/data/templates/firewall/nftables-nat.j2 @@ -1,5 +1,7 @@ #!/usr/sbin/nft -f +{% import 'firewall/nftables-defines.j2' as group_tmpl %} + {% if helper_functions is vyos_defined('remove') %} {# NAT if going to be disabled - remove rules and targets from nftables #} {% set base_command = 'delete rule ip raw' %} @@ -59,5 +61,7 @@ table ip vyos_nat { chain VYOS_PRE_SNAT_HOOK { return } + +{{ group_tmpl.groups(firewall_group, False) }} } {% endif %} diff --git a/interface-definitions/include/nat-rule.xml.i b/interface-definitions/include/nat-rule.xml.i index 84941aa6a..8f2029388 100644 --- a/interface-definitions/include/nat-rule.xml.i +++ b/interface-definitions/include/nat-rule.xml.i @@ -20,6 +20,7 @@ #include #include + #include #include @@ -285,6 +286,7 @@ #include #include + #include diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index db4878c9d..59ec4948f 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -34,6 +34,8 @@ from vyos.util import dict_search_args from vyos.util import dict_search_recursive from vyos.util import run +# Domain Resolver + def fqdn_config_parse(firewall): firewall['ip_fqdn'] = {} firewall['ip6_fqdn'] = {} diff --git a/python/vyos/nat.py b/python/vyos/nat.py index 31bbdc386..3d01829a7 100644 --- a/python/vyos/nat.py +++ b/python/vyos/nat.py @@ -85,8 +85,13 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False): translation_str += f' {",".join(options)}' for target in ['source', 'destination']: + if target not in rule_conf: + continue + + side_conf = rule_conf[target] prefix = target[:1] - addr = dict_search_args(rule_conf, target, 'address') + + addr = dict_search_args(side_conf, 'address') if addr and not (ignore_type_addr and target == nat_type): operator = '' if addr[:1] == '!': @@ -94,7 +99,7 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False): addr = addr[1:] output.append(f'{ip_prefix} {prefix}addr {operator} {addr}') - addr_prefix = dict_search_args(rule_conf, target, 'prefix') + addr_prefix = dict_search_args(side_conf, 'prefix') if addr_prefix and ipv6: operator = '' if addr_prefix[:1] == '!': @@ -102,7 +107,7 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False): addr_prefix = addr[1:] output.append(f'ip6 {prefix}addr {operator} {addr_prefix}') - port = dict_search_args(rule_conf, target, 'port') + port = dict_search_args(side_conf, 'port') if port: protocol = rule_conf['protocol'] if protocol == 'tcp_udp': @@ -113,6 +118,51 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False): port = port[1:] output.append(f'{protocol} {prefix}port {operator} {{ {port} }}') + if 'group' in side_conf: + group = side_conf['group'] + if 'address_group' in group and not (ignore_type_addr and target == nat_type): + group_name = group['address_group'] + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + output.append(f'{ip_prefix} {prefix}addr {operator} @A_{group_name}') + # Generate firewall group domain-group + elif 'domain_group' in group and not (ignore_type_addr and target == nat_type): + group_name = group['domain_group'] + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + output.append(f'{ip_prefix} {prefix}addr {operator} @D_{group_name}') + elif 'network_group' in group and not (ignore_type_addr and target == nat_type): + group_name = group['network_group'] + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + output.append(f'{ip_prefix} {prefix}addr {operator} @N_{group_name}') + if 'mac_group' in group: + group_name = group['mac_group'] + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + output.append(f'ether {prefix}addr {operator} @M_{group_name}') + if 'port_group' in group: + proto = rule_conf['protocol'] + group_name = group['port_group'] + + if proto == 'tcp_udp': + proto = 'th' + + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + + output.append(f'{proto} {prefix}port {operator} @P_{group_name}') + output.append('counter') if 'log' in rule_conf: diff --git a/smoketest/scripts/cli/test_nat.py b/smoketest/scripts/cli/test_nat.py index 2ae90fcaf..9f4e3b831 100755 --- a/smoketest/scripts/cli/test_nat.py +++ b/smoketest/scripts/cli/test_nat.py @@ -58,6 +58,17 @@ class TestNAT(VyOSUnitTestSHIM.TestCase): break self.assertTrue(not matched if inverse else matched, msg=search) + def wait_for_domain_resolver(self, table, set_name, element, max_wait=10): + # Resolver no longer blocks commit, need to wait for daemon to populate set + count = 0 + while count < max_wait: + code = run(f'sudo nft get element {table} {set_name} {{ {element} }}') + if code == 0: + return True + count += 1 + sleep(1) + return False + def test_snat(self): rules = ['100', '110', '120', '130', '200', '210', '220', '230'] outbound_iface_100 = 'eth0' @@ -84,6 +95,30 @@ class TestNAT(VyOSUnitTestSHIM.TestCase): self.verify_nftables(nftables_search, 'ip vyos_nat') + def test_snat_groups(self): + address_group = 'smoketest_addr' + address_group_member = '192.0.2.1' + rule = '100' + outbound_iface = 'eth0' + + self.cli_set(['firewall', 'group', 'address-group', address_group, 'address', address_group_member]) + + self.cli_set(src_path + ['rule', rule, 'source', 'group', 'address-group', address_group]) + self.cli_set(src_path + ['rule', rule, 'outbound-interface', outbound_iface]) + self.cli_set(src_path + ['rule', rule, 'translation', 'address', 'masquerade']) + + self.cli_commit() + + nftables_search = [ + [f'set A_{address_group}'], + [f'elements = {{ {address_group_member} }}'], + [f'ip saddr @A_{address_group}', f'oifname "{outbound_iface}"', 'masquerade'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_nat') + + self.cli_delete(['firewall']) + def test_dnat(self): rules = ['100', '110', '120', '130', '200', '210', '220', '230'] inbound_iface_100 = 'eth0' diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 2bb765e65..783adec46 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -41,6 +41,7 @@ from vyos import ConfigError from vyos import airbag airbag.enable() +nat_conf_script = '/usr/libexec/vyos/conf_mode/nat.py' policy_route_conf_script = '/usr/libexec/vyos/conf_mode/policy-route.py' nftables_conf = '/run/nftables.conf' @@ -158,7 +159,7 @@ def get_config(config=None): for zone in firewall['zone']: firewall['zone'][zone] = dict_merge(default_values, firewall['zone'][zone]) - firewall['policy_resync'] = bool('group' in firewall or node_changed(conf, base + ['group'])) + firewall['group_resync'] = bool('group' in firewall or node_changed(conf, base + ['group'])) if 'config_trap' in firewall and firewall['config_trap'] == 'enable': diff = get_config_diff(conf) @@ -463,6 +464,12 @@ def post_apply_trap(firewall): cmd(base_cmd + ' '.join(objects)) +def resync_nat(): + # Update nat as firewall groups were updated + tmp, out = rc_cmd(nat_conf_script) + if tmp > 0: + Warning(f'Failed to re-apply nat configuration! {out}') + def resync_policy_route(): # Update policy route as firewall groups were updated tmp, out = rc_cmd(policy_route_conf_script) @@ -474,19 +481,20 @@ def apply(firewall): if install_result == 1: raise ConfigError(f'Failed to apply firewall: {output}') + apply_sysfs(firewall) + + if firewall['group_resync']: + resync_nat() + resync_policy_route() + # T970 Enable a resolver (systemd daemon) that checks - # domain-group addresses and update entries for domains by timeout + # domain-group/fqdn addresses and update entries for domains by timeout # If router loaded without internet connection or for synchronization domain_action = 'stop' if dict_search_args(firewall, 'group', 'domain_group') or firewall['ip_fqdn'] or firewall['ip6_fqdn']: domain_action = 'restart' call(f'systemctl {domain_action} vyos-domain-resolver.service') - apply_sysfs(firewall) - - if firewall['policy_resync']: - resync_policy_route() - if firewall['geoip_updated']: # Call helper script to Update set contents if 'name' in firewall['geoip_updated'] or 'ipv6_name' in firewall['geoip_updated']: diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 978c043e9..9f8221514 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -32,6 +32,7 @@ from vyos.util import cmd from vyos.util import run from vyos.util import check_kmod from vyos.util import dict_search +from vyos.util import dict_search_args from vyos.validate import is_addr_assigned from vyos.xml import defaults from vyos import ConfigError @@ -47,6 +48,13 @@ else: nftables_nat_config = '/run/nftables_nat.conf' nftables_static_nat_conf = '/run/nftables_static-nat-rules.nft' +valid_groups = [ + 'address_group', + 'domain_group', + 'network_group', + 'port_group' +] + def get_handler(json, chain, target): """ Get nftable rule handler number of given chain/target combination. Handler is required when adding NAT/Conntrack helper targets """ @@ -60,7 +68,7 @@ def get_handler(json, chain, target): return None -def verify_rule(config, err_msg): +def verify_rule(config, err_msg, groups_dict): """ Common verify steps used for both source and destination NAT """ if (dict_search('translation.port', config) != None or @@ -78,6 +86,45 @@ def verify_rule(config, err_msg): 'statically maps a whole network of addresses onto another\n' \ 'network of addresses') + for side in ['destination', 'source']: + if side in config: + side_conf = config[side] + + if len({'address', 'fqdn'} & set(side_conf)) > 1: + raise ConfigError('Only one of address, fqdn or geoip can be specified') + + if 'group' in side_conf: + if len({'address_group', 'network_group', 'domain_group'} & set(side_conf['group'])) > 1: + raise ConfigError('Only one address-group, network-group or domain-group can be specified') + + for group in valid_groups: + if group in side_conf['group']: + group_name = side_conf['group'][group] + error_group = group.replace("_", "-") + + if group in ['address_group', 'network_group', 'domain_group']: + types = [t for t in ['address', 'fqdn'] if t in side_conf] + if types: + raise ConfigError(f'{error_group} and {types[0]} cannot both be defined') + + if group_name and group_name[0] == '!': + group_name = group_name[1:] + + group_obj = dict_search_args(groups_dict, group, group_name) + + if group_obj is None: + raise ConfigError(f'Invalid {error_group} "{group_name}" on firewall rule') + + if not group_obj: + Warning(f'{error_group} "{group_name}" has no members!') + + if dict_search_args(side_conf, 'group', 'port_group'): + if 'protocol' not in config: + raise ConfigError('Protocol must be defined if specifying a port-group') + + if config['protocol'] not in ['tcp', 'udp', 'tcp_udp']: + raise ConfigError('Protocol must be tcp, udp, or tcp_udp when specifying a port-group') + def get_config(config=None): if config: conf = config @@ -105,16 +152,20 @@ def get_config(config=None): condensed_json = jmespath.search(pattern, nftable_json) if not conf.exists(base): - nat['helper_functions'] = 'remove' - - # Retrieve current table handler positions - nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_HELPER') - nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK') - nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_HELPER') - nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'NAT_CONNTRACK') + if get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_HELPER'): + nat['helper_functions'] = 'remove' + + # Retrieve current table handler positions + nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_HELPER') + nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK') + nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_HELPER') + nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'NAT_CONNTRACK') nat['deleted'] = '' return nat + nat['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) + # check if NAT connection tracking helpers need to be set up - this has to # be done only once if not get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK'): @@ -157,7 +208,7 @@ def verify(nat): Warning(f'IP address {ip} does not exist on the system!') # common rule verification - verify_rule(config, err_msg) + verify_rule(config, err_msg, nat['firewall_group']) if dict_search('destination.rule', nat): @@ -175,7 +226,7 @@ def verify(nat): raise ConfigError(f'{err_msg} translation requires address and/or port') # common rule verification - verify_rule(config, err_msg) + verify_rule(config, err_msg, nat['firewall_group']) if dict_search('static.rule', nat): for rule, config in dict_search('static.rule', nat).items(): @@ -186,7 +237,7 @@ def verify(nat): 'inbound-interface not specified') # common rule verification - verify_rule(config, err_msg) + verify_rule(config, err_msg, nat['firewall_group']) return None diff --git a/src/helpers/vyos-domain-resolver.py b/src/helpers/vyos-domain-resolver.py index 2f71f15db..035c208b2 100755 --- a/src/helpers/vyos-domain-resolver.py +++ b/src/helpers/vyos-domain-resolver.py @@ -37,6 +37,7 @@ domain_state = {} ipv4_tables = { 'ip mangle', 'ip vyos_filter', + 'ip vyos_nat' } ipv6_tables = { -- cgit v1.2.3 From acf2c24f8eec0918ca419db68196c76dc827f197 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 5 Nov 2022 19:47:42 +0100 Subject: container: T4802: support per container shared-memory size configuration Size of /dev/shm within a container can be defined via --shm-size when invoking the container. Add corresponding CLI node. --- interface-definitions/container.xml.in | 20 +++++++++++++++++++- src/conf_mode/container.py | 5 +++-- 2 files changed, 22 insertions(+), 3 deletions(-) (limited to 'interface-definitions') diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in index 51171d881..f84c94a40 100644 --- a/interface-definitions/container.xml.in +++ b/interface-definitions/container.xml.in @@ -111,7 +111,7 @@ - Constrain the memory available to a container + Memory (RAM) available to this container u32:0 Unlimited @@ -127,6 +127,24 @@ 512 + + + Shared memory available to this container + + u32:0 + Unlimited + + + u32:1-8192 + Container memory in megabytes (MB) + + + + + Container memory must be in range 0 to 8192 MB + + 64 + Attach user defined network to container diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py index 70d149f0d..8efeaed54 100755 --- a/src/conf_mode/container.py +++ b/src/conf_mode/container.py @@ -207,6 +207,7 @@ def verify(container): def generate_run_arguments(name, container_config): image = container_config['image'] memory = container_config['memory'] + shared_memory = container_config['shared_memory'] restart = container_config['restart'] # Add capability options. Should be in uppercase @@ -254,9 +255,9 @@ def generate_run_arguments(name, container_config): volume += f' -v {svol}:{dvol}' container_base_cmd = f'--detach --interactive --tty --replace {cap_add} ' \ - f'--memory {memory}m --memory-swap 0 --restart {restart} ' \ + f'--memory {memory}m --shm-size {shared_memory}m --memory-swap 0 --restart {restart} ' \ f'--name {name} {device} {port} {volume} {env_opt}' - + if 'allow_host_networks' in container_config: return f'{container_base_cmd} --net host {image}' -- cgit v1.2.3 From 2f105b1b22de382927699d2f3a1ec6f00cb4ecbe Mon Sep 17 00:00:00 2001 From: Zen3515 <7106408+Zen3515@users.noreply.github.com> Date: Thu, 10 Nov 2022 16:23:48 +0700 Subject: dns: T738: add CLI option for PowerDNS local-port --- data/templates/dns-forwarding/recursor.conf.j2 | 3 +++ interface-definitions/dns-forwarding.xml.in | 4 ++++ .../scripts/cli/test_service_dns_forwarding.py | 21 +++++++++++++++++++++ 3 files changed, 28 insertions(+) (limited to 'interface-definitions') diff --git a/data/templates/dns-forwarding/recursor.conf.j2 b/data/templates/dns-forwarding/recursor.conf.j2 index ce1b676d1..e02e6c13d 100644 --- a/data/templates/dns-forwarding/recursor.conf.j2 +++ b/data/templates/dns-forwarding/recursor.conf.j2 @@ -29,6 +29,9 @@ export-etc-hosts={{ 'no' if ignore_hosts_file is vyos_defined else 'yes' }} # listen-address local-address={{ listen_address | join(',') }} +# listen-port +local-port={{ port }} + # dnssec dnssec={{ dnssec }} diff --git a/interface-definitions/dns-forwarding.xml.in b/interface-definitions/dns-forwarding.xml.in index 3de0dc0eb..409028572 100644 --- a/interface-definitions/dns-forwarding.xml.in +++ b/interface-definitions/dns-forwarding.xml.in @@ -605,6 +605,10 @@ #include + #include + + 53 + Maximum amount of time negative entries are cached diff --git a/smoketest/scripts/cli/test_service_dns_forwarding.py b/smoketest/scripts/cli/test_service_dns_forwarding.py index 65b676451..8e9b7ef43 100755 --- a/smoketest/scripts/cli/test_service_dns_forwarding.py +++ b/smoketest/scripts/cli/test_service_dns_forwarding.py @@ -32,6 +32,7 @@ base_path = ['service', 'dns', 'forwarding'] allow_from = ['192.0.2.0/24', '2001:db8::/32'] listen_adress = ['127.0.0.1', '::1'] +listen_ports = ['53', '5353'] def get_config_value(key, file=CONFIG_FILE): tmp = read_file(file) @@ -224,5 +225,25 @@ class TestServicePowerDNS(VyOSUnitTestSHIM.TestCase): tmp = get_config_value('dns64-prefix') self.assertEqual(tmp, dns_prefix) + def test_listening_port(self): + # only one port can be listen + for port in listen_ports: + self.cli_set(base_path + ['port', port]) + for network in allow_from: + self.cli_set(base_path + ['allow-from', network]) + for address in listen_adress: + self.cli_set(base_path + ['listen-address', address]) + + # commit changes + self.cli_commit() + + # verify local-port configuration + tmp = get_config_value('local-port') + self.assertEqual(tmp, port) + + # reset to test differnt port + self.cli_delete(base_path) + self.cli_commit() + if __name__ == '__main__': unittest.main(verbosity=2) -- cgit v1.2.3 From 586b24e0af1ae57c47c772229fc94ab50dfc1e4f Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Wed, 2 Nov 2022 15:32:11 +0100 Subject: policy: T2199: T4605: Migrate policy route interface to `policy route|route6 interface ` * Include refactor to policy route to allow for deletion of mangle table instead of complex cleanup * T4605: Rename mangle table to vyos_mangle --- data/templates/firewall/nftables-policy.j2 | 31 +++-- .../include/interface/interface-policy-vif-c.xml.i | 26 ---- .../include/interface/interface-policy-vif.xml.i | 26 ---- .../include/interface/interface-policy.xml.i | 26 ---- .../include/interface/vif-s.xml.i | 2 - interface-definitions/include/interface/vif.xml.i | 1 - .../include/version/policy-version.xml.i | 2 +- interface-definitions/interfaces-bonding.xml.in | 1 - interface-definitions/interfaces-bridge.xml.in | 1 - interface-definitions/interfaces-dummy.xml.in | 1 - interface-definitions/interfaces-ethernet.xml.in | 1 - interface-definitions/interfaces-geneve.xml.in | 1 - interface-definitions/interfaces-input.xml.in | 1 - interface-definitions/interfaces-l2tpv3.xml.in | 1 - interface-definitions/interfaces-macsec.xml.in | 1 - interface-definitions/interfaces-openvpn.xml.in | 1 - interface-definitions/interfaces-pppoe.xml.in | 1 - .../interfaces-pseudo-ethernet.xml.in | 1 - interface-definitions/interfaces-tunnel.xml.in | 1 - interface-definitions/interfaces-vti.xml.in | 1 - interface-definitions/interfaces-vxlan.xml.in | 1 - interface-definitions/interfaces-wireguard.xml.in | 1 - interface-definitions/interfaces-wireless.xml.in | 1 - interface-definitions/interfaces-wwan.xml.in | 1 - interface-definitions/policy-route.xml.in | 2 + smoketest/scripts/cli/test_policy_route.py | 58 +++++---- src/conf_mode/policy-route-interface.py | 132 --------------------- src/conf_mode/policy-route.py | 106 +---------------- src/helpers/vyos-domain-resolver.py | 4 +- src/migration-scripts/policy/4-to-5 | 92 ++++++++++++++ src/op_mode/policy_route.py | 42 +------ 31 files changed, 154 insertions(+), 413 deletions(-) delete mode 100644 interface-definitions/include/interface/interface-policy-vif-c.xml.i delete mode 100644 interface-definitions/include/interface/interface-policy-vif.xml.i delete mode 100644 interface-definitions/include/interface/interface-policy.xml.i delete mode 100755 src/conf_mode/policy-route-interface.py create mode 100755 src/migration-scripts/policy/4-to-5 (limited to 'interface-definitions') diff --git a/data/templates/firewall/nftables-policy.j2 b/data/templates/firewall/nftables-policy.j2 index 40118930b..6cb3b2f95 100644 --- a/data/templates/firewall/nftables-policy.j2 +++ b/data/templates/firewall/nftables-policy.j2 @@ -2,21 +2,24 @@ {% import 'firewall/nftables-defines.j2' as group_tmpl %} -{% if cleanup_commands is vyos_defined %} -{% for command in cleanup_commands %} -{{ command }} -{% endfor %} +{% if first_install is not vyos_defined %} +delete table ip vyos_mangle +delete table ip6 vyos_mangle {% endif %} - -table ip mangle { -{% if first_install is vyos_defined %} +table ip vyos_mangle { chain VYOS_PBR_PREROUTING { type filter hook prerouting priority -150; policy accept; +{% if route is vyos_defined %} +{% for route_text, conf in route.items() if conf.interface is vyos_defined %} + iifname { {{ ",".join(conf.interface) }} } counter jump VYOS_PBR_{{ route_text }} +{% endfor %} +{% endif %} } + chain VYOS_PBR_POSTROUTING { type filter hook postrouting priority -150; policy accept; } -{% endif %} + {% if route is vyos_defined %} {% for route_text, conf in route.items() %} chain VYOS_PBR_{{ route_text }} { @@ -32,15 +35,20 @@ table ip mangle { {{ group_tmpl.groups(firewall_group, False) }} } -table ip6 mangle { -{% if first_install is vyos_defined %} +table ip6 vyos_mangle { chain VYOS_PBR6_PREROUTING { type filter hook prerouting priority -150; policy accept; +{% if route6 is vyos_defined %} +{% for route_text, conf in route6.items() if conf.interface is vyos_defined %} + iifname { {{ ",".join(conf.interface) }} } counter jump VYOS_PBR6_{{ route_text }} +{% endfor %} +{% endif %} } + chain VYOS_PBR6_POSTROUTING { type filter hook postrouting priority -150; policy accept; } -{% endif %} + {% if route6 is vyos_defined %} {% for route_text, conf in route6.items() %} chain VYOS_PBR6_{{ route_text }} { @@ -52,5 +60,6 @@ table ip6 mangle { } {% endfor %} {% endif %} + {{ group_tmpl.groups(firewall_group, True) }} } diff --git a/interface-definitions/include/interface/interface-policy-vif-c.xml.i b/interface-definitions/include/interface/interface-policy-vif-c.xml.i deleted file mode 100644 index 866fcd5c0..000000000 --- a/interface-definitions/include/interface/interface-policy-vif-c.xml.i +++ /dev/null @@ -1,26 +0,0 @@ - - - - 620 - Policy route options - - - - - IPv4 policy route ruleset for interface - - policy route - - - - - - IPv6 policy route ruleset for interface - - policy route6 - - - - - - diff --git a/interface-definitions/include/interface/interface-policy-vif.xml.i b/interface-definitions/include/interface/interface-policy-vif.xml.i deleted file mode 100644 index 83510fe59..000000000 --- a/interface-definitions/include/interface/interface-policy-vif.xml.i +++ /dev/null @@ -1,26 +0,0 @@ - - - - 620 - Policy route options - - - - - IPv4 policy route ruleset for interface - - policy route - - - - - - IPv6 policy route ruleset for interface - - policy route6 - - - - - - diff --git a/interface-definitions/include/interface/interface-policy.xml.i b/interface-definitions/include/interface/interface-policy.xml.i deleted file mode 100644 index 42a8fd009..000000000 --- a/interface-definitions/include/interface/interface-policy.xml.i +++ /dev/null @@ -1,26 +0,0 @@ - - - - 620 - Policy route options - - - - - IPv4 policy route ruleset for interface - - policy route - - - - - - IPv6 policy route ruleset for interface - - policy route6 - - - - - - diff --git a/interface-definitions/include/interface/vif-s.xml.i b/interface-definitions/include/interface/vif-s.xml.i index 916349ade..6d50d7238 100644 --- a/interface-definitions/include/interface/vif-s.xml.i +++ b/interface-definitions/include/interface/vif-s.xml.i @@ -18,7 +18,6 @@ #include #include #include - #include Protocol used for service VLAN (default: 802.1ad) @@ -67,7 +66,6 @@ #include #include #include - #include #include diff --git a/interface-definitions/include/interface/vif.xml.i b/interface-definitions/include/interface/vif.xml.i index 73a8c98ff..3f8f113ea 100644 --- a/interface-definitions/include/interface/vif.xml.i +++ b/interface-definitions/include/interface/vif.xml.i @@ -18,7 +18,6 @@ #include #include #include - #include VLAN egress QoS diff --git a/interface-definitions/include/version/policy-version.xml.i b/interface-definitions/include/version/policy-version.xml.i index 89bde20c7..f1494eaa3 100644 --- a/interface-definitions/include/version/policy-version.xml.i +++ b/interface-definitions/include/version/policy-version.xml.i @@ -1,3 +1,3 @@ - + diff --git a/interface-definitions/interfaces-bonding.xml.in b/interface-definitions/interfaces-bonding.xml.in index 41e4a68a8..96e0e5d89 100644 --- a/interface-definitions/interfaces-bonding.xml.in +++ b/interface-definitions/interfaces-bonding.xml.in @@ -56,7 +56,6 @@ #include #include #include - #include Bonding transmit hash policy diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in index d633077d9..d52e213b6 100644 --- a/interface-definitions/interfaces-bridge.xml.in +++ b/interface-definitions/interfaces-bridge.xml.in @@ -41,7 +41,6 @@ #include #include #include - #include Forwarding delay diff --git a/interface-definitions/interfaces-dummy.xml.in b/interface-definitions/interfaces-dummy.xml.in index fb36741f7..eb525b547 100644 --- a/interface-definitions/interfaces-dummy.xml.in +++ b/interface-definitions/interfaces-dummy.xml.in @@ -19,7 +19,6 @@ #include #include #include - #include IPv4 routing parameters diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in index 77f130e1c..e9ae0acfe 100644 --- a/interface-definitions/interfaces-ethernet.xml.in +++ b/interface-definitions/interfaces-ethernet.xml.in @@ -31,7 +31,6 @@ #include #include - #include Duplex mode diff --git a/interface-definitions/interfaces-geneve.xml.in b/interface-definitions/interfaces-geneve.xml.in index b959c787d..f8e9909f8 100644 --- a/interface-definitions/interfaces-geneve.xml.in +++ b/interface-definitions/interfaces-geneve.xml.in @@ -23,7 +23,6 @@ #include #include #include - #include GENEVE tunnel parameters diff --git a/interface-definitions/interfaces-input.xml.in b/interface-definitions/interfaces-input.xml.in index d01c760f8..97502d954 100644 --- a/interface-definitions/interfaces-input.xml.in +++ b/interface-definitions/interfaces-input.xml.in @@ -19,7 +19,6 @@ #include #include - #include #include diff --git a/interface-definitions/interfaces-l2tpv3.xml.in b/interface-definitions/interfaces-l2tpv3.xml.in index bde68dd5a..0ebc3253d 100644 --- a/interface-definitions/interfaces-l2tpv3.xml.in +++ b/interface-definitions/interfaces-l2tpv3.xml.in @@ -32,7 +32,6 @@ 5000 #include - #include Encapsulation type diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in index 5c9f4cd76..441236ec2 100644 --- a/interface-definitions/interfaces-macsec.xml.in +++ b/interface-definitions/interfaces-macsec.xml.in @@ -21,7 +21,6 @@ #include #include #include - #include #include diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in index 3876e31da..7cfb9ee7a 100644 --- a/interface-definitions/interfaces-openvpn.xml.in +++ b/interface-definitions/interfaces-openvpn.xml.in @@ -34,7 +34,6 @@ #include - #include OpenVPN interface device-type diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in index 84f76a7ee..719060fa9 100644 --- a/interface-definitions/interfaces-pppoe.xml.in +++ b/interface-definitions/interfaces-pppoe.xml.in @@ -19,7 +19,6 @@ #include #include #include - #include #include #include #include diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in index 4eb9bf111..2fe07ffd5 100644 --- a/interface-definitions/interfaces-pseudo-ethernet.xml.in +++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in @@ -28,7 +28,6 @@ #include #include #include - #include Receive mode (default: private) diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in index fe49d337a..333a5b178 100644 --- a/interface-definitions/interfaces-tunnel.xml.in +++ b/interface-definitions/interfaces-tunnel.xml.in @@ -29,7 +29,6 @@ #include #include #include - #include 6rd network prefix diff --git a/interface-definitions/interfaces-vti.xml.in b/interface-definitions/interfaces-vti.xml.in index eeaea0dc3..11f001dc0 100644 --- a/interface-definitions/interfaces-vti.xml.in +++ b/interface-definitions/interfaces-vti.xml.in @@ -25,7 +25,6 @@ #include #include #include - #include diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index 4902ff36d..331f930d3 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -54,7 +54,6 @@ #include #include #include - #include 1450 diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in index 23f50d146..35e223588 100644 --- a/interface-definitions/interfaces-wireguard.xml.in +++ b/interface-definitions/interfaces-wireguard.xml.in @@ -21,7 +21,6 @@ #include #include #include - #include #include 1420 diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in index 9e7fc29bc..5271df624 100644 --- a/interface-definitions/interfaces-wireless.xml.in +++ b/interface-definitions/interfaces-wireless.xml.in @@ -20,7 +20,6 @@ #include - #include HT and VHT capabilities for your card diff --git a/interface-definitions/interfaces-wwan.xml.in b/interface-definitions/interfaces-wwan.xml.in index b0b8367dc..758784540 100644 --- a/interface-definitions/interfaces-wwan.xml.in +++ b/interface-definitions/interfaces-wwan.xml.in @@ -39,7 +39,6 @@ #include #include #include - #include #include #include diff --git a/interface-definitions/policy-route.xml.in b/interface-definitions/policy-route.xml.in index 44b96c2e6..48a5bf7d1 100644 --- a/interface-definitions/policy-route.xml.in +++ b/interface-definitions/policy-route.xml.in @@ -12,6 +12,7 @@ #include + #include #include @@ -65,6 +66,7 @@ #include + #include #include diff --git a/smoketest/scripts/cli/test_policy_route.py b/smoketest/scripts/cli/test_policy_route.py index 046e385bb..11b3c678e 100755 --- a/smoketest/scripts/cli/test_policy_route.py +++ b/smoketest/scripts/cli/test_policy_route.py @@ -42,18 +42,25 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): super(TestPolicyRoute, cls).tearDownClass() def tearDown(self): - self.cli_delete(['interfaces', 'ethernet', interface, 'policy']) self.cli_delete(['policy', 'route']) self.cli_delete(['policy', 'route6']) self.cli_commit() + # Verify nftables cleanup nftables_search = [ ['set N_smoketest_network'], ['set N_smoketest_network1'], ['chain VYOS_PBR_smoketest'] ] - self.verify_nftables(nftables_search, 'ip mangle', inverse=True) + self.verify_nftables(nftables_search, 'ip vyos_mangle', inverse=True) + + # Verify ip rule cleanup + ip_rule_search = [ + ['fwmark ' + hex(table_mark_offset - int(table_id)), 'lookup ' + table_id] + ] + + self.verify_rules(ip_rule_search, inverse=True) def verify_nftables(self, nftables_search, table, inverse=False): nftables_output = cmd(f'sudo nft list table {table}') @@ -66,6 +73,17 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): break self.assertTrue(not matched if inverse else matched, msg=search) + def verify_rules(self, rules_search, inverse=False): + rule_output = cmd('ip rule show') + + for search in rules_search: + matched = False + for line in rule_output.split("\n"): + if all(item in line for item in search): + matched = True + break + self.assertTrue(not matched if inverse else matched, msg=search) + def test_pbr_group(self): self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network', 'network', '172.16.99.0/24']) self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network1', 'network', '172.16.101.0/24']) @@ -74,8 +92,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network']) self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'destination', 'group', 'network-group', 'smoketest_network1']) self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'set', 'mark', mark]) - - self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route', 'smoketest']) + self.cli_set(['policy', 'route', 'smoketest', 'interface', interface]) self.cli_commit() @@ -84,7 +101,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): ['ip daddr @N_smoketest_network1', 'ip saddr @N_smoketest_network'], ] - self.verify_nftables(nftables_search, 'ip mangle') + self.verify_nftables(nftables_search, 'ip vyos_mangle') self.cli_delete(['firewall']) @@ -92,8 +109,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'source', 'address', '172.16.20.10']) self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'destination', 'address', '172.16.10.10']) self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'set', 'mark', mark]) - - self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route', 'smoketest']) + self.cli_set(['policy', 'route', 'smoketest', 'interface', interface]) self.cli_commit() @@ -104,7 +120,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): ['ip daddr 172.16.10.10', 'ip saddr 172.16.20.10', 'meta mark set ' + mark_hex], ] - self.verify_nftables(nftables_search, 'ip mangle') + self.verify_nftables(nftables_search, 'ip vyos_mangle') def test_pbr_table(self): self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'protocol', 'tcp']) @@ -116,8 +132,8 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'destination', 'port', '8888']) self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'set', 'table', table_id]) - self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route', 'smoketest']) - self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route6', 'smoketest6']) + self.cli_set(['policy', 'route', 'smoketest', 'interface', interface]) + self.cli_set(['policy', 'route6', 'smoketest6', 'interface', interface]) self.cli_commit() @@ -130,7 +146,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): ['tcp flags syn / syn,ack', 'tcp dport 8888', 'meta mark set ' + mark_hex] ] - self.verify_nftables(nftables_search, 'ip mangle') + self.verify_nftables(nftables_search, 'ip vyos_mangle') # IPv6 @@ -139,7 +155,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): ['meta l4proto { tcp, udp }', 'th dport 8888', 'meta mark set ' + mark_hex] ] - self.verify_nftables(nftables6_search, 'ip6 mangle') + self.verify_nftables(nftables6_search, 'ip6 vyos_mangle') # IP rule fwmark -> table @@ -147,15 +163,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): ['fwmark ' + hex(table_mark_offset - int(table_id)), 'lookup ' + table_id] ] - ip_rule_output = cmd('ip rule show') - - for search in ip_rule_search: - matched = False - for line in ip_rule_output.split("\n"): - if all(item in line for item in search): - matched = True - break - self.assertTrue(matched) + self.verify_rules(ip_rule_search) def test_pbr_matching_criteria(self): @@ -203,8 +211,8 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '5', 'dscp-exclude', '14-19']) self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '5', 'set', 'table', table_id]) - self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route', 'smoketest']) - self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route6', 'smoketest6']) + self.cli_set(['policy', 'route', 'smoketest', 'interface', interface]) + self.cli_set(['policy', 'route6', 'smoketest6', 'interface', interface]) self.cli_commit() @@ -220,7 +228,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): ['ip dscp { 0x29, 0x39-0x3b }', 'meta mark set ' + mark_hex] ] - self.verify_nftables(nftables_search, 'ip mangle') + self.verify_nftables(nftables_search, 'ip vyos_mangle') # IPv6 nftables6_search = [ @@ -232,7 +240,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): ['ip6 dscp != { 0x0e-0x13, 0x3d }', 'meta mark set ' + mark_hex] ] - self.verify_nftables(nftables6_search, 'ip6 mangle') + self.verify_nftables(nftables6_search, 'ip6 vyos_mangle') if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/policy-route-interface.py b/src/conf_mode/policy-route-interface.py deleted file mode 100755 index 58c5fd93d..000000000 --- a/src/conf_mode/policy-route-interface.py +++ /dev/null @@ -1,132 +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 os -import re - -from sys import argv -from sys import exit - -from vyos.config import Config -from vyos.ifconfig import Section -from vyos.template import render -from vyos.util import cmd -from vyos.util import run -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - - ifname = argv[1] - ifpath = Section.get_config_path(ifname) - if_policy_path = f'interfaces {ifpath} policy' - - if_policy = conf.get_config_dict(if_policy_path, key_mangling=('-', '_'), get_first_key=True, - no_tag_node_value_mangle=True) - - if_policy['ifname'] = ifname - if_policy['policy'] = conf.get_config_dict(['policy'], key_mangling=('-', '_'), get_first_key=True, - no_tag_node_value_mangle=True) - - return if_policy - -def verify_chain(table, chain): - # Verify policy route applied - code = run(f'nft list chain {table} {chain}') - return code == 0 - -def verify(if_policy): - # bail out early - looks like removal from running config - if not if_policy: - return None - - for route in ['route', 'route6']: - if route in if_policy: - if route not in if_policy['policy']: - raise ConfigError('Policy route not configured') - - route_name = if_policy[route] - - if route_name not in if_policy['policy'][route]: - raise ConfigError(f'Invalid policy route name "{name}"') - - nft_prefix = 'VYOS_PBR6_' if route == 'route6' else 'VYOS_PBR_' - nft_table = 'ip6 mangle' if route == 'route6' else 'ip mangle' - - if not verify_chain(nft_table, nft_prefix + route_name): - raise ConfigError('Policy route did not apply') - - return None - -def generate(if_policy): - return None - -def cleanup_rule(table, chain, ifname, new_name=None): - results = cmd(f'nft -a list chain {table} {chain}').split("\n") - retval = None - for line in results: - if f'ifname "{ifname}"' in line: - if new_name and f'jump {new_name}' in line: - # new_name is used to clear rules for any previously referenced chains - # returns true when rule exists and doesn't need to be created - retval = True - continue - - handle_search = re.search('handle (\d+)', line) - if handle_search: - cmd(f'nft delete rule {table} {chain} handle {handle_search[1]}') - return retval - -def apply(if_policy): - ifname = if_policy['ifname'] - - route_chain = 'VYOS_PBR_PREROUTING' - ipv6_route_chain = 'VYOS_PBR6_PREROUTING' - - if 'route' in if_policy: - name = 'VYOS_PBR_' + if_policy['route'] - rule_exists = cleanup_rule('ip mangle', route_chain, ifname, name) - - if not rule_exists: - cmd(f'nft insert rule ip mangle {route_chain} iifname {ifname} counter jump {name}') - else: - cleanup_rule('ip mangle', route_chain, ifname) - - if 'route6' in if_policy: - name = 'VYOS_PBR6_' + if_policy['route6'] - rule_exists = cleanup_rule('ip6 mangle', ipv6_route_chain, ifname, name) - - if not rule_exists: - cmd(f'nft insert rule ip6 mangle {ipv6_route_chain} iifname {ifname} counter jump {name}') - else: - cleanup_rule('ip6 mangle', ipv6_route_chain, ifname) - - return None - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py index 00539b9c7..1d016695e 100755 --- a/src/conf_mode/policy-route.py +++ b/src/conf_mode/policy-route.py @@ -15,7 +15,6 @@ # along with this program. If not, see . import os -import re from json import loads from sys import exit @@ -25,7 +24,6 @@ from vyos.config import Config from vyos.template import render from vyos.util import cmd from vyos.util import dict_search_args -from vyos.util import dict_search_recursive from vyos.util import run from vyos import ConfigError from vyos import airbag @@ -34,48 +32,13 @@ airbag.enable() mark_offset = 0x7FFFFFFF nftables_conf = '/run/nftables_policy.conf' -ROUTE_PREFIX = 'VYOS_PBR_' -ROUTE6_PREFIX = 'VYOS_PBR6_' - -preserve_chains = [ - 'VYOS_PBR_PREROUTING', - 'VYOS_PBR_POSTROUTING', - 'VYOS_PBR6_PREROUTING', - 'VYOS_PBR6_POSTROUTING' -] - valid_groups = [ 'address_group', + 'domain_group', 'network_group', 'port_group' ] -group_set_prefix = { - 'A_': 'address_group', - 'A6_': 'ipv6_address_group', -# 'D_': 'domain_group', - 'M_': 'mac_group', - 'N_': 'network_group', - 'N6_': 'ipv6_network_group', - 'P_': 'port_group' -} - -def get_policy_interfaces(conf): - out = {} - interfaces = conf.get_config_dict(['interfaces'], key_mangling=('-', '_'), get_first_key=True, - no_tag_node_value_mangle=True) - def find_interfaces(iftype_conf, output={}, prefix=''): - for ifname, if_conf in iftype_conf.items(): - if 'policy' in if_conf: - output[prefix + ifname] = if_conf['policy'] - for vif in ['vif', 'vif_s', 'vif_c']: - if vif in if_conf: - output.update(find_interfaces(if_conf[vif], output, f'{prefix}{ifname}.')) - return output - for iftype, iftype_conf in interfaces.items(): - out.update(find_interfaces(iftype_conf)) - return out - def get_config(config=None): if config: conf = config @@ -88,7 +51,6 @@ def get_config(config=None): policy['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) - policy['interfaces'] = get_policy_interfaces(conf) return policy @@ -132,8 +94,8 @@ def verify_rule(policy, name, rule_conf, ipv6, rule_id): side_conf = rule_conf[side] if 'group' in side_conf: - if {'address_group', 'network_group'} <= set(side_conf['group']): - raise ConfigError('Only one address-group or network-group can be specified') + if len({'address_group', 'domain_group', 'network_group'} & set(side_conf['group'])) > 1: + raise ConfigError('Only one address-group, domain-group or network-group can be specified') for group in valid_groups: if group in side_conf['group']: @@ -168,73 +130,11 @@ def verify(policy): for rule_id, rule_conf in pol_conf['rule'].items(): verify_rule(policy, name, rule_conf, ipv6, rule_id) - for ifname, if_policy in policy['interfaces'].items(): - name = dict_search_args(if_policy, 'route') - ipv6_name = dict_search_args(if_policy, 'route6') - - if name and not dict_search_args(policy, 'route', name): - raise ConfigError(f'Policy route "{name}" is still referenced on interface {ifname}') - - if ipv6_name and not dict_search_args(policy, 'route6', ipv6_name): - raise ConfigError(f'Policy route6 "{ipv6_name}" is still referenced on interface {ifname}') - return None -def cleanup_commands(policy): - commands = [] - commands_chains = [] - commands_sets = [] - for table in ['ip mangle', 'ip6 mangle']: - route_node = 'route' if table == 'ip mangle' else 'route6' - chain_prefix = ROUTE_PREFIX if table == 'ip mangle' else ROUTE6_PREFIX - - json_str = cmd(f'nft -t -j list table {table}') - obj = loads(json_str) - if 'nftables' not in obj: - continue - for item in obj['nftables']: - if 'chain' in item: - chain = item['chain']['name'] - if chain in preserve_chains or not chain.startswith("VYOS_PBR"): - continue - - if dict_search_args(policy, route_node, chain.replace(chain_prefix, "", 1)) != None: - commands.append(f'flush chain {table} {chain}') - else: - commands_chains.append(f'delete chain {table} {chain}') - - if 'rule' in item: - rule = item['rule'] - chain = rule['chain'] - handle = rule['handle'] - - if chain not in preserve_chains: - continue - - target, _ = next(dict_search_recursive(rule['expr'], 'target')) - - if target.startswith(chain_prefix): - if dict_search_args(policy, route_node, target.replace(chain_prefix, "", 1)) == None: - commands.append(f'delete rule {table} {chain} handle {handle}') - - if 'set' in item: - set_name = item['set']['name'] - - for prefix, group_type in group_set_prefix.items(): - if set_name.startswith(prefix): - group_name = set_name.replace(prefix, "", 1) - if dict_search_args(policy, 'firewall_group', group_type, group_name) != None: - commands_sets.append(f'flush set {table} {set_name}') - else: - commands_sets.append(f'delete set {table} {set_name}') - - return commands + commands_chains + commands_sets - def generate(policy): if not os.path.exists(nftables_conf): policy['first_install'] = True - else: - policy['cleanup_commands'] = cleanup_commands(policy) render(nftables_conf, 'firewall/nftables-policy.j2', policy) return None diff --git a/src/helpers/vyos-domain-resolver.py b/src/helpers/vyos-domain-resolver.py index 035c208b2..e31d9238e 100755 --- a/src/helpers/vyos-domain-resolver.py +++ b/src/helpers/vyos-domain-resolver.py @@ -35,13 +35,13 @@ cache = False domain_state = {} ipv4_tables = { - 'ip mangle', + 'ip vyos_mangle', 'ip vyos_filter', 'ip vyos_nat' } ipv6_tables = { - 'ip6 mangle', + 'ip6 vyos_mangle', 'ip6 vyos_filter' } diff --git a/src/migration-scripts/policy/4-to-5 b/src/migration-scripts/policy/4-to-5 new file mode 100755 index 000000000..33c9e6ade --- /dev/null +++ b/src/migration-scripts/policy/4-to-5 @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# T2199: Migrate interface policy nodes to policy route interface + +import re + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree +from vyos.ifconfig import Section + +if (len(argv) < 1): + print("Must specify file name!") + exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +base4 = ['policy', 'route'] +base6 = ['policy', 'route6'] +config = ConfigTree(config_file) + +if not config.exists(base4) and not config.exists(base6): + # Nothing to do + exit(0) + +def migrate_interface(config, iftype, ifname, vif=None, vifs=None, vifc=None): + if_path = ['interfaces', iftype, ifname] + ifname_full = ifname + + if vif: + if_path += ['vif', vif] + ifname_full = f'{ifname}.{vif}' + elif vifs: + if_path += ['vif-s', vifs] + ifname_full = f'{ifname}.{vifs}' + if vifc: + if_path += ['vif-c', vifc] + ifname_full = f'{ifname}.{vifs}.{vifc}' + + if not config.exists(if_path + ['policy']): + return + + if config.exists(if_path + ['policy', 'route']): + route_name = config.return_value(if_path + ['policy', 'route']) + config.set(base4 + [route_name, 'interface'], value=ifname_full, replace=False) + + if config.exists(if_path + ['policy', 'route6']): + route_name = config.return_value(if_path + ['policy', 'route6']) + config.set(base6 + [route_name, 'interface'], value=ifname_full, replace=False) + + config.delete(if_path + ['policy']) + +for iftype in config.list_nodes(['interfaces']): + for ifname in config.list_nodes(['interfaces', iftype]): + migrate_interface(config, iftype, ifname) + + if config.exists(['interfaces', iftype, ifname, 'vif']): + for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']): + migrate_interface(config, iftype, ifname, vif=vif) + + if config.exists(['interfaces', iftype, ifname, 'vif-s']): + for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']): + migrate_interface(config, iftype, ifname, vifs=vifs) + + if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + migrate_interface(config, iftype, ifname, vifs=vifs, vifc=vifc) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1) diff --git a/src/op_mode/policy_route.py b/src/op_mode/policy_route.py index 5be40082f..5953786f3 100755 --- a/src/op_mode/policy_route.py +++ b/src/op_mode/policy_route.py @@ -22,53 +22,13 @@ from vyos.config import Config from vyos.util import cmd from vyos.util import dict_search_args -def get_policy_interfaces(conf, policy, name=None, ipv6=False): - interfaces = conf.get_config_dict(['interfaces'], key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - - routes = ['route', 'route6'] - - def parse_if(ifname, if_conf): - if 'policy' in if_conf: - for route in routes: - if route in if_conf['policy']: - route_name = if_conf['policy'][route] - name_str = f'({ifname},{route})' - - if not name: - policy[route][route_name]['interface'].append(name_str) - elif not ipv6 and name == route_name: - policy['interface'].append(name_str) - - for iftype in ['vif', 'vif_s', 'vif_c']: - if iftype in if_conf: - for vifname, vif_conf in if_conf[iftype].items(): - parse_if(f'{ifname}.{vifname}', vif_conf) - - for iftype, iftype_conf in interfaces.items(): - for ifname, if_conf in iftype_conf.items(): - parse_if(ifname, if_conf) - -def get_config_policy(conf, name=None, ipv6=False, interfaces=True): +def get_config_policy(conf, name=None, ipv6=False): config_path = ['policy'] if name: config_path += ['route6' if ipv6 else 'route', name] policy = conf.get_config_dict(config_path, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) - if policy and interfaces: - if name: - policy['interface'] = [] - else: - if 'route' in policy: - for route_name, route_conf in policy['route'].items(): - route_conf['interface'] = [] - - if 'route6' in policy: - for route_name, route_conf in policy['route6'].items(): - route_conf['interface'] = [] - - get_policy_interfaces(conf, policy, name, ipv6) return policy -- cgit v1.2.3 From a8daba9549668c45abf33c55bc5e68f4b9320f5e Mon Sep 17 00:00:00 2001 From: fett0 Date: Sun, 13 Nov 2022 17:05:00 +0000 Subject: l3VPN : T4182: add l3vpn over gre option from route-map --- data/templates/frr/policy.frr.j2 | 3 +++ interface-definitions/policy.xml.in | 22 +++++++++++++++++++++- smoketest/scripts/cli/test_policy.py | 5 +++++ 3 files changed, 29 insertions(+), 1 deletion(-) (limited to 'interface-definitions') diff --git a/data/templates/frr/policy.frr.j2 b/data/templates/frr/policy.frr.j2 index 5ad4bd28c..9b5e80aed 100644 --- a/data/templates/frr/policy.frr.j2 +++ b/data/templates/frr/policy.frr.j2 @@ -322,6 +322,9 @@ route-map {{ route_map }} {{ rule_config.action }} {{ rule }} {% if rule_config.set.ipv6_next_hop.prefer_global is vyos_defined %} set ipv6 next-hop prefer-global {% endif %} +{% if rule_config.set.l3vpn_nexthop.encapsulation.gre is vyos_defined %} +set l3vpn next-hop encapsulation gre +{% endif %} {% if rule_config.set.large_community.replace is vyos_defined %} set large-community {{ rule_config.set.large_community.replace | join(' ') }} {% endif %} diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in index 6c60276d5..ac774dc1f 100644 --- a/interface-definitions/policy.xml.in +++ b/interface-definitions/policy.xml.in @@ -1341,7 +1341,7 @@ - + Use peer address (for BGP only) @@ -1356,6 +1356,26 @@ + + + Next hop Information + + + + + Encapsulation options (for BGP only) + + + + + Accept L3VPN traffic over GRE encapsulation + + + + + + + BGP local preference attribute diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py index 2166e63ec..3a4ef666a 100755 --- a/smoketest/scripts/cli/test_policy.py +++ b/smoketest/scripts/cli/test_policy.py @@ -1030,6 +1030,7 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): 'metric' : '150', 'metric-type' : 'type-1', 'origin' : 'incomplete', + 'l3vpn' : '', 'originator-id' : '172.16.10.1', 'src' : '100.0.0.1', 'tag' : '65530', @@ -1229,6 +1230,8 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): self.cli_set(path + ['rule', rule, 'set', 'ipv6-next-hop', 'local', rule_config['set']['ipv6-next-hop-local']]) if 'ip-next-hop' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'ip-next-hop', rule_config['set']['ip-next-hop']]) + if 'l3vpn' in rule_config['set']: + self.cli_set(path + ['rule', rule, 'set', 'l3vpn-nexthop', 'encapsulation', 'gre']) if 'local-preference' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'local-preference', rule_config['set']['local-preference']]) if 'metric' in rule_config['set']: @@ -1408,6 +1411,8 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): tmp += 'ipv6 next-hop global ' + rule_config['set']['ipv6-next-hop-global'] elif 'ipv6-next-hop-local' in rule_config['set']: tmp += 'ipv6 next-hop local ' + rule_config['set']['ipv6-next-hop-local'] + elif 'l3vpn' in rule_config['set']: + tmp += 'l3vpn next-hop encapsulation gre' elif 'local-preference' in rule_config['set']: tmp += 'local-preference ' + rule_config['set']['local-preference'] elif 'metric' in rule_config['set']: -- cgit v1.2.3 From 2a203e816f7cf3c6152e77277ec114b4767a06c7 Mon Sep 17 00:00:00 2001 From: fett0 Date: Sun, 13 Nov 2022 23:11:49 +0000 Subject: T4813: add l3vpn over gre option from route-map --- interface-definitions/policy.xml.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'interface-definitions') diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in index ac774dc1f..b3745fda0 100644 --- a/interface-definitions/policy.xml.in +++ b/interface-definitions/policy.xml.in @@ -1341,7 +1341,7 @@ - + Use peer address (for BGP only) -- cgit v1.2.3 From 6458f99cc31bb8965648cf78149d2ac4088e0892 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Fri, 18 Nov 2022 14:07:19 +0000 Subject: T4826: Fix login pubkey key type ed25519-sk ecdsa-sk Requires full key type name like sk-ecdsa-sha2-nistp256@openssh.com and sk-ssh-ed25519@openssh.com --- interface-definitions/system-login.xml.in | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'interface-definitions') diff --git a/interface-definitions/system-login.xml.in b/interface-definitions/system-login.xml.in index 027d3f587..e71a647ef 100644 --- a/interface-definitions/system-login.xml.in +++ b/interface-definitions/system-login.xml.in @@ -129,7 +129,7 @@ SSH public key type - ssh-dss ssh-rsa ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 ssh-ed25519 ecdsa-sk ed25519-sk + ssh-dss ssh-rsa ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 ssh-ed25519 sk-ecdsa-sha2-nistp256@openssh.com sk-ssh-ed25519@openssh.com ssh-dss @@ -156,15 +156,15 @@ Edwards-curve DSA with elliptic curve 25519 - ecdsa-sk + sk-ecdsa-sha2-nistp256@openssh.com Elliptic Curve DSA security key - ed25519-sk + sk-ssh-ed25519@openssh.com Elliptic curve 25519 security key - (ssh-dss|ssh-rsa|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|ssh-ed25519|ecdsa-sk|ed25519-sk) + (ssh-dss|ssh-rsa|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|ssh-ed25519|sk-ecdsa-sha2-nistp256@openssh.com|sk-ssh-ed25519@openssh.com) -- cgit v1.2.3 From 8b7a1399fb2b4327798ec748f96a457420a912c3 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Tue, 22 Nov 2022 12:23:24 +0100 Subject: container: T4834: Limit network names to 11 characters (15 char max including "cni-" prefix) * Error: unable to start container "": plugin type="bridge" failed (add): cni plugin bridge failed: failed to create bridge "cni-thisismorethan15chars": could not add "cni-thisismorethan15chars": numerical result out of range --- interface-definitions/container.xml.in | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'interface-definitions') diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in index f84c94a40..d50039665 100644 --- a/interface-definitions/container.xml.in +++ b/interface-definitions/container.xml.in @@ -272,6 +272,10 @@ Network name + + [-_a-zA-Z0-9]{1,11} + + Network name cannot be longer than 11 characters -- cgit v1.2.3 From 48ab6413cedb71934118143a7e095e946ac38bb7 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Sun, 20 Nov 2022 13:35:24 +0000 Subject: T4825: Add interface type veth Add interface type veth (Virtual ethernet) One of the usecases it's interconnect different vrf's and default vrf via bridge set interfaces virtual-ethernet veth0 peer-name 'veth1010' set interfaces virtual-ethernet veth1010 address '10.0.0.10/24' set interfaces virtual-ethernet veth1010 peer-name 'veth0' set interfaces virtual-ethernet veth1010 vrf 'foo' set interfaces bridge br0 address '10.0.0.1/24' set interfaces bridge br0 member interface veth0 --- .../interfaces-virtual-ethernet.xml.in | 36 ++++++++ python/vyos/ifconfig/__init__.py | 1 + python/vyos/ifconfig/veth.py | 54 ++++++++++++ src/conf_mode/interfaces-virtual-ethernet.py | 97 ++++++++++++++++++++++ 4 files changed, 188 insertions(+) create mode 100644 interface-definitions/interfaces-virtual-ethernet.xml.in create mode 100644 python/vyos/ifconfig/veth.py create mode 100755 src/conf_mode/interfaces-virtual-ethernet.py (limited to 'interface-definitions') diff --git a/interface-definitions/interfaces-virtual-ethernet.xml.in b/interface-definitions/interfaces-virtual-ethernet.xml.in new file mode 100644 index 000000000..3b78b3637 --- /dev/null +++ b/interface-definitions/interfaces-virtual-ethernet.xml.in @@ -0,0 +1,36 @@ + + + + + + + Virtual Ethernet Interface (veth) + 300 + + veth[0-9]+ + + Virutal Ethernet interface must be named vethN + + vethN + Virtual Ethernet interface name + + + + #include + #include + #include + #include + + + Virtual ethernet peer interface name + + veth[0-9]+ + + Virutal Ethernet interface must be named vethN + + + + + + + diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py index a37615c8f..d1ddaa13e 100644 --- a/python/vyos/ifconfig/__init__.py +++ b/python/vyos/ifconfig/__init__.py @@ -36,4 +36,5 @@ from vyos.ifconfig.tunnel import TunnelIf from vyos.ifconfig.wireless import WiFiIf from vyos.ifconfig.l2tpv3 import L2TPv3If from vyos.ifconfig.macsec import MACsecIf +from vyos.ifconfig.veth import VethIf from vyos.ifconfig.wwan import WWANIf diff --git a/python/vyos/ifconfig/veth.py b/python/vyos/ifconfig/veth.py new file mode 100644 index 000000000..aafbf226a --- /dev/null +++ b/python/vyos/ifconfig/veth.py @@ -0,0 +1,54 @@ +# Copyright 2022 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see . + +from vyos.ifconfig.interface import Interface + + +@Interface.register +class VethIf(Interface): + """ + Abstraction of a Linux veth interface + """ + iftype = 'veth' + definition = { + **Interface.definition, + **{ + 'section': 'virtual-ethernet', + 'prefixes': ['veth', ], + 'bridgeable': True, + }, + } + + def _create(self): + """ + Create veth interface in OS kernel. Interface is administrative + down by default. + """ + # check before create, as we have 2 veth interfaces in our CLI + # interface virtual-ethernet veth0 peer-name 'veth1' + # interface virtual-ethernet veth1 peer-name 'veth0' + # + # but iproute2 creates the pair with one command: + # ip link add vet0 type veth peer name veth1 + if self.exists(self.config['peer_name']): + return + + # create virtual-ethernet interface + cmd = 'ip link add {ifname} type {type}'.format(**self.config) + cmd += f' peer name {self.config["peer_name"]}' + self._cmd(cmd) + + # interface is always A/D down. It needs to be enabled explicitly + self.set_admin_state('down') diff --git a/src/conf_mode/interfaces-virtual-ethernet.py b/src/conf_mode/interfaces-virtual-ethernet.py new file mode 100755 index 000000000..91609ded9 --- /dev/null +++ b/src/conf_mode/interfaces-virtual-ethernet.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from sys import exit + +from netifaces import interfaces +from vyos import ConfigError +from vyos import airbag +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_vrf +from vyos.ifconfig import VethIf + +airbag.enable() + + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at + least the interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'virtual-ethernet'] + ifname, veth = get_interface_dict(conf, base) + + veth_dict = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + veth['config_dict'] = veth_dict + + return veth + + +def verify(veth): + if 'deleted' in veth: + verify_bridge_delete(veth) + return None + + verify_vrf(veth) + verify_address(veth) + + if 'peer_name' not in veth: + raise ConfigError( + f'Remote peer name must be set for \"{veth["ifname"]}\"!') + + if veth['peer_name'] not in veth['config_dict'].keys(): + raise ConfigError( + f'Interface \"{veth["peer_name"]}\" is not configured!') + + return None + + +def generate(peth): + return None + + +def apply(veth): + # Check if the Veth interface already exists + if 'rebuild_required' in veth or 'deleted' in veth: + if veth['ifname'] in interfaces(): + p = VethIf(veth['ifname']) + p.remove() + + if 'deleted' not in veth: + p = VethIf(**veth) + p.update(veth) + + return None + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) -- cgit v1.2.3 From f9a5286f163b27b51eb5e9a801c5d646c07a7990 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 24 Nov 2022 20:35:31 +0100 Subject: veth: T4825: minor improvements on XML peer-name handling --- .../interfaces-virtual-ethernet.xml.in | 9 ++++++++- src/conf_mode/interfaces-virtual-ethernet.py | 23 +++++++++++----------- 2 files changed, 20 insertions(+), 12 deletions(-) (limited to 'interface-definitions') diff --git a/interface-definitions/interfaces-virtual-ethernet.xml.in b/interface-definitions/interfaces-virtual-ethernet.xml.in index 3b78b3637..d52e9ef80 100644 --- a/interface-definitions/interfaces-virtual-ethernet.xml.in +++ b/interface-definitions/interfaces-virtual-ethernet.xml.in @@ -4,7 +4,7 @@ - Virtual Ethernet Interface (veth) + Virtual Ethernet (veth) Interface 300 veth[0-9]+ @@ -23,6 +23,13 @@ Virtual ethernet peer interface name + + interfaces virtual-ethernet + + + txt + Name of peer interface + veth[0-9]+ diff --git a/src/conf_mode/interfaces-virtual-ethernet.py b/src/conf_mode/interfaces-virtual-ethernet.py index 91609ded9..b1819233c 100755 --- a/src/conf_mode/interfaces-virtual-ethernet.py +++ b/src/conf_mode/interfaces-virtual-ethernet.py @@ -28,7 +28,6 @@ from vyos.ifconfig import VethIf airbag.enable() - def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at @@ -41,10 +40,12 @@ def get_config(config=None): base = ['interfaces', 'virtual-ethernet'] ifname, veth = get_interface_dict(conf, base) - veth_dict = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - veth['config_dict'] = veth_dict + # We need to know all other veth related interfaces as veth requires a 1:1 + # mapping for the peer-names. The Linux kernel automatically creates both + # interfaces, the local one and the peer-name, but VyOS also needs a peer + # interfaces configrued on the CLI so we can assign proper IP addresses etc. + veth['other_interfaces'] = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) return veth @@ -58,12 +59,13 @@ def verify(veth): verify_address(veth) if 'peer_name' not in veth: - raise ConfigError( - f'Remote peer name must be set for \"{veth["ifname"]}\"!') + raise ConfigError(f'Remote peer name must be set for "{veth["ifname"]}"!') - if veth['peer_name'] not in veth['config_dict'].keys(): - raise ConfigError( - f'Interface \"{veth["peer_name"]}\" is not configured!') + if veth['peer_name'] not in veth['other_interfaces']: + peer_name = veth['peer_name'] + ifname = veth['ifname'] + raise ConfigError(f'Used peer-name "{peer_name}" on interface "{ifname}" ' \ + 'is not configured!') return None @@ -71,7 +73,6 @@ def verify(veth): def generate(peth): return None - def apply(veth): # Check if the Veth interface already exists if 'rebuild_required' in veth or 'deleted' in veth: -- cgit v1.2.3 From f311fdae68d26d5b3719bf2a86fc935d81571c8a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Nov 2022 20:49:02 +0100 Subject: veth: T4825: add dhcp(v6) client options to CLI --- interface-definitions/interfaces-virtual-ethernet.xml.in | 2 ++ 1 file changed, 2 insertions(+) (limited to 'interface-definitions') diff --git a/interface-definitions/interfaces-virtual-ethernet.xml.in b/interface-definitions/interfaces-virtual-ethernet.xml.in index d52e9ef80..8059ec33b 100644 --- a/interface-definitions/interfaces-virtual-ethernet.xml.in +++ b/interface-definitions/interfaces-virtual-ethernet.xml.in @@ -18,6 +18,8 @@ #include #include + #include + #include #include #include -- cgit v1.2.3 From ff8da7dcd5a20c4075d4eeae08e519c3b271517c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 29 Nov 2022 07:16:51 +0100 Subject: xml: telegraf: T4680: add missing comment in listen-address-single.xml.i --- interface-definitions/include/listen-address-single.xml.i | 1 + 1 file changed, 1 insertion(+) (limited to 'interface-definitions') diff --git a/interface-definitions/include/listen-address-single.xml.i b/interface-definitions/include/listen-address-single.xml.i index b5841cabb..30293b338 100644 --- a/interface-definitions/include/listen-address-single.xml.i +++ b/interface-definitions/include/listen-address-single.xml.i @@ -1,3 +1,4 @@ + Local IP addresses to listen on -- cgit v1.2.3 From fdeb731f831f1f42332e5c5b318cd1016cf98f03 Mon Sep 17 00:00:00 2001 From: fett0 Date: Fri, 2 Dec 2022 17:57:07 +0000 Subject: T4858: Fix l3vpn Route Distinguisher validator --- interface-definitions/include/bgp/afi-rd.xml.i | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'interface-definitions') diff --git a/interface-definitions/include/bgp/afi-rd.xml.i b/interface-definitions/include/bgp/afi-rd.xml.i index 767502094..beb1447df 100644 --- a/interface-definitions/include/bgp/afi-rd.xml.i +++ b/interface-definitions/include/bgp/afi-rd.xml.i @@ -17,7 +17,7 @@ Route Distinguisher, (x.x.x.x:yyy|xxxx:yyyy) - ((25[0-5]|2[0-4][0-9]|[1][0-9][0-9]|[1-9][0-9]|[0-9]?)(\.(25[0-5]|2[0-4][0-9]|[1][0-9][0-9]|[1-9][0-9]|[0-9]?)){3}|[0-9]{1,10}):[0-9]{1,5} + -- cgit v1.2.3 From e4befa4987404aecc83e3e48b3d52dd4b64f7d99 Mon Sep 17 00:00:00 2001 From: fett0 Date: Fri, 2 Dec 2022 21:38:36 +0000 Subject: T4854: route reflector allows to apply route-maps --- data/templates/frr/bgpd.frr.j2 | 3 +++ interface-definitions/include/bgp/protocol-common-config.xml.i | 6 ++++++ smoketest/scripts/cli/test_protocols_bgp.py | 2 ++ 3 files changed, 11 insertions(+) (limited to 'interface-definitions') diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2 index e8d135c78..5febd7c66 100644 --- a/data/templates/frr/bgpd.frr.j2 +++ b/data/templates/frr/bgpd.frr.j2 @@ -517,6 +517,9 @@ router bgp {{ system_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }} {% if parameters.network_import_check is vyos_defined %} bgp network import-check {% endif %} +{% if parameters.route_reflector_allow_outbound_policy is vyos_defined %} +bgp route-reflector allow-outbound-policy +{% endif %} {% if parameters.no_client_to_client_reflection is vyos_defined %} no bgp client-to-client reflection {% endif %} diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i index 70176144d..fe192434d 100644 --- a/interface-definitions/include/bgp/protocol-common-config.xml.i +++ b/interface-definitions/include/bgp/protocol-common-config.xml.i @@ -1431,6 +1431,12 @@ + + + Route reflector client allow policy outbound + + + Disable client to client route reflection diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index d2dad8c1a..debc8270c 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -294,6 +294,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['parameters', 'minimum-holdtime', min_hold_time]) self.cli_set(base_path + ['parameters', 'no-suppress-duplicates']) self.cli_set(base_path + ['parameters', 'reject-as-sets']) + self.cli_set(base_path + ['parameters', 'route-reflector-allow-outbound-policy']) self.cli_set(base_path + ['parameters', 'shutdown']) self.cli_set(base_path + ['parameters', 'suppress-fib-pending']) @@ -322,6 +323,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' bgp bestpath peer-type multipath-relax', frrconfig) self.assertIn(f' bgp minimum-holdtime {min_hold_time}', frrconfig) self.assertIn(f' bgp reject-as-sets', frrconfig) + self.assertIn(f' bgp route-reflector allow-outbound-policy', frrconfig) self.assertIn(f' bgp shutdown', frrconfig) self.assertIn(f' bgp suppress-fib-pending', frrconfig) self.assertNotIn(f'bgp ebgp-requires-policy', frrconfig) -- cgit v1.2.3 From 9fa4b761d027e2eee8a6fac587857548292261fb Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Thu, 8 Dec 2022 13:04:56 +0000 Subject: T4117: Fix for L2TP DAE CoA server configuration Fix l2tp dae server template and python config dict for correctlly handling Dynamic Authorization Extension server configuration --- data/templates/accel-ppp/l2tp.config.j2 | 3 +++ interface-definitions/vpn-l2tp.xml.in | 1 + src/conf_mode/vpn_l2tp.py | 34 ++++++++++++++++++++++++--------- 3 files changed, 29 insertions(+), 9 deletions(-) (limited to 'interface-definitions') diff --git a/data/templates/accel-ppp/l2tp.config.j2 b/data/templates/accel-ppp/l2tp.config.j2 index 9eeaf7622..986f19656 100644 --- a/data/templates/accel-ppp/l2tp.config.j2 +++ b/data/templates/accel-ppp/l2tp.config.j2 @@ -88,6 +88,9 @@ verbose=1 {% for r in radius_server %} server={{ r.server }},{{ r.key }},auth-port={{ r.port }},acct-port={{ r.acct_port }},req-limit=0,fail-time={{ r.fail_time }} {% endfor %} +{% if radius_dynamic_author.server is vyos_defined %} +dae-server={{ radius_dynamic_author.server }}:{{ radius_dynamic_author.port }},{{ radius_dynamic_author.key }} +{% endif %} {% if radius_acct_inter_jitter %} acct-interim-jitter={{ radius_acct_inter_jitter }} {% endif %} diff --git a/interface-definitions/vpn-l2tp.xml.in b/interface-definitions/vpn-l2tp.xml.in index cb5900e0d..06ca4ece5 100644 --- a/interface-definitions/vpn-l2tp.xml.in +++ b/interface-definitions/vpn-l2tp.xml.in @@ -230,6 +230,7 @@ Port for Dynamic Authorization Extension server (DM/CoA) + 1700 diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py index fd5a4acd8..c533ad404 100755 --- a/src/conf_mode/vpn_l2tp.py +++ b/src/conf_mode/vpn_l2tp.py @@ -26,7 +26,10 @@ from ipaddress import ip_network from vyos.config import Config from vyos.template import is_ipv4 from vyos.template import render -from vyos.util import call, get_half_cpus +from vyos.util import call +from vyos.util import get_half_cpus +from vyos.util import check_port_availability +from vyos.util import is_listen_port_bind_service from vyos import ConfigError from vyos import airbag @@ -64,7 +67,7 @@ default_config_data = { 'radius_source_address': '', 'radius_shaper_attr': '', 'radius_shaper_vendor': '', - 'radius_dynamic_author': '', + 'radius_dynamic_author': {}, 'wins': [], 'ip6_column': [], 'thread_cnt': get_half_cpus() @@ -205,21 +208,21 @@ def get_config(config=None): l2tp['radius_source_address'] = conf.return_value(['source-address']) # Dynamic Authorization Extensions (DOA)/Change Of Authentication (COA) - if conf.exists(['dynamic-author']): + if conf.exists(['dae-server']): dae = { 'port' : '', 'server' : '', 'key' : '' } - if conf.exists(['dynamic-author', 'server']): - dae['server'] = conf.return_value(['dynamic-author', 'server']) + if conf.exists(['dae-server', 'ip-address']): + dae['server'] = conf.return_value(['dae-server', 'ip-address']) - if conf.exists(['dynamic-author', 'port']): - dae['port'] = conf.return_value(['dynamic-author', 'port']) + if conf.exists(['dae-server', 'port']): + dae['port'] = conf.return_value(['dae-server', 'port']) - if conf.exists(['dynamic-author', 'key']): - dae['key'] = conf.return_value(['dynamic-author', 'key']) + if conf.exists(['dae-server', 'secret']): + dae['key'] = conf.return_value(['dae-server', 'secret']) l2tp['radius_dynamic_author'] = dae @@ -329,6 +332,19 @@ def verify(l2tp): if not radius['key']: raise ConfigError(f"Missing RADIUS secret for server { radius['key'] }") + if l2tp['radius_dynamic_author']: + if not l2tp['radius_dynamic_author']['server']: + raise ConfigError("Missing ip-address for dae-server") + if not l2tp['radius_dynamic_author']['key']: + raise ConfigError("Missing secret for dae-server") + address = l2tp['radius_dynamic_author']['server'] + port = l2tp['radius_dynamic_author']['port'] + proto = 'tcp' + # check if dae listen port is not used by another service + if check_port_availability(address, 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') + # check for the existence of a client ip pool if not (l2tp['client_ip_pool'] or l2tp['client_ip_subnets']): raise ConfigError( -- cgit v1.2.3 From a52a52c433d43e4df986fdb7192d9a8357df446a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 11 Dec 2022 19:32:02 +0100 Subject: xml: ddns: T4792: split "server" CLI node into building block --- interface-definitions/dns-dynamic.xml.in | 14 +------------- interface-definitions/include/server-ipv4-fqdn.xml.i | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 13 deletions(-) create mode 100644 interface-definitions/include/server-ipv4-fqdn.xml.i (limited to 'interface-definitions') diff --git a/interface-definitions/dns-dynamic.xml.in b/interface-definitions/dns-dynamic.xml.in index e41ba7f60..a39e412b2 100644 --- a/interface-definitions/dns-dynamic.xml.in +++ b/interface-definitions/dns-dynamic.xml.in @@ -237,19 +237,7 @@ Please choose from the list of allowed protocols - - - Server to send DDNS update to - - IPv4 - IP address of DDNS server - - - FQDN - Hostname of DDNS server - - - + #include DNS zone to update (only available with CloudFlare) diff --git a/interface-definitions/include/server-ipv4-fqdn.xml.i b/interface-definitions/include/server-ipv4-fqdn.xml.i new file mode 100644 index 000000000..7bab9812c --- /dev/null +++ b/interface-definitions/include/server-ipv4-fqdn.xml.i @@ -0,0 +1,15 @@ + + + + Remote server to connect to + + ipv4 + Server IPv4 address + + + hostname + Server hostname/FQDN + + + + -- cgit v1.2.3 From 9fe2353ee85fda18c181dab973cbcde6d2294e6c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 11 Dec 2022 19:33:08 +0100 Subject: pppoe: xml: T4792: split "no-peer-dns" CLI node into building block --- interface-definitions/include/interface/no-peer-dns.xml.i | 8 ++++++++ interface-definitions/interfaces-pppoe.xml.in | 7 +------ 2 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 interface-definitions/include/interface/no-peer-dns.xml.i (limited to 'interface-definitions') diff --git a/interface-definitions/include/interface/no-peer-dns.xml.i b/interface-definitions/include/interface/no-peer-dns.xml.i new file mode 100644 index 000000000..d663f04c1 --- /dev/null +++ b/interface-definitions/include/interface/no-peer-dns.xml.i @@ -0,0 +1,8 @@ + + + + Do not use DNS servers provided by the peer + + + + diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in index 719060fa9..35c4889ea 100644 --- a/interface-definitions/interfaces-pppoe.xml.in +++ b/interface-definitions/interfaces-pppoe.xml.in @@ -82,12 +82,7 @@ 1492 - - - Do not use DNS servers provided by the peer - - - + #include IPv4 address of remote end of the PPPoE link -- cgit v1.2.3 From ff56aeefddaad2d37d3ea32626e1adf3960eaf26 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 11 Dec 2022 19:38:28 +0100 Subject: sstp: T4384: initial implementation of SSTP client CLI vyos@vyos# show interfaces sstpc sstpc sstpc10 { authentication { password vyos user vyos } server sstp.vyos.net ssl { ca-certificate VyOS-CA } } --- data/configd-include.json | 1 + data/templates/sstp-client/peer.j2 | 46 ++++++++ interface-definitions/interfaces-sstpc.xml.in | 47 ++++++++ op-mode-definitions/connect.xml.in | 1 + op-mode-definitions/disconnect.xml.in | 1 + op-mode-definitions/monitor-log.xml.in | 17 +++ op-mode-definitions/show-interfaces-sstpc.xml.in | 51 ++++++++ op-mode-definitions/show-log.xml.in | 17 +++ python/vyos/ifconfig/__init__.py | 3 +- python/vyos/ifconfig/sstpc.py | 40 +++++++ src/conf_mode/interfaces-sstpc.py | 142 +++++++++++++++++++++++ src/etc/ppp/ip-up.d/96-vyos-sstpc-callback | 49 ++++++++ src/op_mode/connect_disconnect.py | 4 +- 13 files changed, 416 insertions(+), 3 deletions(-) create mode 100644 data/templates/sstp-client/peer.j2 create mode 100644 interface-definitions/interfaces-sstpc.xml.in create mode 100644 op-mode-definitions/show-interfaces-sstpc.xml.in create mode 100644 python/vyos/ifconfig/sstpc.py create mode 100755 src/conf_mode/interfaces-sstpc.py create mode 100755 src/etc/ppp/ip-up.d/96-vyos-sstpc-callback (limited to 'interface-definitions') diff --git a/data/configd-include.json b/data/configd-include.json index 5a4912e30..648655a8b 100644 --- a/data/configd-include.json +++ b/data/configd-include.json @@ -28,6 +28,7 @@ "interfaces-openvpn.py", "interfaces-pppoe.py", "interfaces-pseudo-ethernet.py", +"interfaces-sstpc.py", "interfaces-tunnel.py", "interfaces-vti.py", "interfaces-vxlan.py", diff --git a/data/templates/sstp-client/peer.j2 b/data/templates/sstp-client/peer.j2 new file mode 100644 index 000000000..1127d0564 --- /dev/null +++ b/data/templates/sstp-client/peer.j2 @@ -0,0 +1,46 @@ +### Autogenerated by interfaces-sstpc.py ### +{{ '# ' ~ description if description is vyos_defined else '' }} + +# Require peer to provide the local IP address if it is not +# specified explicitly in the config file. +noipdefault + +# Don't show the password in logfiles: +hide-password + +remotename {{ ifname }} +linkname {{ ifname }} +ipparam {{ ifname }} +ifname {{ ifname }} +pty "sstpc --ipparam {{ ifname }} --nolaunchpppd {{ server }}:{{ port }} --ca-cert {{ ca_file_path }}" + +# Override any connect script that may have been set in /etc/ppp/options. +connect /bin/true + +# Don't try to authenticate the remote node +noauth + +# We won't want EAP +refuse-eap + +# Don't try to proxy ARP for the remote endpoint. User can set proxy +# arp entries up manually if they wish. More importantly, having +# the "proxyarp" parameter set disables the "defaultroute" option. +noproxyarp + +# Unlimited connection attempts +maxfail 0 + +plugin sstp-pppd-plugin.so +sstp-sock /var/run/sstpc/sstpc-{{ ifname }} + +persist +debug + +{% if authentication is vyos_defined %} +{{ 'user "' + authentication.user + '"' if authentication.user is vyos_defined }} +{{ 'password "' + authentication.password + '"' if authentication.password is vyos_defined }} +{% endif %} + +{{ "usepeerdns" if no_peer_dns is not vyos_defined }} + diff --git a/interface-definitions/interfaces-sstpc.xml.in b/interface-definitions/interfaces-sstpc.xml.in new file mode 100644 index 000000000..30b55a9fa --- /dev/null +++ b/interface-definitions/interfaces-sstpc.xml.in @@ -0,0 +1,47 @@ + + + + + + + Secure Socket Tunneling Protocol (SSTP) client Interface + 460 + + sstpc[0-9]+ + + Secure Socket Tunneling Protocol interface must be named sstpcN + + sstpcN + Secure Socket Tunneling Protocol interface name + + + + #include + #include + #include + #include + #include + #include + #include + + 1452 + + #include + #include + + 443 + + + + Secure Sockets Layer (SSL) configuration + + + #include + + + #include + + + + + diff --git a/op-mode-definitions/connect.xml.in b/op-mode-definitions/connect.xml.in index d0c93195c..116cd6231 100644 --- a/op-mode-definitions/connect.xml.in +++ b/op-mode-definitions/connect.xml.in @@ -20,6 +20,7 @@ Bring up a connection-oriented network interface interfaces pppoe + interfaces sstpc interfaces wwan diff --git a/op-mode-definitions/disconnect.xml.in b/op-mode-definitions/disconnect.xml.in index 4415c0ed2..843998c4f 100644 --- a/op-mode-definitions/disconnect.xml.in +++ b/op-mode-definitions/disconnect.xml.in @@ -10,6 +10,7 @@ Take down a connection-oriented network interface interfaces pppoe + interfaces sstpc interfaces wwan diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in index dccdfaf9a..1b1f53dc2 100644 --- a/op-mode-definitions/monitor-log.xml.in +++ b/op-mode-definitions/monitor-log.xml.in @@ -224,6 +224,23 @@ journalctl --no-hostname --boot --follow --unit ssh.service + + + Monitor last lines of SSTP client log + + journalctl --no-hostname --boot --follow --unit "ppp@sstpc*.service" + + + + Monitor last lines of SSTP client log for specific interface + + + + + journalctl --no-hostname --boot --follow --unit "ppp@$5.service" + + + Show log for Virtual Private Network (VPN) diff --git a/op-mode-definitions/show-interfaces-sstpc.xml.in b/op-mode-definitions/show-interfaces-sstpc.xml.in new file mode 100644 index 000000000..e66d3a0ac --- /dev/null +++ b/op-mode-definitions/show-interfaces-sstpc.xml.in @@ -0,0 +1,51 @@ + + + + + + + + + Show specified SSTP client interface information + + interfaces sstpc + + + ${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" + + + + Show specified SSTP client interface log + + journalctl --no-hostname --boot --follow --unit "ppp@$4".service + + + + Show specified SSTP client interface statistics + + interfaces sstpc + + + if [ -d "/sys/class/net/$4" ]; then /usr/sbin/pppstats "$4"; fi + + + + + + Show SSTP client interface information + + ${vyos_op_scripts_dir}/show_interfaces.py --intf-type=sstpc --action=show-brief + + + + Show detailed SSTP client interface information + + ${vyos_op_scripts_dir}/show_interfaces.py --intf-type=sstpc --action=show + + + + + + + + diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in index 404de1913..64a54015b 100644 --- a/op-mode-definitions/show-log.xml.in +++ b/op-mode-definitions/show-log.xml.in @@ -356,6 +356,23 @@ journalctl --no-hostname --boot --unit ssh.service + + + Show log for SSTP client + + journalctl --no-hostname --boot --unit "ppp@sstpc*.service" + + + + Show SSTP client log on specific interface + + + + + journalctl --no-hostname --boot --unit "ppp@$5.service" + + + Show last n changes to messages diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py index d1ddaa13e..206b2bba1 100644 --- a/python/vyos/ifconfig/__init__.py +++ b/python/vyos/ifconfig/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2019-2021 VyOS maintainers and contributors +# Copyright 2019-2022 VyOS maintainers and contributors # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -38,3 +38,4 @@ from vyos.ifconfig.l2tpv3 import L2TPv3If from vyos.ifconfig.macsec import MACsecIf from vyos.ifconfig.veth import VethIf from vyos.ifconfig.wwan import WWANIf +from vyos.ifconfig.sstpc import SSTPCIf diff --git a/python/vyos/ifconfig/sstpc.py b/python/vyos/ifconfig/sstpc.py new file mode 100644 index 000000000..50fc6ee6b --- /dev/null +++ b/python/vyos/ifconfig/sstpc.py @@ -0,0 +1,40 @@ +# Copyright 2022 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see . + +from vyos.ifconfig.interface import Interface + +@Interface.register +class SSTPCIf(Interface): + iftype = 'sstpc' + definition = { + **Interface.definition, + **{ + 'section': 'sstpc', + 'prefixes': ['sstpc', ], + 'eternal': 'sstpc[0-9]+$', + }, + } + + def _create(self): + # we can not create this interface as it is managed outside + pass + + def _delete(self): + # we can not create this interface as it is managed outside + pass + + def get_mac(self): + """ Get a synthetic MAC address. """ + return self.get_mac_synthetic() diff --git a/src/conf_mode/interfaces-sstpc.py b/src/conf_mode/interfaces-sstpc.py new file mode 100755 index 000000000..6b8094c51 --- /dev/null +++ b/src/conf_mode/interfaces-sstpc.py @@ -0,0 +1,142 @@ +#!/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 +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.configverify import verify_authentication +from vyos.configverify import verify_vrf +from vyos.ifconfig import SSTPCIf +from vyos.pki import encode_certificate +from vyos.pki import find_chain +from vyos.pki import load_certificate +from vyos.template import render +from vyos.util import call +from vyos.util import dict_search +from vyos.util import is_systemd_service_running +from vyos.util import write_file +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'sstpc'] + ifname, sstpc = get_interface_dict(conf, base) + + # We should only terminate the SSTP client session if critical parameters + # change. All parameters that can be changed on-the-fly (like interface + # description) should not lead to a reconnect! + for options in ['authentication', 'no_peer_dns', 'no_default_route', + 'server', 'ssl']: + if is_node_changed(conf, base + [ifname, options]): + sstpc.update({'shutdown_required': {}}) + # bail out early - no need to further process other nodes + break + + # Load PKI certificates for later processing + sstpc['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + return sstpc + +def verify(sstpc): + if 'deleted' in sstpc: + return None + + verify_authentication(sstpc) + verify_vrf(sstpc) + + if dict_search('ssl.ca_certificate', sstpc) == None: + raise ConfigError('Missing mandatory CA certificate!') + + return None + +def generate(sstpc): + ifname = sstpc['ifname'] + config_sstpc = f'/etc/ppp/peers/{ifname}' + + sstpc['ca_file_path'] = f'/run/sstpc/{ifname}_ca-cert.pem' + + if 'deleted' in sstpc: + for file in [sstpc['ca_file_path'], config_sstpc]: + if os.path.exists(file): + os.unlink(file) + return None + + ca_name = sstpc['ssl']['ca_certificate'] + pki_ca_cert = sstpc['pki']['ca'][ca_name] + + loaded_ca_cert = load_certificate(pki_ca_cert['certificate']) + loaded_ca_certs = {load_certificate(c['certificate']) + for c in sstpc['pki']['ca'].values()} if 'ca' in sstpc['pki'] else {} + + ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs) + + write_file(sstpc['ca_file_path'], '\n'.join(encode_certificate(c) for c in ca_full_chain)) + render(config_sstpc, 'sstp-client/peer.j2', sstpc, permission=0o640) + + return None + +def apply(sstpc): + ifname = sstpc['ifname'] + if 'deleted' in sstpc or 'disable' in sstpc: + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = SSTPCIf(ifname) + p.remove() + call(f'systemctl stop ppp@{ifname}.service') + return None + + # reconnect should only be necessary when specific options change, + # like server, authentication ... (see get_config() for details) + if ((not is_systemd_service_running(f'ppp@{ifname}.service')) or + 'shutdown_required' in sstpc): + + # cleanup system (e.g. FRR routes first) + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = SSTPCIf(ifname) + p.remove() + + call(f'systemctl restart ppp@{ifname}.service') + # When interface comes "live" a hook is called: + # /etc/ppp/ip-up.d/96-vyos-sstpc-callback + # which triggers SSTPCIf.update() + else: + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = SSTPCIf(ifname) + p.update(sstpc) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/etc/ppp/ip-up.d/96-vyos-sstpc-callback b/src/etc/ppp/ip-up.d/96-vyos-sstpc-callback new file mode 100755 index 000000000..4e8804f29 --- /dev/null +++ b/src/etc/ppp/ip-up.d/96-vyos-sstpc-callback @@ -0,0 +1,49 @@ +#!/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 . + +# This is a Python hook script which is invoked whenever a SSTP client session +# goes "ip-up". It will call into our vyos.ifconfig library and will then +# execute common tasks for the SSTP interface. The reason we have to "hook" this +# is that we can not create a sstpcX interface in advance in linux and then +# connect pppd to this already existing interface. + +from sys import argv +from sys import exit + +from vyos.configquery import ConfigTreeQuery +from vyos.configdict import get_interface_dict +from vyos.ifconfig import SSTPCIf + +# When the ppp link comes up, this script is called with the following +# parameters +# $1 the interface name used by pppd (e.g. ppp3) +# $2 the tty device name +# $3 the tty device speed +# $4 the local IP address for the interface +# $5 the remote IP address +# $6 the parameter specified by the 'ipparam' option to pppd + +if (len(argv) < 7): + exit(1) + +interface = argv[6] + +conf = ConfigTreeQuery() +_, sstpc = get_interface_dict(conf.config, ['interfaces', 'sstpc'], interface) + +# Update the config +p = SSTPCIf(interface) +p.update(sstpc) diff --git a/src/op_mode/connect_disconnect.py b/src/op_mode/connect_disconnect.py index 936c20bcb..d39e88bf3 100755 --- a/src/op_mode/connect_disconnect.py +++ b/src/op_mode/connect_disconnect.py @@ -41,7 +41,7 @@ def check_ppp_running(interface): def connect(interface): """ Connect dialer interface """ - if interface.startswith('ppp'): + if interface.startswith('pppoe') or interface.startswith('sstpc'): check_ppp_interface(interface) # Check if interface is already dialed if os.path.isdir(f'/sys/class/net/{interface}'): @@ -62,7 +62,7 @@ def connect(interface): def disconnect(interface): """ Disconnect dialer interface """ - if interface.startswith('ppp'): + if interface.startswith('pppoe') or interface.startswith('sstpc'): check_ppp_interface(interface) # Check if interface is already down -- cgit v1.2.3 From 046bb9ccd56ac5e97c638bb4a9ca856d3d36026a Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Tue, 13 Dec 2022 12:09:03 -0600 Subject: validators: T4798: replace python file-exists validator with file-path --- interface-definitions/include/certificate-ca.xml.i | 2 +- .../include/certificate-key.xml.i | 2 +- interface-definitions/include/certificate.xml.i | 2 +- interface-definitions/protocols-rpki.xml.in | 6 +-- src/validators/file-exists | 61 ---------------------- 5 files changed, 6 insertions(+), 67 deletions(-) delete mode 100755 src/validators/file-exists (limited to 'interface-definitions') diff --git a/interface-definitions/include/certificate-ca.xml.i b/interface-definitions/include/certificate-ca.xml.i index b97378658..3cde2a48d 100644 --- a/interface-definitions/include/certificate-ca.xml.i +++ b/interface-definitions/include/certificate-ca.xml.i @@ -7,7 +7,7 @@ File in /config/auth directory - + diff --git a/interface-definitions/include/certificate-key.xml.i b/interface-definitions/include/certificate-key.xml.i index 1db9dd069..2c4d81fbb 100644 --- a/interface-definitions/include/certificate-key.xml.i +++ b/interface-definitions/include/certificate-key.xml.i @@ -7,7 +7,7 @@ File in /config/auth directory - + diff --git a/interface-definitions/include/certificate.xml.i b/interface-definitions/include/certificate.xml.i index fb5be45cc..6a5b2936c 100644 --- a/interface-definitions/include/certificate.xml.i +++ b/interface-definitions/include/certificate.xml.i @@ -7,7 +7,7 @@ File in /config/auth directory - + diff --git a/interface-definitions/protocols-rpki.xml.in b/interface-definitions/protocols-rpki.xml.in index 4535d3990..0098cacb6 100644 --- a/interface-definitions/protocols-rpki.xml.in +++ b/interface-definitions/protocols-rpki.xml.in @@ -51,7 +51,7 @@ RPKI SSH known hosts file - + @@ -59,7 +59,7 @@ RPKI SSH private key file - + @@ -67,7 +67,7 @@ RPKI SSH public key file path - + diff --git a/src/validators/file-exists b/src/validators/file-exists deleted file mode 100755 index 5cef6b199..000000000 --- a/src/validators/file-exists +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2019 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 . -# -# Description: -# Check if a given file exists on the system. Used for files that -# are referenced from the CLI and need to be preserved during an image upgrade. -# Warn the user if these aren't under /config - -import os -import sys -import argparse - - -def exit(strict, message): - if strict: - sys.exit(f'ERROR: {message}') - print(f'WARNING: {message}', file=sys.stderr) - sys.exit() - - -if __name__ == '__main__': - parser = argparse.ArgumentParser() - parser.add_argument("-d", "--directory", type=str, help="File must be present in this directory.") - parser.add_argument("-e", "--error", action="store_true", help="Tread warnings as errors - change exit code to '1'") - parser.add_argument("file", type=str, help="Path of file to validate") - - args = parser.parse_args() - - # - # Always check if the given file exists - # - if not os.path.exists(args.file): - exit(args.error, f"File '{args.file}' not found") - - # - # Optional check if the file is under a certain directory path - # - if args.directory: - # remove directory path from path to verify - rel_filename = args.file.replace(args.directory, '').lstrip('/') - - if not os.path.exists(args.directory + '/' + rel_filename): - exit(args.error, - f"'{args.file}' lies outside of '{args.directory}' directory.\n" - "It will not get preserved during image upgrade!" - ) - - sys.exit() -- cgit v1.2.3 From f0bc6c62016d285f0645c4b3ba8b1451c40c637f Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 12 Dec 2022 15:06:08 -0600 Subject: validators: T4875: use file-path to replace validator 'interface-name' --- interface-definitions/dns-domain-name.xml.in | 2 +- interface-definitions/high-availability.xml.in | 2 +- .../include/bgp/neighbor-update-source.xml.i | 2 +- .../include/bgp/protocol-common-config.xml.i | 2 +- .../include/constraint/interface-name.xml.in | 4 +++ interface-definitions/include/dhcp-interface.xml.i | 2 +- .../include/generic-interface-broadcast.xml.i | 2 +- .../generic-interface-multi-broadcast.xml.i | 2 +- .../include/generic-interface-multi.xml.i | 2 +- .../include/generic-interface.xml.i | 2 +- .../include/interface/redirect.xml.i | 2 +- .../include/ospf/protocol-common-config.xml.i | 2 +- .../include/ospfv3/protocol-common-config.xml.i | 2 +- interface-definitions/include/rip/interface.xml.i | 2 +- .../include/routing-passive-interface.xml.i | 2 +- .../include/source-interface.xml.i | 2 +- .../include/static/static-route-interface.xml.i | 2 +- .../include/static/static-route.xml.i | 2 +- .../include/static/static-route6.xml.i | 2 +- interface-definitions/interfaces-bonding.xml.in | 4 +-- interface-definitions/protocols-rip.xml.in | 2 +- interface-definitions/protocols-ripng.xml.in | 2 +- interface-definitions/protocols-static-arp.xml.in | 2 +- interface-definitions/qos.xml.in | 2 +- interface-definitions/service-upnp.xml.in | 4 +-- src/validators/interface-name | 34 ---------------------- 26 files changed, 30 insertions(+), 60 deletions(-) create mode 100644 interface-definitions/include/constraint/interface-name.xml.in delete mode 100755 src/validators/interface-name (limited to 'interface-definitions') diff --git a/interface-definitions/dns-domain-name.xml.in b/interface-definitions/dns-domain-name.xml.in index 70b2fb271..9aca38735 100644 --- a/interface-definitions/dns-domain-name.xml.in +++ b/interface-definitions/dns-domain-name.xml.in @@ -25,7 +25,7 @@ - + #include diff --git a/interface-definitions/high-availability.xml.in b/interface-definitions/high-availability.xml.in index 0631acdda..784e51151 100644 --- a/interface-definitions/high-availability.xml.in +++ b/interface-definitions/high-availability.xml.in @@ -199,7 +199,7 @@ Interface name - + #include diff --git a/interface-definitions/include/bgp/neighbor-update-source.xml.i b/interface-definitions/include/bgp/neighbor-update-source.xml.i index 37faf2cce..60c127e8f 100644 --- a/interface-definitions/include/bgp/neighbor-update-source.xml.i +++ b/interface-definitions/include/bgp/neighbor-update-source.xml.i @@ -22,7 +22,7 @@ - + #include diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i index fe192434d..366630f78 100644 --- a/interface-definitions/include/bgp/protocol-common-config.xml.i +++ b/interface-definitions/include/bgp/protocol-common-config.xml.i @@ -926,7 +926,7 @@ - + #include diff --git a/interface-definitions/include/constraint/interface-name.xml.in b/interface-definitions/include/constraint/interface-name.xml.in new file mode 100644 index 000000000..2d1f7b757 --- /dev/null +++ b/interface-definitions/include/constraint/interface-name.xml.in @@ -0,0 +1,4 @@ + +(bond|br|dum|en|ersp|eth|gnv|lan|l2tp|l2tpeth|macsec|peth|ppp|pppoe|pptp|sstp|tun|veth|vti|vtun|vxlan|wg|wlan|wwan)[0-9]+(.\d+)?|lo + + diff --git a/interface-definitions/include/dhcp-interface.xml.i b/interface-definitions/include/dhcp-interface.xml.i index 939b45f15..f5107ba2b 100644 --- a/interface-definitions/include/dhcp-interface.xml.i +++ b/interface-definitions/include/dhcp-interface.xml.i @@ -9,7 +9,7 @@ DHCP interface name - + #include diff --git a/interface-definitions/include/generic-interface-broadcast.xml.i b/interface-definitions/include/generic-interface-broadcast.xml.i index 6f76dde1a..af35a888b 100644 --- a/interface-definitions/include/generic-interface-broadcast.xml.i +++ b/interface-definitions/include/generic-interface-broadcast.xml.i @@ -10,7 +10,7 @@ Interface name - + #include diff --git a/interface-definitions/include/generic-interface-multi-broadcast.xml.i b/interface-definitions/include/generic-interface-multi-broadcast.xml.i index 00638f3b7..1ae38fb43 100644 --- a/interface-definitions/include/generic-interface-multi-broadcast.xml.i +++ b/interface-definitions/include/generic-interface-multi-broadcast.xml.i @@ -10,7 +10,7 @@ Interface name - + #include diff --git a/interface-definitions/include/generic-interface-multi.xml.i b/interface-definitions/include/generic-interface-multi.xml.i index 65aae28ae..16916ff54 100644 --- a/interface-definitions/include/generic-interface-multi.xml.i +++ b/interface-definitions/include/generic-interface-multi.xml.i @@ -10,7 +10,7 @@ Interface name - + #include diff --git a/interface-definitions/include/generic-interface.xml.i b/interface-definitions/include/generic-interface.xml.i index 8b4cf1d65..36ddee417 100644 --- a/interface-definitions/include/generic-interface.xml.i +++ b/interface-definitions/include/generic-interface.xml.i @@ -10,7 +10,7 @@ Interface name - + #include diff --git a/interface-definitions/include/interface/redirect.xml.i b/interface-definitions/include/interface/redirect.xml.i index 3be9ee16b..8df8957ac 100644 --- a/interface-definitions/include/interface/redirect.xml.i +++ b/interface-definitions/include/interface/redirect.xml.i @@ -10,7 +10,7 @@ Interface name - + #include diff --git a/interface-definitions/include/ospf/protocol-common-config.xml.i b/interface-definitions/include/ospf/protocol-common-config.xml.i index 0615063af..06609c10e 100644 --- a/interface-definitions/include/ospf/protocol-common-config.xml.i +++ b/interface-definitions/include/ospf/protocol-common-config.xml.i @@ -358,7 +358,7 @@ Interface name - + #include diff --git a/interface-definitions/include/ospfv3/protocol-common-config.xml.i b/interface-definitions/include/ospfv3/protocol-common-config.xml.i index 630534eea..c0aab912d 100644 --- a/interface-definitions/include/ospfv3/protocol-common-config.xml.i +++ b/interface-definitions/include/ospfv3/protocol-common-config.xml.i @@ -118,7 +118,7 @@ Interface used for routing information exchange - + #include diff --git a/interface-definitions/include/rip/interface.xml.i b/interface-definitions/include/rip/interface.xml.i index baeceac1c..e0792cdc1 100644 --- a/interface-definitions/include/rip/interface.xml.i +++ b/interface-definitions/include/rip/interface.xml.i @@ -10,7 +10,7 @@ Interface name - + #include diff --git a/interface-definitions/include/routing-passive-interface.xml.i b/interface-definitions/include/routing-passive-interface.xml.i index 095b683de..fe229aebe 100644 --- a/interface-definitions/include/routing-passive-interface.xml.i +++ b/interface-definitions/include/routing-passive-interface.xml.i @@ -16,7 +16,7 @@ (default) - + #include diff --git a/interface-definitions/include/source-interface.xml.i b/interface-definitions/include/source-interface.xml.i index a9c2a0f9d..4c1fddb57 100644 --- a/interface-definitions/include/source-interface.xml.i +++ b/interface-definitions/include/source-interface.xml.i @@ -10,7 +10,7 @@ - + #include diff --git a/interface-definitions/include/static/static-route-interface.xml.i b/interface-definitions/include/static/static-route-interface.xml.i index ed4f455e5..cc7a92612 100644 --- a/interface-definitions/include/static/static-route-interface.xml.i +++ b/interface-definitions/include/static/static-route-interface.xml.i @@ -10,7 +10,7 @@ Gateway interface name - + #include diff --git a/interface-definitions/include/static/static-route.xml.i b/interface-definitions/include/static/static-route.xml.i index 04ee999c7..aeb2044c9 100644 --- a/interface-definitions/include/static/static-route.xml.i +++ b/interface-definitions/include/static/static-route.xml.i @@ -26,7 +26,7 @@ Gateway interface name - + #include diff --git a/interface-definitions/include/static/static-route6.xml.i b/interface-definitions/include/static/static-route6.xml.i index 6131ac7fe..d5e7a25bc 100644 --- a/interface-definitions/include/static/static-route6.xml.i +++ b/interface-definitions/include/static/static-route6.xml.i @@ -25,7 +25,7 @@ Gateway interface name - + #include diff --git a/interface-definitions/interfaces-bonding.xml.in b/interface-definitions/interfaces-bonding.xml.in index 96e0e5d89..a8a558348 100644 --- a/interface-definitions/interfaces-bonding.xml.in +++ b/interface-definitions/interfaces-bonding.xml.in @@ -199,7 +199,7 @@ Interface name - + #include @@ -218,7 +218,7 @@ Interface name - + #include diff --git a/interface-definitions/protocols-rip.xml.in b/interface-definitions/protocols-rip.xml.in index 2195b0316..33aae5015 100644 --- a/interface-definitions/protocols-rip.xml.in +++ b/interface-definitions/protocols-rip.xml.in @@ -39,7 +39,7 @@ - + #include diff --git a/interface-definitions/protocols-ripng.xml.in b/interface-definitions/protocols-ripng.xml.in index d7e4b2514..cd35dbf53 100644 --- a/interface-definitions/protocols-ripng.xml.in +++ b/interface-definitions/protocols-ripng.xml.in @@ -40,7 +40,7 @@ - + #include diff --git a/interface-definitions/protocols-static-arp.xml.in b/interface-definitions/protocols-static-arp.xml.in index 8b1b3b5e1..52caf435a 100644 --- a/interface-definitions/protocols-static-arp.xml.in +++ b/interface-definitions/protocols-static-arp.xml.in @@ -20,7 +20,7 @@ Interface name - + #include diff --git a/interface-definitions/qos.xml.in b/interface-definitions/qos.xml.in index e2dbcbeef..dc807781e 100644 --- a/interface-definitions/qos.xml.in +++ b/interface-definitions/qos.xml.in @@ -16,7 +16,7 @@ Interface name - + #include diff --git a/interface-definitions/service-upnp.xml.in b/interface-definitions/service-upnp.xml.in index ec23d87df..79d8ae42e 100644 --- a/interface-definitions/service-upnp.xml.in +++ b/interface-definitions/service-upnp.xml.in @@ -24,7 +24,7 @@ - + #include @@ -119,7 +119,7 @@ - + #include diff --git a/src/validators/interface-name b/src/validators/interface-name deleted file mode 100755 index 105815eee..000000000 --- a/src/validators/interface-name +++ /dev/null @@ -1,34 +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 os -import re - -from sys import argv -from sys import exit - -pattern = '^(bond|br|dum|en|ersp|eth|gnv|lan|l2tp|l2tpeth|macsec|peth|ppp|pppoe|pptp|sstp|tun|vti|vtun|vxlan|wg|wlan|wwan)[0-9]+(.\d+)?|lo$' - -if __name__ == '__main__': - if len(argv) != 2: - exit(1) - interface = argv[1] - - if re.match(pattern, interface): - exit(0) - if os.path.exists(f'/sys/class/net/{interface}'): - exit(0) - exit(1) -- cgit v1.2.3 From 932af7f098808009f626c788deb9e1d1c8bf3426 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Mon, 13 Jun 2022 15:40:11 +0000 Subject: routing: T1237: Add new feature failover route Failover route allows to install static routes to the kernel routing table only if required target or gateway is alive When target or gateway doesn't respond for ICMP/ARP checks this route deleted from the routing table Routes are marked as protocol 'failover' (rt_protos) cat /etc/iproute2/rt_protos.d/failover.conf 111 failover ip route add 203.0.113.1 metric 2 via 192.0.2.1 dev eth0 proto failover $ sudo ip route show proto failover 203.0.113.1 via 192.0.2.1 dev eth0 metric 1 So we can safely flush such routes --- .../protocols/systemd_vyos_failover_service.j2 | 11 ++ interface-definitions/protocols-failover.xml.in | 114 +++++++++++++ src/conf_mode/protocols_failover.py | 121 ++++++++++++++ src/helpers/vyos-failover.py | 184 +++++++++++++++++++++ 4 files changed, 430 insertions(+) create mode 100644 data/templates/protocols/systemd_vyos_failover_service.j2 create mode 100644 interface-definitions/protocols-failover.xml.in create mode 100755 src/conf_mode/protocols_failover.py create mode 100755 src/helpers/vyos-failover.py (limited to 'interface-definitions') diff --git a/data/templates/protocols/systemd_vyos_failover_service.j2 b/data/templates/protocols/systemd_vyos_failover_service.j2 new file mode 100644 index 000000000..e6501e0f5 --- /dev/null +++ b/data/templates/protocols/systemd_vyos_failover_service.j2 @@ -0,0 +1,11 @@ +[Unit] +Description=Failover route service +After=vyos-router.service + +[Service] +Type=simple +Restart=always +ExecStart=/usr/bin/python3 /usr/libexec/vyos/vyos-failover.py --config /run/vyos-failover.conf + +[Install] +WantedBy=multi-user.target diff --git a/interface-definitions/protocols-failover.xml.in b/interface-definitions/protocols-failover.xml.in new file mode 100644 index 000000000..900c76eab --- /dev/null +++ b/interface-definitions/protocols-failover.xml.in @@ -0,0 +1,114 @@ + + + + + + + Failover Routing + 490 + + + + + Failover IPv4 route + + ipv4net + IPv4 failover route + + + + + + + + + Next-hop IPv4 router address + + ipv4 + Next-hop router address + + + + + + + + + Check target options + + + #include + + + Check target address + + ipv4 + Address to check + + + + + + + + + Timeout between checks + + u32:1-300 + Timeout in seconds between checks + + + + + + 10 + + + + Check type + + arp icmp tcp + + + arp + Check target by ARP + + + icmp + Check target by ICMP + + + tcp + Check target by TCP + + + (arp|icmp|tcp) + + + icmp + + + + #include + + + Route metric for this gateway + + u32:1-255 + Route metric + + + + + + 1 + + + + + + + + + + diff --git a/src/conf_mode/protocols_failover.py b/src/conf_mode/protocols_failover.py new file mode 100755 index 000000000..048ba7a89 --- /dev/null +++ b/src/conf_mode/protocols_failover.py @@ -0,0 +1,121 @@ +#!/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 json + +from pathlib import Path + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.template import render +from vyos.util import call +from vyos.xml import defaults +from vyos import ConfigError +from vyos import airbag + +airbag.enable() + + +service_name = 'vyos-failover' +service_conf = Path(f'/run/{service_name}.conf') +systemd_service = '/etc/systemd/system/vyos-failover.service' +rt_proto_failover = '/etc/iproute2/rt_protos.d/failover.conf' + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['protocols', 'failover'] + failover = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + # Set default values only if we set config + if failover.get('route'): + for route, route_config in failover.get('route').items(): + for next_hop, next_hop_config in route_config.get('next_hop').items(): + default_values = defaults(base + ['route']) + failover['route'][route]['next_hop'][next_hop] = dict_merge( + default_values['next_hop'], failover['route'][route]['next_hop'][next_hop]) + + return failover + +def verify(failover): + # bail out early - looks like removal from running config + if not failover: + return None + + if 'route' not in failover: + raise ConfigError(f'Failover "route" is mandatory!') + + for route, route_config in failover['route'].items(): + if not route_config.get('next_hop'): + raise ConfigError(f'Next-hop for "{route}" is mandatory!') + + for next_hop, next_hop_config in route_config.get('next_hop').items(): + if 'interface' not in next_hop_config: + raise ConfigError(f'Interface for route "{route}" next-hop "{next_hop}" is mandatory!') + + if not next_hop_config.get('check'): + raise ConfigError(f'Check target for next-hop "{next_hop}" is mandatory!') + + if 'target' not in next_hop_config['check']: + raise ConfigError(f'Check target for next-hop "{next_hop}" is mandatory!') + + check_type = next_hop_config['check']['type'] + if check_type == 'tcp' and 'port' not in next_hop_config['check']: + raise ConfigError(f'Check port for next-hop "{next_hop}" and type TCP is mandatory!') + + return None + +def generate(failover): + if not failover: + service_conf.unlink(missing_ok=True) + return None + + # Add own rt_proto 'failover' + # Helps to detect all own routes 'proto failover' + with open(rt_proto_failover, 'w') as f: + f.write('111 failover\n') + + # Write configuration file + conf_json = json.dumps(failover, indent=4) + service_conf.write_text(conf_json) + render(systemd_service, 'protocols/systemd_vyos_failover_service.j2', failover) + + return None + +def apply(failover): + if not failover: + call(f'systemctl stop {service_name}.service') + call('ip route flush protocol failover') + else: + call('systemctl daemon-reload') + call(f'systemctl restart {service_name}.service') + call(f'ip route flush protocol failover') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/helpers/vyos-failover.py b/src/helpers/vyos-failover.py new file mode 100755 index 000000000..1ac193423 --- /dev/null +++ b/src/helpers/vyos-failover.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import argparse +import json +import subprocess +import socket +import time + +from vyos.util import rc_cmd +from pathlib import Path +from systemd import journal + + +my_name = Path(__file__).stem + + +def get_best_route_options(route, debug=False): + """ + Return current best route ('gateway, interface, metric) + + % get_best_route_options('203.0.113.1') + ('192.168.0.1', 'eth1', 1) + + % get_best_route_options('203.0.113.254') + (None, None, None) + """ + rc, data = rc_cmd(f'ip --detail --json route show protocol failover {route}') + if rc == 0: + data = json.loads(data) + if len(data) == 0: + print(f'\nRoute {route} for protocol failover was not found') + return None, None, None + # Fake metric 999 by default + # Search route with the lowest metric + best_metric = 999 + for entry in data: + if debug: print('\n', entry) + metric = entry.get('metric') + gateway = entry.get('gateway') + iface = entry.get('dev') + if metric < best_metric: + best_metric = metric + best_gateway = gateway + best_interface = iface + if debug: + print(f'### Best_route exists: {route}, best_gateway: {best_gateway}, ' + f'best_metric: {best_metric}, best_iface: {best_interface}') + return best_gateway, best_interface, best_metric + +def is_port_open(ip, port): + """ + Check connection to remote host and port + Return True if host alive + + % is_port_open('example.com', 8080) + True + """ + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) + s.settimeout(2) + try: + s.connect((ip, int(port))) + s.shutdown(socket.SHUT_RDWR) + return True + except: + return False + finally: + s.close() + +def is_target_alive(target=None, iface='', proto='icmp', port=None, debug=False): + """ + Host availability check by ICMP, ARP, TCP + Return True if target checks is successful + + % is_target_alive('192.0.2.1', 'eth1', proto='arp') + True + """ + if iface != '': + iface = f'-I {iface}' + if proto == 'icmp': + command = f'/usr/bin/ping -q {target} {iface} -n -c 2 -W 1' + rc, response = rc_cmd(command) + if debug: print(f' [ CHECK-TARGET ]: [{command}] -- return-code [RC: {rc}]') + if rc == 0: + return True + elif proto == 'arp': + command = f'/usr/bin/arping -b -c 2 -f -w 1 -i 1 {iface} {target}' + rc, response = rc_cmd(command) + if debug: print(f' [ CHECK-TARGET ]: [{command}] -- return-code [RC: {rc}]') + if rc == 0: + return True + elif proto == 'tcp' and port is not None: + return True if is_port_open(target, port) else False + else: + return False + + +if __name__ == '__main__': + # Parse command arguments and get config + parser = argparse.ArgumentParser() + parser.add_argument('-c', + '--config', + action='store', + help='Path to protocols failover configuration', + required=True, + type=Path) + + args = parser.parse_args() + try: + config_path = Path(args.config) + config = json.loads(config_path.read_text()) + except Exception as err: + print( + f'Configuration file "{config_path}" does not exist or malformed: {err}' + ) + exit(1) + + # Useful debug info to console, use debug = True + # sudo systemctl stop vyos-failover.service + # sudo /usr/libexec/vyos/vyos-failover.py --config /run/vyos-failover.conf + debug = False + + while(True): + + for route, route_config in config.get('route').items(): + + exists_route = exists_gateway, exists_iface, exists_metric = get_best_route_options(route, debug=debug) + + for next_hop, nexthop_config in route_config.get('next_hop').items(): + conf_iface = nexthop_config.get('interface') + conf_metric = int(nexthop_config.get('metric')) + port = nexthop_config.get('check').get('port') + port_opt = f'port {port}' if port else '' + proto = nexthop_config.get('check').get('type') + target = nexthop_config.get('check').get('target') + timeout = nexthop_config.get('check').get('timeout') + + # Best route not fonund in the current routing table + if exists_route == (None, None, None): + if debug: print(f" [NEW_ROUTE_DETECTED] route: [{route}]") + # Add route if check-target alive + if is_target_alive(target, conf_iface, proto, port, debug=debug): + if debug: print(f' [ ADD ] -- ip route add {route} via {next_hop} dev {conf_iface} ' + f'metric {conf_metric} proto failover\n###') + rc, command = rc_cmd(f'ip route add {route} via {next_hop} dev {conf_iface} ' + f'metric {conf_metric} proto failover') + # If something is wrong and gateway not added + # Example: Error: Next-hop has invalid gateway. + if rc !=0: + if debug: print(f'{command} -- return-code [RC: {rc}] {next_hop} dev {conf_iface}') + else: + journal.send(f'ip route add {route} via {next_hop} dev {conf_iface} ' + f'metric {conf_metric} proto failover', SYSLOG_IDENTIFIER=my_name) + else: + if debug: print(f' [ TARGET_FAIL ] target checks fails for [{target}], do nothing') + journal.send(f'Check fail for route {route} target {target} proto {proto} ' + f'{port_opt}', SYSLOG_IDENTIFIER=my_name) + + # Route was added, check if the target is alive + # We should delete route if check fails only if route exists in the routing table + if not is_target_alive(target, conf_iface, proto, port, debug=debug) and \ + exists_route != (None, None, None): + if debug: + print(f'Nexh_hop {next_hop} fail, target not response') + print(f' [ DEL ] -- ip route del {route} via {next_hop} dev {conf_iface} ' + f'metric {conf_metric} proto failover [DELETE]') + rc_cmd(f'ip route del {route} via {next_hop} dev {conf_iface} metric {conf_metric} proto failover') + journal.send(f'ip route del {route} via {next_hop} dev {conf_iface} ' + f'metric {conf_metric} proto failover', SYSLOG_IDENTIFIER=my_name) + + time.sleep(int(timeout)) -- cgit v1.2.3 From 1c9bd9375765c3d0a9d603286bb9977b99a5535c Mon Sep 17 00:00:00 2001 From: initramfs Date: Thu, 15 Dec 2022 17:04:41 +0800 Subject: firewall: T4882: add missing ICMPv6 type names --- .../include/firewall/icmpv6-type-name.xml.i | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) (limited to 'interface-definitions') diff --git a/interface-definitions/include/firewall/icmpv6-type-name.xml.i b/interface-definitions/include/firewall/icmpv6-type-name.xml.i index a2e68abfb..e17a20e17 100644 --- a/interface-definitions/include/firewall/icmpv6-type-name.xml.i +++ b/interface-definitions/include/firewall/icmpv6-type-name.xml.i @@ -3,7 +3,7 @@ ICMPv6 type-name - destination-unreachable packet-too-big time-exceeded echo-request echo-reply mld-listener-query mld-listener-report mld-listener-reduction nd-router-solicit nd-router-advert nd-neighbor-solicit nd-neighbor-advert nd-redirect parameter-problem router-renumbering + destination-unreachable packet-too-big time-exceeded echo-request echo-reply mld-listener-query mld-listener-report mld-listener-reduction nd-router-solicit nd-router-advert nd-neighbor-solicit nd-neighbor-advert nd-redirect parameter-problem router-renumbering ind-neighbor-solicit ind-neighbor-advert mld2-listener-report destination-unreachable @@ -65,8 +65,20 @@ router-renumbering ICMPv6 type 138: router-renumbering + + ind-neighbor-solicit + ICMPv6 type 141: ind-neighbor-solicit + + + ind-neighbor-advert + ICMPv6 type 142: ind-neighbor-advert + + + mld2-listener-report + ICMPv6 type 143: mld2-listener-report + - (destination-unreachable|packet-too-big|time-exceeded|echo-request|echo-reply|mld-listener-query|mld-listener-report|mld-listener-reduction|nd-router-solicit|nd-router-advert|nd-neighbor-solicit|nd-neighbor-advert|nd-redirect|parameter-problem|router-renumbering) + (destination-unreachable|packet-too-big|time-exceeded|echo-request|echo-reply|mld-listener-query|mld-listener-report|mld-listener-reduction|nd-router-solicit|nd-router-advert|nd-neighbor-solicit|nd-neighbor-advert|nd-redirect|parameter-problem|router-renumbering|ind-neighbor-solicit|ind-neighbor-advert|mld2-listener-report) -- cgit v1.2.3 From 13071a4a534b2101120e0c007f0fbb5174b79052 Mon Sep 17 00:00:00 2001 From: Sander Klein Date: Fri, 16 Dec 2022 22:55:57 +0100 Subject: T4809: radvd: Allow the use of AdvRASrcAddress This add the AdvRASrcAddress configuration option to configure a source address for the router advertisements. The source address still must be configured on the system. This is useful for VRRP setups where you want fe80::1 on the VRRP interface for cleaner VRRP failovers. --- data/templates/router-advert/radvd.conf.j2 | 7 +++++++ interface-definitions/service-router-advert.xml.in | 13 +++++++++++++ 2 files changed, 20 insertions(+) (limited to 'interface-definitions') diff --git a/data/templates/router-advert/radvd.conf.j2 b/data/templates/router-advert/radvd.conf.j2 index a464795ad..f4b384958 100644 --- a/data/templates/router-advert/radvd.conf.j2 +++ b/data/templates/router-advert/radvd.conf.j2 @@ -43,6 +43,13 @@ interface {{ iface }} { }; {% endfor %} {% endif %} +{% if iface_config.source_address is vyos_defined %} + AdvRASrcAddress { +{% for source_address in iface_config.source_address %} + {{ source_address }} +{% endfor %} + }; +{% endif %} {% if iface_config.prefix is vyos_defined %} {% for prefix, prefix_options in iface_config.prefix.items() %} prefix {{ prefix }} { diff --git a/interface-definitions/service-router-advert.xml.in b/interface-definitions/service-router-advert.xml.in index 87ec512d6..8b7364a8c 100644 --- a/interface-definitions/service-router-advert.xml.in +++ b/interface-definitions/service-router-advert.xml.in @@ -305,6 +305,19 @@ + + + Use IPv6 address as source address. Useful with VRRP. + + ipv6 + IPv6 address to be advertized (must be configured on interface) + + + + + + + Time, in milliseconds, that a node assumes a neighbor is reachable after having received a reachability confirmation -- cgit v1.2.3