diff --git a/data/op-mode-standardized.json b/data/op-mode-standardized.json index 35587b63c9..170f0d2599 100644 --- a/data/op-mode-standardized.json +++ b/data/op-mode-standardized.json @@ -19,7 +19,6 @@ "multicast.py", "nat.py", "neighbor.py", -"nhrp.py", "openconnect.py", "openvpn.py", "otp.py", diff --git a/data/templates/frr/daemons.frr.tmpl b/data/templates/frr/daemons.frr.tmpl index 3506528d2e..835dc382b8 100644 --- a/data/templates/frr/daemons.frr.tmpl +++ b/data/templates/frr/daemons.frr.tmpl @@ -30,7 +30,7 @@ isisd=yes pimd=no pim6d=yes ldpd=yes -nhrpd=no +nhrpd=yes eigrpd=no babeld=yes sharpd=no diff --git a/data/templates/frr/nhrpd.frr.j2 b/data/templates/frr/nhrpd.frr.j2 new file mode 100644 index 0000000000..2b2aba2563 --- /dev/null +++ b/data/templates/frr/nhrpd.frr.j2 @@ -0,0 +1,62 @@ +! +{% if redirect is vyos_defined %} +nhrp nflog-group {{ redirect }} +{% endif %} +{% if multicast is vyos_defined %} +nhrp multicast-nflog-group {{ multicast }} +{% endif %} +{% if tunnel is vyos_defined %} +{% for iface, iface_config in tunnel.items() %} +interface {{ iface }} +{% if iface_config.authentication is vyos_defined %} + ip nhrp authentication {{ iface_config.authentication }} +{% endif %} +{% if iface_config.holdtime is vyos_defined %} + ip nhrp holdtime {{ iface_config.holdtime }} +{% endif %} +{% if iface_config.map.tunnel_ip is vyos_defined %} +{% for tunip, tunip_config in iface_config.map.tunnel_ip.items() %} +{% if tunip_config.nbma is vyos_defined %} + ip nhrp map {{ tunip }} {{ tunip_config.nbma }} +{% endif %} +{% endfor %} +{% endif %} +{% if iface_config.mtu is vyos_defined %} + ip nhrp mtu {{ iface_config.mtu }} +{% endif %} +{% if iface_config.multicast is vyos_defined %} +{% for multicast_ip in iface_config.multicast %} + ip nhrp map multicast {{ multicast_ip }} +{% endfor %} +{% endif %} +{% if iface_config.nhs.tunnel_ip is vyos_defined %} +{% for tunip, tunip_config in iface_config.nhs.tunnel_ip.items() %} +{% if tunip_config.nbma is vyos_defined %} +{% for nbmaip in tunip_config.nbma %} + ip nhrp nhs {{ tunip }} nbma {{ nbmaip }} +{% endfor %} +{% endif %} +{% endfor %} +{% endif %} +{% if iface_config.network_id is vyos_defined %} + ip nhrp network-id {{ iface_config.network_id }} +{% endif %} +{% if iface_config.redirect is vyos_defined %} + ip nhrp redirect +{% endif %} +{% if iface_config.registration_no_unique is vyos_defined %} + ip nhrp registration no-unique +{% endif %} +{% if iface_config.shortcut is vyos_defined %} + ip nhrp shortcut +{% endif %} +{% if iface_config.security_profile is vyos_defined %} + tunnel protection vici profile dmvpn-{{ iface_config.security_profile }}-{{ iface }}-child +{% endif %} +exit +! +{% endfor %} +{% endif %} +! +exit +! diff --git a/data/templates/frr/nhrpd_nftables.conf.j2 b/data/templates/frr/nhrpd_nftables.conf.j2 new file mode 100644 index 0000000000..6ae35ef52c --- /dev/null +++ b/data/templates/frr/nhrpd_nftables.conf.j2 @@ -0,0 +1,46 @@ +#!/usr/sbin/nft -f + +table ip vyos_nhrp_multicast +table ip vyos_nhrp_redirect +delete table ip vyos_nhrp_multicast +delete table ip vyos_nhrp_redirect +{% if multicast is vyos_defined %} +table ip vyos_nhrp_multicast { + chain VYOS_NHRP_MULTICAST_OUTPUT { + type filter hook output priority filter+10; policy accept; +{% if tunnel is vyos_defined %} +{% for tun, tunnel_conf in tunnel.items() %} +{% if tunnel_conf.multicast is vyos_defined %} + oifname "{{ tun }}" ip daddr 224.0.0.0/24 counter log group {{ multicast }} + oifname "{{ tun }}" ip daddr 224.0.0.0/24 counter drop +{% endif %} +{% endfor %} +{% endif %} + } + chain VYOS_NHRP_MULTICAST_FORWARD { + type filter hook forward priority filter+10; policy accept; +{% if tunnel is vyos_defined %} +{% for tun, tunnel_conf in tunnel.items() %} +{% if tunnel_conf.multicast is vyos_defined %} + oifname "{{ tun }}" ip daddr 224.0.0.0/4 counter log group {{ multicast }} + oifname "{{ tun }}" ip daddr 224.0.0.0/4 counter drop +{% endif %} +{% endfor %} +{% endif %} + } +} +{% endif %} +{% if redirect is vyos_defined %} +table ip vyos_nhrp_redirect { + chain VYOS_NHRP_REDIRECT_FORWARD { + type filter hook forward priority filter+10; policy accept; +{% if tunnel is vyos_defined %} +{% for tun, tunnel_conf in tunnel.items() %} +{% if tunnel_conf.redirect is vyos_defined %} + iifname "{{ tun }}" oifname "{{ tun }}" meter loglimit-0 size 65535 { ip daddr & 255.255.255.0 . ip saddr & 255.255.255.0 timeout 1m limit rate 4/minute burst 1 packets } counter log group {{ redirect }} +{% endif %} +{% endfor %} +{% endif %} + } +} +{% endif %} diff --git a/data/templates/ipsec/swanctl/profile.j2 b/data/templates/ipsec/swanctl/profile.j2 index 8519a84f8b..6a04b038a6 100644 --- a/data/templates/ipsec/swanctl/profile.j2 +++ b/data/templates/ipsec/swanctl/profile.j2 @@ -22,16 +22,16 @@ } {% endif %} children { - dmvpn { + dmvpn-{{ name }}-{{ interface }}-child { esp_proposals = {{ esp | get_esp_ike_cipher(ike) | join(',') }} rekey_time = {{ esp.lifetime }}s rand_time = 540s local_ts = dynamic[gre] remote_ts = dynamic[gre] mode = {{ esp.mode }} -{% if ike.dead_peer_detection.action is vyos_defined %} - dpd_action = {{ ike.dead_peer_detection.action }} -{% endif %} + dpd_action = clear + close_action = none + start_action = none {% if esp.compression is vyos_defined('enable') %} ipcomp = yes {% endif %} diff --git a/data/templates/nhrp/nftables.conf.j2 b/data/templates/nhrp/nftables.conf.j2 deleted file mode 100644 index a0d1f6d4cb..0000000000 --- a/data/templates/nhrp/nftables.conf.j2 +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/sbin/nft -f - -{% if first_install is not vyos_defined %} -delete table ip vyos_nhrp_filter -{% endif %} -table ip vyos_nhrp_filter { - chain VYOS_NHRP_OUTPUT { - type filter hook output priority 10; policy accept; -{% if tunnel is vyos_defined %} -{% for tun, tunnel_conf in tunnel.items() %} -{% if if_tunnel[tun].source_address is vyos_defined %} - ip protocol gre ip saddr {{ if_tunnel[tun].source_address }} ip daddr 224.0.0.0/4 counter drop comment "VYOS_NHRP_{{ tun }}" -{% endif %} -{% endfor %} -{% endif %} - } -} diff --git a/data/templates/nhrp/opennhrp.conf.j2 b/data/templates/nhrp/opennhrp.conf.j2 deleted file mode 100644 index c040a8f148..0000000000 --- a/data/templates/nhrp/opennhrp.conf.j2 +++ /dev/null @@ -1,42 +0,0 @@ -{# j2lint: disable=jinja-variable-format #} -# Created by VyOS - manual changes will be overwritten - -{% if tunnel is vyos_defined %} -{% for name, tunnel_conf in tunnel.items() %} -{% set type = 'spoke' if tunnel_conf.map is vyos_defined or tunnel_conf.dynamic_map is vyos_defined else 'hub' %} -{% set profile_name = profile_map[name] if profile_map is vyos_defined and name in profile_map else '' %} -interface {{ name }} #{{ type }} {{ profile_name }} -{% if tunnel_conf.map is vyos_defined %} -{% for map, map_conf in tunnel_conf.map.items() %} -{% set cisco = ' cisco' if map_conf.cisco is vyos_defined else '' %} -{% set register = ' register' if map_conf.register is vyos_defined else '' %} - map {{ map }} {{ map_conf.nbma_address }}{{ register }}{{ cisco }} -{% endfor %} -{% endif %} -{% if tunnel_conf.dynamic_map is vyos_defined %} -{% for map, map_conf in tunnel_conf.dynamic_map.items() %} - dynamic-map {{ map }} {{ map_conf.nbma_domain_name }} -{% endfor %} -{% endif %} -{% if tunnel_conf.cisco_authentication is vyos_defined %} - cisco-authentication {{ tunnel_conf.cisco_authentication }} -{% endif %} -{% if tunnel_conf.holding_time is vyos_defined %} - holding-time {{ tunnel_conf.holding_time }} -{% endif %} -{% if tunnel_conf.multicast is vyos_defined %} - multicast {{ tunnel_conf.multicast }} -{% endif %} -{% for key in ['non_caching', 'redirect', 'shortcut', 'shortcut_destination'] %} -{% if key in tunnel_conf %} - {{ key | replace("_", "-") }} -{% endif %} -{% endfor %} -{% if tunnel_conf.shortcut_target is vyos_defined %} -{% for target, shortcut_conf in tunnel_conf.shortcut_target.items() %} - shortcut-target {{ target }}{{ ' holding-time ' + shortcut_conf.holding_time if shortcut_conf.holding_time is vyos_defined }} -{% endfor %} -{% endif %} - -{% endfor %} -{% endif %} diff --git a/debian/control b/debian/control index b1cb5c4ef0..bfef9e6008 100644 --- a/debian/control +++ b/debian/control @@ -171,9 +171,6 @@ Depends: frr-rpki-rtrlib, frr-snmp, # End "protocols *" -# For "protocols nhrp" (part of DMVPN) - opennhrp, -# End "protocols nhrp" # For "protocols igmp-proxy" igmpproxy, # End "protocols igmp-proxy" diff --git a/interface-definitions/include/version/nhrp-version.xml.i b/interface-definitions/include/version/nhrp-version.xml.i new file mode 100644 index 0000000000..7f6f3c4f74 --- /dev/null +++ b/interface-definitions/include/version/nhrp-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/protocols_nhrp.xml.in b/interface-definitions/protocols_nhrp.xml.in index d7663c095c..5304fbd78d 100644 --- a/interface-definitions/protocols_nhrp.xml.in +++ b/interface-definitions/protocols_nhrp.xml.in @@ -20,115 +20,163 @@ - + - Pass phrase for cisco authentication - - txt - Pass phrase for cisco authentication - - - [^[:space:]]{1,8} - - Password should contain up to eight non-whitespace characters - - - - - Set an HUB tunnel address - - ipv4net - Set the IP address and prefix length - + Map tunnel IP to NBMA - + - Set HUB fqdn (nbma-address - fqdn) + Set a NHRP tunnel address - <fqdn> - Set the external HUB fqdn + ipv4 + Set the IP address to map + + + - + + + + Set NHRP NBMA address to map + + local + + + ipv4 + Set the IP address to map + + + local + Set the local address + + + + (local) + + + + + - - + + - Holding time in seconds - - - - - Set an HUB tunnel address + Map tunnel IP to NBMA of Next Hop Server - - - If the statically mapped peer is running Cisco IOS, specify this - - - - + - Set HUB address (nbma-address - external hub address or fqdn) - - - - - Specifies that Registration Request should be sent to this peer on startup - + Set a NHRP NHS tunnel address + + dynamic + + + ipv4 + Set the IP address to map + + + dynamic + Set Next Hop Server to have a dynamic address + + + + (dynamic) + - + + + + Set NHRP NBMA address of NHS + + ipv4 + Set the IP address to map + + + + + + + + + - + - Set multicast for NHRP + Map multicast to NBMA - dynamic nhs + dynamic + + ipv4 + Set the IP address to map(IP|FQDN) + + + dynamic + NBMA address is learnt dynamically + - (dynamic|nhs) + + (dynamic) + - + - This can be used to reduce memory consumption on big NBMA subnets + Don't set unique flag - + - Enable sending of Cisco style NHRP Traffic Indication packets - + NHRP authentication + + txt + Pass phrase for NHRP authentication + + + [^[:space:]]{1,8} + + Password should contain up to eight non-whitespace characters - + - This instructs opennhrp to reply with authorative answers on NHRP Resolution Requests destined to addresses in this interface - + Holding time in seconds + + u32:1-65000 + ring buffer size + + + + - + - Defines an off-NBMA network prefix for which the GRE interface will act as a gateway + Enable sending of Cisco style NHRP Traffic Indication packets + - - - - Holding time in seconds - - - - + Enable creation of shortcut routes. A received NHRP Traffic Indication will trigger the resolution and establishment of a shortcut route + #include + + + NHRP network id + + <1-4294967295> + NHRP network id + + + diff --git a/interface-definitions/xml-component-version.xml.in b/interface-definitions/xml-component-version.xml.in index 67d86a1d02..2a3356a2e9 100644 --- a/interface-definitions/xml-component-version.xml.in +++ b/interface-definitions/xml-component-version.xml.in @@ -27,6 +27,7 @@ #include #include #include + #include #include #include #include diff --git a/op-mode-definitions/nhrp.xml.in b/op-mode-definitions/nhrp.xml.in index 11a4b8814c..515c0af39d 100644 --- a/op-mode-definitions/nhrp.xml.in +++ b/op-mode-definitions/nhrp.xml.in @@ -2,38 +2,26 @@ - - - Clear/Purge NHRP entries - + - + - Clear all non-permanent entries + Clear/Purge NHRP entries - + - Clear all non-permanent entries + Clear Dynamic cache entries - sudo opennhrpctl flush dev $5 || echo OpenNHRP is not running. - - - sudo opennhrpctl flush || echo OpenNHRP is not running. - - - - Purge entries from NHRP cache - - - + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + - Purge all entries from NHRP cache + Clear Shortcut entries - sudo opennhrpctl purge dev $5 || echo OpenNHRP is not running. - + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + - sudo opennhrpctl purge || echo OpenNHRP is not running. @@ -41,25 +29,38 @@ - + - Show NHRP (Next Hop Resolution Protocol) information + Show IPv4 routing information - + - Show NHRP interface connection information + Show NHRP (Next Hop Resolution Protocol) information - ${vyos_op_scripts_dir}/nhrp.py show_interface - - - - Show NHRP tunnel connection information - - ${vyos_op_scripts_dir}/nhrp.py show_tunnel - + + + + Show NHRP interface connection information + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + + + Show NHRP tunnel connection information + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + + + Show NHRP tunnel connection information + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + + - + diff --git a/python/vyos/frrender.py b/python/vyos/frrender.py index 544983b2c4..44991a0940 100644 --- a/python/vyos/frrender.py +++ b/python/vyos/frrender.py @@ -1,4 +1,4 @@ -# Copyright 2024 VyOS maintainers and contributors +# Copyright 2024-2025 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 @@ -52,6 +52,7 @@ def debug(message): rip_daemon = 'ripd' ripng_daemon = 'ripngd' zebra_daemon = 'zebra' +nhrp_daemon = 'nhrpd' def get_frrender_dict(conf, argv=None) -> dict: from copy import deepcopy @@ -147,6 +148,43 @@ def dict_helper_pim_defaults(pim, path): pim = config_dict_merge(default_values, pim) return pim + def dict_helper_nhrp_defaults(nhrp): + nflog_redirect = 1 + nflog_multicast = 2 + + nhrp = conf.merge_defaults(nhrp, recursive=True) + + nhrp_tunnel = conf.get_config_dict(['interfaces', 'tunnel'], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + if nhrp_tunnel: nhrp.update({'if_tunnel': nhrp_tunnel}) + + for intf, intf_config in nhrp['tunnel'].items(): + if 'multicast' in intf_config: + nhrp['multicast'] = nflog_multicast + if 'redirect' in intf_config: + nhrp['redirect'] = nflog_redirect + + ##Add ipsec profile config to nhrp configuration to apply encryption + profile = conf.get_config_dict(['vpn', 'ipsec', 'profile'], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + for name, profile_conf in profile.items(): + if 'disable' not in profile_conf: + if 'bind' in profile_conf and 'tunnel' in profile_conf['bind']: + interfaces = profile_conf['bind']['tunnel'] + if isinstance(interfaces, str): + interfaces = [interfaces] + for interface in interfaces: + if dict_search(f'tunnel.{interface}', nhrp): + nhrp['tunnel'][interface][ + 'security_profile'] = name + return nhrp + # Ethernet and bonding interfaces can participate in EVPN which is configured via FRR tmp = {} for if_type in ['ethernet', 'bonding']: @@ -364,6 +402,18 @@ def dict_helper_pim_defaults(pim, path): elif conf.exists_effective(static_cli_path): dict.update({'static' : {'deleted' : ''}}) + # We need to check the CLI if the NHRP node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + nhrp_cli_path = ['protocols', 'nhrp'] + if conf.exists(nhrp_cli_path): + nhrp = conf.get_config_dict(nhrp_cli_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + nhrp = dict_helper_nhrp_defaults(nhrp) + dict.update({'nhrp' : nhrp}) + elif conf.exists_effective(nhrp_cli_path): + dict.update({'nhrp' : {'deleted' : ''}}) + # T3680 - get a list of all interfaces currently configured to use DHCP tmp = get_dhcp_interfaces(conf) if tmp: @@ -378,6 +428,8 @@ def dict_helper_pim_defaults(pim, path): else: dict.update({'static' : {'pppoe' : tmp}}) + + # keep a re-usable list of dependent VRFs dependent_vrfs_default = {} if 'bgp' in dict: @@ -626,6 +678,9 @@ def inline_helper(config_dict) -> str: if 'ipv6' in config_dict and 'deleted' not in config_dict['ipv6']: output += render_to_string('frr/zebra.route-map.frr.j2', config_dict['ipv6']) output += '\n' + if 'nhrp' in config_dict and 'deleted' not in config_dict['nhrp']: + output += render_to_string('frr/nhrpd.frr.j2', config_dict['nhrp']) + output += '\n' return output debug('FRR: START CONFIGURATION RENDERING') diff --git a/smoketest/scripts/cli/test_protocols_nhrp.py b/smoketest/scripts/cli/test_protocols_nhrp.py index 43ae4abf22..f6d1f1da5d 100755 --- a/smoketest/scripts/cli/test_protocols_nhrp.py +++ b/smoketest/scripts/cli/test_protocols_nhrp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2024 VyOS maintainers and contributors +# Copyright (C) 2021-2025 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 @@ -25,6 +25,7 @@ tunnel_path = ['interfaces', 'tunnel'] nhrp_path = ['protocols', 'nhrp'] vpn_path = ['vpn', 'ipsec'] +PROCESS_NAME = 'nhrpd' class TestProtocolsNHRP(VyOSUnitTestSHIM.TestCase): @classmethod @@ -41,29 +42,41 @@ def tearDown(self): self.cli_delete(tunnel_path) self.cli_commit() - def test_config(self): + def test_01_nhrp_config(self): tunnel_if = "tun100" - tunnel_source = "192.0.2.1" + tunnel_ip = '172.16.253.134/32' + tunnel_source = "192.0.2.134" tunnel_encapsulation = "gre" esp_group = "ESP-HUB" ike_group = "IKE-HUB" nhrp_secret = "vyos123" nhrp_profile = "NHRPVPN" + nhrp_holdtime = '300' + nhs_tunnelip = '172.16.253.1' + nhs_nbmaip = '192.0.2.1' + map_tunnelip = '172.16.253.135' + map_nbmaip = "192.0.2.135" + nhrp_networkid = '1' ipsec_secret = "secret" - + multicat_log_group = '2' + redirect_log_group = '1' # Tunnel - self.cli_set(tunnel_path + [tunnel_if, "address", "172.16.253.134/29"]) + self.cli_set(tunnel_path + [tunnel_if, "address", tunnel_ip]) self.cli_set(tunnel_path + [tunnel_if, "encapsulation", tunnel_encapsulation]) self.cli_set(tunnel_path + [tunnel_if, "source-address", tunnel_source]) self.cli_set(tunnel_path + [tunnel_if, "enable-multicast"]) self.cli_set(tunnel_path + [tunnel_if, "parameters", "ip", "key", "1"]) # NHRP - self.cli_set(nhrp_path + ["tunnel", tunnel_if, "cisco-authentication", nhrp_secret]) - self.cli_set(nhrp_path + ["tunnel", tunnel_if, "holding-time", "300"]) - self.cli_set(nhrp_path + ["tunnel", tunnel_if, "multicast", "dynamic"]) + self.cli_set(nhrp_path + ["tunnel", tunnel_if, "authentication", nhrp_secret]) + self.cli_set(nhrp_path + ["tunnel", tunnel_if, "holdtime", nhrp_holdtime]) + self.cli_set(nhrp_path + ["tunnel", tunnel_if, "multicast", nhs_tunnelip]) self.cli_set(nhrp_path + ["tunnel", tunnel_if, "redirect"]) self.cli_set(nhrp_path + ["tunnel", tunnel_if, "shortcut"]) + self.cli_set(nhrp_path + ["tunnel", tunnel_if, "registration-no-unique"]) + self.cli_set(nhrp_path + ["tunnel", tunnel_if, "network-id", nhrp_networkid]) + self.cli_set(nhrp_path + ["tunnel", tunnel_if, "nhs", "tunnel-ip", nhs_tunnelip, "nbma", nhs_nbmaip]) + self.cli_set(nhrp_path + ["tunnel", tunnel_if, "map", "tunnel-ip", map_tunnelip, "nbma", map_nbmaip]) # IKE/ESP Groups self.cli_set(vpn_path + ["esp-group", esp_group, "lifetime", "1800"]) @@ -93,29 +106,40 @@ def test_config(self): self.cli_commit() - opennhrp_lines = [ - f'interface {tunnel_if} #hub {nhrp_profile}', - f'cisco-authentication {nhrp_secret}', - f'holding-time 300', - f'shortcut', - f'multicast dynamic', - f'redirect' + frrconfig = self.getFRRconfig(f'interface {tunnel_if}', endsection='^exit') + self.assertIn(f'interface {tunnel_if}', frrconfig) + self.assertIn(f' ip nhrp authentication {nhrp_secret}', frrconfig) + self.assertIn(f' ip nhrp holdtime {nhrp_holdtime}', frrconfig) + self.assertIn(f' ip nhrp map multicast {nhs_tunnelip}', frrconfig) + self.assertIn(f' ip nhrp redirect', frrconfig) + self.assertIn(f' ip nhrp registration no-unique', frrconfig) + self.assertIn(f' ip nhrp shortcut', frrconfig) + self.assertIn(f' ip nhrp network-id {nhrp_networkid}', frrconfig) + self.assertIn(f' ip nhrp nhs {nhs_tunnelip} nbma {nhs_nbmaip}', frrconfig) + self.assertIn(f' ip nhrp map {map_tunnelip} {map_nbmaip}', frrconfig) + self.assertIn(f' tunnel protection vici profile dmvpn-{nhrp_profile}-{tunnel_if}-child', + frrconfig) + + nftables_search_multicast = [ + ['chain VYOS_NHRP_MULTICAST_OUTPUT'], + ['type filter hook output priority filter + 10; policy accept;'], + [f'oifname "{tunnel_if}"', 'ip daddr 224.0.0.0/24', 'counter', f'log group {multicat_log_group}'], + [f'oifname "{tunnel_if}"', 'ip daddr 224.0.0.0/24', 'counter', 'drop'], + ['chain VYOS_NHRP_MULTICAST_FORWARD'], + ['type filter hook output priority filter + 10; policy accept;'], + [f'oifname "{tunnel_if}"', 'ip daddr 224.0.0.0/4', 'counter', f'log group {multicat_log_group}'], + [f'oifname "{tunnel_if}"', 'ip daddr 224.0.0.0/4', 'counter', 'drop'] ] - tmp_opennhrp_conf = read_file('/run/opennhrp/opennhrp.conf') - - for line in opennhrp_lines: - self.assertIn(line, tmp_opennhrp_conf) - - firewall_matches = [ - f'ip protocol {tunnel_encapsulation}', - f'ip saddr {tunnel_source}', - f'ip daddr 224.0.0.0/4', - f'comment "VYOS_NHRP_{tunnel_if}"' + nftables_search_redirect = [ + ['chain VYOS_NHRP_REDIRECT_FORWARD'], + ['type filter hook forward priority filter + 10; policy accept;'], + [f'iifname "{tunnel_if}" oifname "{tunnel_if}"', 'meter loglimit-0 size 65535 { ip daddr & 255.255.255.0 . ip saddr & 255.255.255.0 timeout 1m limit rate 4/minute burst 1 packets }', 'counter', f'log group {redirect_log_group}'] ] + self.verify_nftables(nftables_search_multicast, 'ip vyos_nhrp_multicast') + self.verify_nftables(nftables_search_redirect, 'ip vyos_nhrp_redirect') - self.assertTrue(find_nftables_rule('ip vyos_nhrp_filter', 'VYOS_NHRP_OUTPUT', firewall_matches) is not None) - self.assertTrue(process_named_running('opennhrp')) + self.assertTrue(process_named_running(PROCESS_NAME)) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_vpn_ipsec.py b/smoketest/scripts/cli/test_vpn_ipsec.py index f2bea58d1c..91a76e6f6d 100755 --- a/smoketest/scripts/cli/test_vpn_ipsec.py +++ b/smoketest/scripts/cli/test_vpn_ipsec.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2024 VyOS maintainers and contributors +# Copyright (C) 2021-2025 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 @@ -353,24 +353,40 @@ def test_site_to_site_vti(self): def test_dmvpn(self): - tunnel_if = 'tun100' - nhrp_secret = 'secret' ike_lifetime = '3600' esp_lifetime = '1800' + tunnel_if = "tun100" + tunnel_ip = '172.16.253.134/32' + tunnel_source = "192.0.2.134" + tunnel_encapsulation = "gre" + esp_group = "ESP-HUB" + ike_group = "IKE-HUB" + nhrp_secret = "vyos123" + nhrp_holdtime = '300' + nhs_tunnelip = '172.16.253.1' + nhs_nbmaip = '192.0.2.1' + map_tunnelip = '172.16.253.135' + map_nbmaip = "192.0.2.135" + nhrp_networkid = '1' + # Tunnel - self.cli_set(tunnel_path + [tunnel_if, 'address', '172.16.253.134/29']) - self.cli_set(tunnel_path + [tunnel_if, 'encapsulation', 'gre']) - self.cli_set(tunnel_path + [tunnel_if, 'source-address', '192.0.2.1']) - self.cli_set(tunnel_path + [tunnel_if, 'enable-multicast']) - self.cli_set(tunnel_path + [tunnel_if, 'parameters', 'ip', 'key', '1']) + self.cli_set(tunnel_path + [tunnel_if, "address", tunnel_ip]) + self.cli_set(tunnel_path + [tunnel_if, "encapsulation", tunnel_encapsulation]) + self.cli_set(tunnel_path + [tunnel_if, "source-address", tunnel_source]) + self.cli_set(tunnel_path + [tunnel_if, "enable-multicast"]) + self.cli_set(tunnel_path + [tunnel_if, "parameters", "ip", "key", "1"]) # NHRP - self.cli_set(nhrp_path + ['tunnel', tunnel_if, 'cisco-authentication', nhrp_secret]) - self.cli_set(nhrp_path + ['tunnel', tunnel_if, 'holding-time', '300']) - self.cli_set(nhrp_path + ['tunnel', tunnel_if, 'multicast', 'dynamic']) - self.cli_set(nhrp_path + ['tunnel', tunnel_if, 'redirect']) - self.cli_set(nhrp_path + ['tunnel', tunnel_if, 'shortcut']) + self.cli_set(nhrp_path + ["tunnel", tunnel_if, "authentication", nhrp_secret]) + self.cli_set(nhrp_path + ["tunnel", tunnel_if, "holdtime", nhrp_holdtime]) + self.cli_set(nhrp_path + ["tunnel", tunnel_if, "multicast", nhs_tunnelip]) + self.cli_set(nhrp_path + ["tunnel", tunnel_if, "redirect"]) + self.cli_set(nhrp_path + ["tunnel", tunnel_if, "shortcut"]) + self.cli_set(nhrp_path + ["tunnel", tunnel_if, "registration-no-unique"]) + self.cli_set(nhrp_path + ["tunnel", tunnel_if, "network-id", nhrp_networkid]) + self.cli_set(nhrp_path + ["tunnel", tunnel_if, "nhs", "tunnel-ip", nhs_tunnelip, "nbma", nhs_nbmaip]) + self.cli_set(nhrp_path + ["tunnel", tunnel_if, "map", "tunnel-ip", map_tunnelip, "nbma", map_nbmaip]) # IKE/ESP Groups self.cli_set(base_path + ['esp-group', esp_group, 'lifetime', esp_lifetime]) @@ -399,11 +415,11 @@ def test_dmvpn(self): swanctl_conf = read_file(swanctl_file) swanctl_lines = [ - f'proposals = aes128-sha1-modp1024,aes256-sha1-prfsha1-modp1024', + f'proposals = aes256-sha1-prfsha1-modp1024', f'version = 1', f'rekey_time = {ike_lifetime}s', f'rekey_time = {esp_lifetime}s', - f'esp_proposals = aes128-sha1-modp1024,aes256-sha1-modp1024,3des-md5-modp1024', + f'esp_proposals = aes256-sha1-modp1024,3des-md5-modp1024', f'local_ts = dynamic[gre]', f'remote_ts = dynamic[gre]', f'mode = transport', diff --git a/src/conf_mode/interfaces_tunnel.py b/src/conf_mode/interfaces_tunnel.py index 98ef98d125..ee1436e49b 100755 --- a/src/conf_mode/interfaces_tunnel.py +++ b/src/conf_mode/interfaces_tunnel.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2024 yOS maintainers and contributors +# Copyright (C) 2018-2025 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 @@ -13,9 +13,8 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . - from sys import exit - +import ipaddress from vyos.config import Config from vyos.configdict import get_interface_dict from vyos.configdict import is_node_changed @@ -89,6 +88,13 @@ def verify(tunnel): raise ConfigError('Tunnel used for NHRP, it can not be deleted!') return None + if 'nhrp' in tunnel: + if 'address' in tunnel: + address_list = dict_search('address', tunnel) + for tunip in address_list: + if ipaddress.ip_network(tunip, strict=False).prefixlen != 32: + raise ConfigError( + 'Tunnel is used for NHRP, Netmask should be /32!') verify_tunnel(tunnel) diff --git a/src/conf_mode/protocols_nhrp.py b/src/conf_mode/protocols_nhrp.py index 0bd68b7d86..ac92c9d99f 100755 --- a/src/conf_mode/protocols_nhrp.py +++ b/src/conf_mode/protocols_nhrp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2024 VyOS maintainers and contributors +# Copyright (C) 2021-2025 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -14,95 +14,112 @@ # 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 sys import argv +import ipaddress from vyos.config import Config -from vyos.configdict import node_changed from vyos.template import render +from vyos.configverify import has_frr_protocol_in_dict from vyos.utils.process import run +from vyos.utils.dict import dict_search from vyos import ConfigError from vyos import airbag +from vyos.frrender import FRRender +from vyos.frrender import get_frrender_dict +from vyos.utils.process import is_systemd_service_running + airbag.enable() -opennhrp_conf = '/run/opennhrp/opennhrp.conf' +nflog_redirect = 1 +nflog_multicast = 2 nhrp_nftables_conf = '/run/nftables_nhrp.conf' + def get_config(config=None): if config: conf = config else: conf = Config() - base = ['protocols', 'nhrp'] - - nhrp = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - nhrp['del_tunnels'] = node_changed(conf, base + ['tunnel']) - - if not conf.exists(base): - return nhrp - nhrp['if_tunnel'] = conf.get_config_dict(['interfaces', 'tunnel'], key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) + return get_frrender_dict(conf, argv) - nhrp['profile_map'] = {} - profile = conf.get_config_dict(['vpn', 'ipsec', 'profile'], key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - for name, profile_conf in profile.items(): - if 'bind' in profile_conf and 'tunnel' in profile_conf['bind']: - interfaces = profile_conf['bind']['tunnel'] - if isinstance(interfaces, str): - interfaces = [interfaces] - for interface in interfaces: - nhrp['profile_map'][interface] = name - - return nhrp - -def verify(nhrp): - if 'tunnel' in nhrp: - for name, nhrp_conf in nhrp['tunnel'].items(): - if not nhrp['if_tunnel'] or name not in nhrp['if_tunnel']: +def verify(config_dict): + if not config_dict or 'deleted' in config_dict: + return None + if 'tunnel' in config_dict: + for name, nhrp_conf in config_dict['tunnel'].items(): + if not config_dict['if_tunnel'] or name not in config_dict['if_tunnel']: raise ConfigError(f'Tunnel interface "{name}" does not exist') - tunnel_conf = nhrp['if_tunnel'][name] + tunnel_conf = config_dict['if_tunnel'][name] + if 'address' in tunnel_conf: + address_list = dict_search('address', tunnel_conf) + for tunip in address_list: + if ipaddress.ip_network(tunip, + strict=False).prefixlen != 32: + raise ConfigError( + f'Tunnel {name} is used for NHRP, Netmask should be /32!') if 'encapsulation' not in tunnel_conf or tunnel_conf['encapsulation'] != 'gre': raise ConfigError(f'Tunnel "{name}" is not an mGRE tunnel') + if 'network_id' not in nhrp_conf: + raise ConfigError(f'network-id is not specified in tunnel "{name}"') + if 'remote' in tunnel_conf: raise ConfigError(f'Tunnel "{name}" cannot have a remote address defined') - if 'map' in nhrp_conf: - for map_name, map_conf in nhrp_conf['map'].items(): - if 'nbma_address' not in map_conf: + map_tunnelip = dict_search('map.tunnel_ip', nhrp_conf) + if map_tunnelip: + for map_name, map_conf in map_tunnelip.items(): + if 'nbma' not in map_conf: raise ConfigError(f'nbma-address missing on map {map_name} on tunnel {name}') - if 'dynamic_map' in nhrp_conf: - for map_name, map_conf in nhrp_conf['dynamic_map'].items(): - if 'nbma_domain_name' not in map_conf: - raise ConfigError(f'nbma-domain-name missing on dynamic-map {map_name} on tunnel {name}') + nhs_tunnelip = dict_search('nhs.tunnel_ip', nhrp_conf) + nbma_list = [] + if nhs_tunnelip: + for nhs_name, nhs_conf in nhs_tunnelip.items(): + if 'nbma' not in nhs_conf: + raise ConfigError(f'nbma-address missing on map nhs {nhs_name} on tunnel {name}') + if nhs_name != 'dynamic': + if len(list(dict_search('nbma', nhs_conf))) > 1: + raise ConfigError( + f'Static nhs tunnel-ip {nhs_name} cannot contain multiple nbma-addresses') + for nbma_ip in dict_search('nbma', nhs_conf): + if nbma_ip not in nbma_list: + nbma_list.append(nbma_ip) + else: + raise ConfigError( + f'Nbma address {nbma_ip} cannot be maped to several tunnel-ip') return None -def generate(nhrp): - if not os.path.exists(nhrp_nftables_conf): - nhrp['first_install'] = True - render(opennhrp_conf, 'nhrp/opennhrp.conf.j2', nhrp) - render(nhrp_nftables_conf, 'nhrp/nftables.conf.j2', nhrp) +def generate(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'nhrp'): + return None + + if 'deleted' in config_dict['nhrp']: + return None + render(nhrp_nftables_conf, 'frr/nhrpd_nftables.conf.j2', config_dict['nhrp']) + + if config_dict and not is_systemd_service_running('vyos-configd.service'): + FRRender().generate(config_dict) return None -def apply(nhrp): + +def apply(config_dict): + nft_rc = run(f'nft --file {nhrp_nftables_conf}') if nft_rc != 0: raise ConfigError('Failed to apply NHRP tunnel firewall rules') - action = 'restart' if nhrp and 'tunnel' in nhrp else 'stop' - service_rc = run(f'systemctl {action} opennhrp.service') - if service_rc != 0: - raise ConfigError(f'Failed to {action} the NHRP service') - + if config_dict and not is_systemd_service_running('vyos-configd.service'): + FRRender().apply() return None + if __name__ == '__main__': try: c = get_config() @@ -112,3 +129,4 @@ def apply(nhrp): except ConfigError as e: print(e) exit(1) + diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index e22b7550c9..25604d2a28 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2024 VyOS maintainers and contributors +# Copyright (C) 2021-2025 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 @@ -86,8 +86,6 @@ def get_config(config=None): conf = Config() base = ['vpn', 'ipsec'] l2tp_base = ['vpn', 'l2tp', 'remote-access', 'ipsec-settings'] - if not conf.exists(base): - return None # retrieve common dictionary keys ipsec = conf.get_config_dict(base, key_mangling=('-', '_'), @@ -95,6 +93,14 @@ def get_config(config=None): get_first_key=True, with_pki=True) + ipsec['nhrp_exists'] = conf.exists(['protocols', 'nhrp', 'tunnel']) + if ipsec['nhrp_exists']: + set_dependents('nhrp', conf) + + if not conf.exists(base): + ipsec.update({'deleted' : ''}) + return ipsec + # We have to cleanup the default dict, as default values could # enable features which are not explicitly enabled on the # CLI. E.g. dead-peer-detection defaults should not be injected @@ -115,7 +121,6 @@ def get_config(config=None): ipsec['dhcp_no_address'] = {} ipsec['install_routes'] = 'no' if conf.exists(base + ["options", "disable-route-autoinstall"]) else default_install_routes ipsec['interface_change'] = leaf_node_changed(conf, base + ['interface']) - ipsec['nhrp_exists'] = conf.exists(['protocols', 'nhrp', 'tunnel']) if ipsec['nhrp_exists']: set_dependents('nhrp', conf) @@ -196,8 +201,8 @@ def verify_pki_rsa(pki, rsa_conf): return True def verify(ipsec): - if not ipsec: - return None + if not ipsec or 'deleted' in ipsec: + return if 'authentication' in ipsec: if 'psk' in ipsec['authentication']: @@ -624,7 +629,7 @@ def generate_pki_files_rsa(pki, rsa_conf): def generate(ipsec): cleanup_pki_files() - if not ipsec: + if not ipsec or 'deleted' in ipsec: for config_file in [charon_dhcp_conf, charon_radius_conf, interface_conf, swanctl_conf]: if os.path.isfile(config_file): os.unlink(config_file) @@ -721,15 +726,12 @@ def generate(ipsec): def apply(ipsec): systemd_service = 'strongswan.service' - if not ipsec: + if not ipsec or 'deleted' in ipsec: call(f'systemctl stop {systemd_service}') - if vti_updown_db_exists(): remove_vti_updown_db() - else: call(f'systemctl reload-or-restart {systemd_service}') - if ipsec['enabled_vti_interfaces']: with open_vti_updown_db_for_create_or_update() as db: db.removeAllOtherInterfaces(ipsec['enabled_vti_interfaces']) @@ -737,7 +739,7 @@ def apply(ipsec): db.commit(lambda interface: ipsec['vti_interface_dicts'][interface]) elif vti_updown_db_exists(): remove_vti_updown_db() - + if ipsec: if ipsec.get('nhrp_exists', False): try: call_dependents() @@ -746,7 +748,6 @@ def apply(ipsec): # ConfigError("ConfigError('Interface ethN requires an IP address!')") pass - if __name__ == '__main__': try: ipsec = get_config() diff --git a/src/migration-scripts/nhrp/0-to-1 b/src/migration-scripts/nhrp/0-to-1 new file mode 100644 index 0000000000..50a7e65edf --- /dev/null +++ b/src/migration-scripts/nhrp/0-to-1 @@ -0,0 +1,119 @@ +# Copyright 2025 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 . + +# Migration from Opennhrp to FRR NHRP +import ipaddress + +from vyos.configtree import ConfigTree + +base = ['protocols', 'nhrp', 'tunnel'] +interface_base = ['interfaces', 'tunnel'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + for tunnel_name in config.list_nodes(base): + ## Cisco Authentication migration + if config.exists(base + [tunnel_name,'cisco-authentication']): + auth = config.return_value(base + [tunnel_name,'cisco-authentication']) + config.delete(base + [tunnel_name,'cisco-authentication']) + config.set(base + [tunnel_name,'authentication'], value=auth) + ## Delete Dynamic-map to fqdn + if config.exists(base + [tunnel_name,'dynamic-map']): + config.delete(base + [tunnel_name,'dynamic-map']) + ## Holdtime migration + if config.exists(base + [tunnel_name,'holding-time']): + holdtime = config.return_value(base + [tunnel_name,'holding-time']) + config.delete(base + [tunnel_name,'holding-time']) + config.set(base + [tunnel_name,'holdtime'], value=holdtime) + ## Add network-id + config.set(base + [tunnel_name, 'network-id'], value='1') + ## Map and nhs migration + nhs_list = [] + is_nhs = False + if config.exists(base + [tunnel_name,'map']): + is_map = False + for tunnel_ip in config.list_nodes(base + [tunnel_name, 'map']): + tunnel_ip_path = base + [tunnel_name, 'map', tunnel_ip] + tunnel_ip = tunnel_ip.split('/')[0] + if config.exists(tunnel_ip_path + ['cisco']): + config.delete(tunnel_ip_path + ['cisco']) + if config.exists(tunnel_ip_path + ['nbma-address']): + nbma = config.return_value(tunnel_ip_path + ['nbma-address']) + if config.exists (tunnel_ip_path + ['register']): + config.delete(tunnel_ip_path + ['register']) + config.delete(tunnel_ip_path + ['nbma-address']) + config.set(base + [tunnel_name, 'nhs', 'tunnel-ip', tunnel_ip, 'nbma'], value=nbma) + is_nhs = True + if tunnel_ip not in nhs_list: + nhs_list.append(tunnel_ip) + else: + config.delete(tunnel_ip_path + ['nbma-address']) + config.set(base + [tunnel_name, 'map_test', 'tunnel-ip', tunnel_ip, 'nbma'], value=nbma) + is_map = True + config.delete(base + [tunnel_name,'map']) + if is_nhs: + config.set_tag(base + [tunnel_name, 'nhs', 'tunnel-ip']) + # + # Change netmask to /32 on tunnel interface + # If nhs is alone, add static route tunnel network to nhs + # + if config.exists(interface_base + [tunnel_name, 'address']): + tunnel_ip_list = [] + for tunnel_ip in config.return_values( + interface_base + [tunnel_name, 'address']): + tunnel_ip_ch = tunnel_ip.split('/')[0]+'/32' + if tunnel_ip_ch not in tunnel_ip_list: + tunnel_ip_list.append(tunnel_ip_ch) + for nhs in nhs_list: + config.set(['protocols', 'static', 'route', str(ipaddress.ip_network(tunnel_ip, strict=False)), 'next-hop', nhs, 'distance'], value='250') + if nhs_list: + if not config.is_tag(['protocols', 'static', 'route']): + config.set_tag(['protocols', 'static', 'route']) + if not config.is_tag(['protocols', 'static', 'route', str(ipaddress.ip_network(tunnel_ip, strict=False)), 'next-hop']): + config.set_tag(['protocols', 'static', 'route', str(ipaddress.ip_network(tunnel_ip, strict=False)), 'next-hop']) + + config.delete(interface_base + [tunnel_name, 'address']) + for tunnel_ip in tunnel_ip_list: + config.set( + interface_base + [tunnel_name, 'address'], value=tunnel_ip, replace=False) + ######################################## + if is_map: + config.copy(base + [tunnel_name, 'map_test'], base + [tunnel_name, 'map']) + config.delete(base + [tunnel_name, 'map_test']) + config.set_tag(base + [tunnel_name, 'map', 'tunnel-ip']) + ## Map multicast migration + if config.exists(base + [tunnel_name, 'multicast']): + multicast_map = config.return_value(base + [tunnel_name, 'multicast']) + if multicast_map == 'nhs': + config.delete(base + [tunnel_name, 'multicast']) + for nhs in nhs_list: + config.set(base + [tunnel_name, 'multicast'], value=nhs, replace=False) + ## Delete non-cahching + if config.exists(base + [tunnel_name, 'non-caching']): + config.delete(base + [tunnel_name, 'non-caching']) + ## Delete shortcut-destination + if config.exists(base + [tunnel_name, 'shortcut-destination']): + if not config.exists(base + [tunnel_name, 'shortcut']): + config.set(base + [tunnel_name, 'shortcut']) + config.delete(base + [tunnel_name, 'shortcut-destination']) + ## Delete shortcut-target + if config.exists(base + [tunnel_name, 'shortcut-target']): + if not config.exists(base + [tunnel_name, 'shortcut']): + config.set(base + [tunnel_name, 'shortcut']) + config.delete(base + [tunnel_name, 'shortcut-target']) + ## Set registration-no-unique + config.set(base + [tunnel_name, 'registration-no-unique']) \ No newline at end of file diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py index 02ba126b46..1ab50b1052 100755 --- a/src/op_mode/ipsec.py +++ b/src/op_mode/ipsec.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022-2024 VyOS maintainers and contributors +# Copyright (C) 2022-2025 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 @@ -700,15 +700,6 @@ def reset_profile_dst(profile: str, tunnel: str, nbma_dst: str): ] ) ) - # initiate IKE SAs - for ike in sa_nbma_list: - if ike_sa_name in ike: - vyos.ipsec.vici_initiate( - ike_sa_name, - 'dmvpn', - ike[ike_sa_name]['local-host'], - ike[ike_sa_name]['remote-host'], - ) print( f'Profile {profile} tunnel {tunnel} remote-host {nbma_dst} reset result: success' ) @@ -732,18 +723,6 @@ def reset_profile_all(profile: str, tunnel: str): ) # terminate IKE SAs vyos.ipsec.terminate_vici_by_name(ike_sa_name, None) - # initiate IKE SAs - for ike in sa_list: - if ike_sa_name in ike: - vyos.ipsec.vici_initiate( - ike_sa_name, - 'dmvpn', - ike[ike_sa_name]['local-host'], - ike[ike_sa_name]['remote-host'], - ) - print( - f'Profile {profile} tunnel {tunnel} remote-host {ike[ike_sa_name]["remote-host"]} reset result: success' - ) print(f'Profile {profile} tunnel {tunnel} reset result: success') except vyos.ipsec.ViciInitiateError as err: raise vyos.opmode.UnconfiguredSubsystem(err) diff --git a/src/op_mode/nhrp.py b/src/op_mode/nhrp.py deleted file mode 100755 index e66f33079a..0000000000 --- a/src/op_mode/nhrp.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2023 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import sys -import tabulate -import vyos.opmode - -from vyos.utils.process import cmd -from vyos.utils.process import process_named_running -from vyos.utils.dict import colon_separated_to_dict - - -def _get_formatted_output(output_dict: dict) -> str: - """ - Create formatted table for CLI output - :param output_dict: dictionary for API - :type output_dict: dict - :return: tabulate string - :rtype: str - """ - print(f"Status: {output_dict['Status']}") - output: str = tabulate.tabulate(output_dict['routes'], headers='keys', - numalign="left") - return output - - -def _get_formatted_dict(output_string: str) -> dict: - """ - Format string returned from CMD to API list - :param output_string: String received by CMD - :type output_string: str - :return: dictionary for API - :rtype: dict - """ - formatted_dict: dict = { - 'Status': '', - 'routes': [] - } - output_list: list = output_string.split('\n\n') - for list_a in output_list: - output_dict = colon_separated_to_dict(list_a, True) - if 'Status' in output_dict: - formatted_dict['Status'] = output_dict['Status'] - else: - formatted_dict['routes'].append(output_dict) - return formatted_dict - - -def show_interface(raw: bool): - """ - Command 'show nhrp interface' - :param raw: if API - :type raw: bool - """ - if not process_named_running('opennhrp'): - raise vyos.opmode.UnconfiguredSubsystem('OpenNHRP is not running.') - interface_string: str = cmd('sudo opennhrpctl interface show') - interface_dict: dict = _get_formatted_dict(interface_string) - if raw: - return interface_dict - else: - return _get_formatted_output(interface_dict) - - -def show_tunnel(raw: bool): - """ - Command 'show nhrp tunnel' - :param raw: if API - :type raw: bool - """ - if not process_named_running('opennhrp'): - raise vyos.opmode.UnconfiguredSubsystem('OpenNHRP is not running.') - tunnel_string: str = cmd('sudo opennhrpctl show') - tunnel_dict: list = _get_formatted_dict(tunnel_string) - if raw: - return tunnel_dict - else: - return _get_formatted_output(tunnel_dict) - - -if __name__ == '__main__': - try: - res = vyos.opmode.run(sys.modules[__name__]) - if res: - print(res) - except (ValueError, vyos.opmode.Error) as e: - print(e) - sys.exit(1) diff --git a/src/op_mode/vtysh_wrapper.sh b/src/op_mode/vtysh_wrapper.sh index 25d09ce77e..bc472f7bb8 100755 --- a/src/op_mode/vtysh_wrapper.sh +++ b/src/op_mode/vtysh_wrapper.sh @@ -2,5 +2,5 @@ declare -a tmp # FRR uses ospf6 where we use ospfv3, and we use reset over clear for BGP, # thus alter the commands -tmp=$(echo $@ | sed -e "s/ospfv3/ospf6/" | sed -e "s/^reset bgp/clear bgp/" | sed -e "s/^reset ip bgp/clear ip bgp/") +tmp=$(echo $@ | sed -e "s/ospfv3/ospf6/" | sed -e "s/^reset bgp/clear bgp/" | sed -e "s/^reset ip bgp/clear ip bgp/"| sed -e "s/^reset ip nhrp/clear ip nhrp/") vtysh -c "$tmp"