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"