summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsarthurdev <965089+sarthurdev@users.noreply.github.com>2021-05-31 12:08:48 +0200
committersarthurdev <965089+sarthurdev@users.noreply.github.com>2021-06-06 00:14:10 +0200
commitb3bce6497cc20cad687efc818d679d71d62fbd26 (patch)
tree67ccbd191f824de18d8b39f0694c040c65e38e78
parent77866ccb16194a2446b2faccb52ec852aa882b96 (diff)
downloadvyos-1x-b3bce6497cc20cad687efc818d679d71d62fbd26.tar.gz
vyos-1x-b3bce6497cc20cad687efc818d679d71d62fbd26.zip
nhrp: T3599: Migrate NHRP to XML/Python
-rw-r--r--data/configd-include.json1
-rw-r--r--data/templates/nhrp/opennhrp.conf.tmpl41
-rw-r--r--debian/control1
-rw-r--r--debian/vyos-1x.install1
-rw-r--r--interface-definitions/protocols-nhrp.xml.in134
-rw-r--r--op-mode-definitions/nhrp.xml.in65
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_nhrp.py97
-rwxr-xr-xsrc/conf_mode/protocols_nhrp.py122
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py4
-rwxr-xr-xsrc/etc/opennhrp/opennhrp-script.py136
-rw-r--r--src/systemd/opennhrp.service13
11 files changed, 614 insertions, 1 deletions
diff --git a/data/configd-include.json b/data/configd-include.json
index 28267d575..d25fa3de7 100644
--- a/data/configd-include.json
+++ b/data/configd-include.json
@@ -39,6 +39,7 @@
"protocols_igmp.py",
"protocols_isis.py",
"protocols_mpls.py",
+"protocols_nhrp.py",
"protocols_ospf.py",
"protocols_ospfv3.py",
"protocols_pim.py",
diff --git a/data/templates/nhrp/opennhrp.conf.tmpl b/data/templates/nhrp/opennhrp.conf.tmpl
new file mode 100644
index 000000000..308459407
--- /dev/null
+++ b/data/templates/nhrp/opennhrp.conf.tmpl
@@ -0,0 +1,41 @@
+# Created by VyOS - manual changes will be overwritten
+
+{% if tunnel is defined %}
+{% for name, tunnel_conf in tunnel.items() %}
+{% set type = 'spoke' if 'map' in tunnel_conf or 'dynamic_map' in tunnel_conf else 'hub' %}
+{% set profile_name = profile_map[name] if profile_map is defined and name in profile_map else '' %}
+interface {{ name }} #{{ type }} {{ profile_name }}
+{% if 'map' in tunnel_conf %}
+{% for map, map_conf in tunnel_conf.map.items() %}
+{% set cisco = ' cisco' if 'cisco' in map_conf else '' %}
+{% set register = ' register' if 'register' in map_conf else '' %}
+ map {{ map }} {{ map_conf.nbma_address }}{{ register }}{{ cisco }}
+{% endfor %}
+{% endif %}
+{% if 'dynamic_map' in tunnel_conf %}
+{% for map, map_conf in tunnel_conf.dynamic_map.items() %}
+ dynamic-map {{ map }} {{ map_conf.nbma_domain_name }}
+{% endfor %}
+{% endif %}
+{% if 'cisco_authentication' in tunnel_conf %}
+ cisco-authentication {{ tunnel_conf.cisco_authentication }}
+{% endif %}
+{% if 'holding_time' in tunnel_conf %}
+ holding-time {{ tunnel_conf.holding_time }}
+{% endif %}
+{% if 'multicast' in tunnel_conf %}
+ multicast {{ tunnel_conf.multicast }}
+{% endif %}
+{% for key in ['non_caching', 'redirect', 'shortcut', 'shortcut_destination'] %}
+{% if key in tunnel_conf %}
+ {{ key | replace("_", "-") }}
+{% endif %}
+{% endfor %}
+{% if 'shortcut_target' in tunnel_conf %}
+{% for target, shortcut_conf in tunnel_conf.shortcut_target.items() %}
+ shortcut-target {{ target }} {{ shortcut_conf.holding_time if 'holding_time' in shortcut_conf else '' }}
+{% endfor %}
+{% endif %}
+
+{% endfor %}
+{% endif %}
diff --git a/debian/control b/debian/control
index 105c4dfca..bbe8200ca 100644
--- a/debian/control
+++ b/debian/control
@@ -143,6 +143,7 @@ Depends:
udp-broadcast-relay,
usb-modeswitch,
usbutils,
+ vyos-opennhrp,
vyos-http-api-tools,
vyos-utils,
wide-dhcpv6-client,
diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install
index e5de7f074..0c6c226ee 100644
--- a/debian/vyos-1x.install
+++ b/debian/vyos-1x.install
@@ -1,6 +1,7 @@
etc/dhcp
etc/ipsec.d
etc/netplug
+etc/opennhrp
etc/ppp
etc/rsyslog.d
etc/systemd
diff --git a/interface-definitions/protocols-nhrp.xml.in b/interface-definitions/protocols-nhrp.xml.in
new file mode 100644
index 000000000..9dd9d3389
--- /dev/null
+++ b/interface-definitions/protocols-nhrp.xml.in
@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interfaceDefinition>
+ <node name="protocols">
+ <children>
+ <node name="nhrp" owner="${vyos_conf_scripts_dir}/protocols_nhrp.py">
+ <properties>
+ <help>NHRP parameters</help>
+ <priority>680</priority>
+ </properties>
+ <children>
+ <tagNode name="tunnel">
+ <properties>
+ <help>Tunnel for NHRP [REQUIRED]</help>
+ <constraint>
+ <regex>^tun[0-9]+$</regex>
+ </constraint>
+ <valueHelp>
+ <format>tunN</format>
+ <description>NHRP tunnel name</description>
+ </valueHelp>
+ </properties>
+ <children>
+ <leafNode name="cisco-authentication">
+ <properties>
+ <help>Pass phrase for cisco authentication</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Pass phrase for cisco authentication</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <tagNode name="dynamic-map">
+ <properties>
+ <help>Set an HUB tunnel address</help>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>Set the IP address and prefix length</description>
+ </valueHelp>
+ </properties>
+ <children>
+ <leafNode name="nbma-domain-name">
+ <properties>
+ <help>Set HUB fqdn (nbma-address - fqdn) [REQUIRED]</help>
+ <valueHelp>
+ <format>&lt;fqdn&gt;</format>
+ <description>Set the external HUB fqdn</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <leafNode name="holding-time">
+ <properties>
+ <help>Holding time in seconds</help>
+ </properties>
+ </leafNode>
+ <tagNode name="map">
+ <properties>
+ <help>Set an HUB tunnel address</help>
+ </properties>
+ <children>
+ <leafNode name="cisco">
+ <properties>
+ <help>If the statically mapped peer is running Cisco IOS, specify this</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="nbma-address">
+ <properties>
+ <help>Set HUB address (nbma-address - external hub address or fqdn) [REQUIRED]</help>
+ </properties>
+ </leafNode>
+ <leafNode name="register">
+ <properties>
+ <help>Specifies that Registration Request should be sent to this peer on startup</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <leafNode name="multicast">
+ <properties>
+ <help>Set multicast for NHRP</help>
+ <completionHelp>
+ <list>dynamic nhs</list>
+ </completionHelp>
+ <constraint>
+ <regex>^(dynamic|nhs)$</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="non-caching">
+ <properties>
+ <help>This can be used to reduce memory consumption on big NBMA subnets</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="redirect">
+ <properties>
+ <help>Enable sending of Cisco style NHRP Traffic Indication packets</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="shortcut-destination">
+ <properties>
+ <help>This instructs opennhrp to reply with authorative answers on NHRP Resolution Requests destined to addresses in this interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <tagNode name="shortcut-target">
+ <properties>
+ <help>Defines an off-NBMA network prefix for which the GRE interface will act as a gateway</help>
+ </properties>
+ <children>
+ <leafNode name="holding-time">
+ <properties>
+ <help>Holding time in seconds</help>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <leafNode name="shortcut">
+ <properties>
+ <help>Enable creation of shortcut routes. A received NHRP Traffic Indication will trigger the resolution and establishment of a shortcut route</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/nhrp.xml.in b/op-mode-definitions/nhrp.xml.in
new file mode 100644
index 000000000..9e746cc35
--- /dev/null
+++ b/op-mode-definitions/nhrp.xml.in
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interfaceDefinition>
+ <node name="reset">
+ <children>
+ <node name="nhrp">
+ <properties>
+ <help>Clear/Purge NHRP entries</help>
+ </properties>
+ <children>
+ <node name="flush">
+ <properties>
+ <help>Clear all non-permanent entries</help>
+ </properties>
+ <children>
+ <tagNode name="tunnel">
+ <properties>
+ <help>Clear all non-permanent entries</help>
+ </properties>
+ <command>sudo opennhrpctl flush dev $5 || echo OpenNHRP is not running.</command>
+ </tagNode>
+ </children>
+ <command>sudo opennhrpctl flush || echo OpenNHRP is not running.</command>
+ </node>
+ <node name="purge">
+ <properties>
+ <help>Purge entries from NHRP cache</help>
+ </properties>
+ <children>
+ <tagNode name="tunnel">
+ <properties>
+ <help>Purge all entries from NHRP cache</help>
+ </properties>
+ <command>sudo opennhrpctl purge dev $5 || echo OpenNHRP is not running.</command>
+ </tagNode>
+ </children>
+ <command>sudo opennhrpctl purge || echo OpenNHRP is not running.</command>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+ <node name="show">
+ <children>
+ <node name="nhrp">
+ <properties>
+ <help>Show NHRP info</help>
+ </properties>
+ <children>
+ <leafNode name="interface">
+ <properties>
+ <help>Show NHRP interface connection information</help>
+ </properties>
+ <command>if [ -f /var/run/opennhrp.pid ]; then sudo opennhrpctl interface show; else echo OpenNHRP is not running.; fi</command>
+ </leafNode>
+ <leafNode name="tunnel">
+ <properties>
+ <help>Show NHRP tunnel connection information</help>
+ </properties>
+ <command>if [ -f /var/run/opennhrp.pid ]; then sudo opennhrpctl show ; else echo OpenNHRP is not running.; fi</command>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/smoketest/scripts/cli/test_protocols_nhrp.py b/smoketest/scripts/cli/test_protocols_nhrp.py
new file mode 100755
index 000000000..8389e42e9
--- /dev/null
+++ b/smoketest/scripts/cli/test_protocols_nhrp.py
@@ -0,0 +1,97 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import unittest
+
+from base_vyostest_shim import VyOSUnitTestSHIM
+
+from vyos.util import call, process_named_running, read_file
+
+tunnel_path = ['interfaces', 'tunnel']
+nhrp_path = ['protocols', 'nhrp']
+vpn_path = ['vpn', 'ipsec']
+
+class TestProtocolsNHRP(VyOSUnitTestSHIM.TestCase):
+ def tearDown(self):
+ self.cli_delete(nhrp_path)
+ self.cli_delete(tunnel_path)
+ self.cli_commit()
+
+ def test_config(self):
+ self.cli_delete(nhrp_path)
+ self.cli_delete(tunnel_path)
+
+ # Tunnel
+ self.cli_set(tunnel_path + ["tun100", "address", "172.16.253.134/29"])
+ self.cli_set(tunnel_path + ["tun100", "encapsulation", "gre"])
+ self.cli_set(tunnel_path + ["tun100", "source-address", "192.0.2.1"])
+ self.cli_set(tunnel_path + ["tun100", "multicast", "enable"])
+ self.cli_set(tunnel_path + ["tun100", "parameters", "ip", "key", "1"])
+
+ # NHRP
+ self.cli_set(nhrp_path + ["tunnel", "tun100", "cisco-authentication", "secret"])
+ self.cli_set(nhrp_path + ["tunnel", "tun100", "holding-time", "300"])
+ self.cli_set(nhrp_path + ["tunnel", "tun100", "multicast", "dynamic"])
+ self.cli_set(nhrp_path + ["tunnel", "tun100", "redirect"])
+ self.cli_set(nhrp_path + ["tunnel", "tun100", "shortcut"])
+
+ # IKE/ESP Groups
+ self.cli_set(vpn_path + ["esp-group", "ESP-HUB", "compression", "disable"])
+ self.cli_set(vpn_path + ["esp-group", "ESP-HUB", "lifetime", "1800"])
+ self.cli_set(vpn_path + ["esp-group", "ESP-HUB", "mode", "transport"])
+ self.cli_set(vpn_path + ["esp-group", "ESP-HUB", "pfs", "dh-group2"])
+ self.cli_set(vpn_path + ["esp-group", "ESP-HUB", "proposal", "1", "encryption", "aes256"])
+ self.cli_set(vpn_path + ["esp-group", "ESP-HUB", "proposal", "1", "hash", "sha1"])
+ self.cli_set(vpn_path + ["esp-group", "ESP-HUB", "proposal", "2", "encryption", "3des"])
+ self.cli_set(vpn_path + ["esp-group", "ESP-HUB", "proposal", "2", "hash", "md5"])
+ self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "ikev2-reauth", "no"])
+ self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "key-exchange", "ikev1"])
+ self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "lifetime", "3600"])
+ self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "proposal", "1", "dh-group", "2"])
+ self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "proposal", "1", "encryption", "aes256"])
+ self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "proposal", "1", "hash", "sha1"])
+ self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "proposal", "2", "dh-group", "2"])
+ self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "proposal", "2", "encryption", "aes128"])
+ self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "proposal", "2", "hash", "sha1"])
+
+ # Profile - Not doing full DMVPN checks here, just want to verify the profile name in the output
+ self.cli_set(vpn_path + ["ipsec-interfaces", "interface", "eth0"])
+ self.cli_set(vpn_path + ["profile", "NHRPVPN", "authentication", "mode", "pre-shared-secret"])
+ self.cli_set(vpn_path + ["profile", "NHRPVPN", "authentication", "pre-shared-secret", "secret"])
+ self.cli_set(vpn_path + ["profile", "NHRPVPN", "bind", "tunnel", "tun100"])
+ self.cli_set(vpn_path + ["profile", "NHRPVPN", "esp-group", "ESP-HUB"])
+ self.cli_set(vpn_path + ["profile", "NHRPVPN", "ike-group", "IKE-HUB"])
+
+ self.cli_commit()
+
+ opennhrp_lines = [
+ 'interface tun100 #hub NHRPVPN',
+ 'cisco-authentication secret',
+ 'holding-time 300',
+ 'shortcut',
+ 'multicast dynamic',
+ 'redirect'
+ ]
+
+ tmp_opennhrp_conf = read_file('/run/opennhrp/opennhrp.conf')
+
+ for line in opennhrp_lines:
+ self.assertIn(line, tmp_opennhrp_conf)
+
+ self.assertTrue(process_named_running('opennhrp'))
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/src/conf_mode/protocols_nhrp.py b/src/conf_mode/protocols_nhrp.py
new file mode 100755
index 000000000..12dacdba0
--- /dev/null
+++ b/src/conf_mode/protocols_nhrp.py
@@ -0,0 +1,122 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from vyos.config import Config
+from vyos.configdict import node_changed
+from vyos.template import render
+from vyos.util import process_named_running
+from vyos.util import run
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+opennhrp_conf = '/run/opennhrp/opennhrp.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'], key_mangling=('-', '_'))
+
+ 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)
+
+ 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']:
+ raise ConfigError(f'Tunnel interface "{name}" does not exist')
+
+ tunnel_conf = nhrp['if_tunnel'][name]
+
+ if 'encapsulation' not in tunnel_conf or tunnel_conf['encapsulation'] != 'gre':
+ raise ConfigError(f'Tunnel "{name}" is not an mGRE tunnel')
+
+ 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:
+ 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}')
+ return None
+
+def generate(nhrp):
+ render(opennhrp_conf, 'nhrp/opennhrp.conf.tmpl', nhrp)
+ return None
+
+def apply(nhrp):
+ if 'tunnel' in nhrp:
+ for tunnel, tunnel_conf in nhrp['tunnel'].items():
+ if 'source_address' in tunnel_conf:
+ chain = f'VYOS_NHRP_{tunnel}_OUT_HOOK'
+ source_address = tunnel_conf['source_address']
+
+ chain_exists = run(f'sudo iptables --check {chain} -j RETURN') == 0
+ if not chain_exists:
+ run(f'sudo iptables --new {chain}')
+ run(f'sudo iptables --append {chain} -p gre -s {source_address} -d 224.0.0.0/4 -j DROP')
+ run(f'sudo iptables --append {chain} -j RETURN')
+ run(f'sudo iptables --insert OUTPUT 2 -j {chain}')
+
+ for tunnel in nhrp['del_tunnels']:
+ chain = f'VYOS_NHRP_{tunnel}_OUT_HOOK'
+ chain_exists = run(f'sudo iptables --check {chain} -j RETURN') == 0
+ if chain_exists:
+ run(f'sudo iptables --delete OUTPUT -j {chain}')
+ run(f'sudo iptables --flush {chain}')
+ run(f'sudo iptables --delete-chain {chain}')
+
+ action = 'restart' if nhrp and 'tunnel' in nhrp else 'stop'
+ run(f'systemctl {action} opennhrp')
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index c57697a8f..eedb9098c 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -356,7 +356,9 @@ def resync_nhrp(ipsec):
if ipsec and not ipsec['nhrp_exists']:
return
- run('/opt/vyatta/sbin/vyos-update-nhrp.pl --set_ipsec')
+ tmp = run('/usr/libexec/vyos/conf_mode/protocols_nhrp.py')
+ if tmp > 0:
+ print('ERROR: failed to reapply NHRP settings!')
def apply(ipsec):
if not ipsec:
diff --git a/src/etc/opennhrp/opennhrp-script.py b/src/etc/opennhrp/opennhrp-script.py
new file mode 100755
index 000000000..74c45f2f6
--- /dev/null
+++ b/src/etc/opennhrp/opennhrp-script.py
@@ -0,0 +1,136 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from pprint import pprint
+import os
+import re
+import sys
+import vici
+
+from vyos.util import cmd
+from vyos.util import process_named_running
+
+NHRP_CONFIG="/etc/opennhrp/opennhrp.conf"
+
+def parse_type_ipsec(interface):
+ with open(NHRP_CONFIG, 'r') as f:
+ lines = f.readlines()
+ match = rf'^interface {interface} #(hub|spoke)(?:\s([\w-]+))?$'
+ for line in lines:
+ m = re.match(match, line)
+ if m:
+ return m[1], m[2]
+ return None, None
+
+def vici_initiate(conn, child_sa, src_addr, dest_addr):
+ try:
+ session = vici.Session()
+ logs = session.initiate({
+ 'ike': conn,
+ 'child': child_sa,
+ 'timeout': '-1',
+ 'my-host': src_addr,
+ 'other-host': dest_addr
+ })
+ for log in logs:
+ message = log['msg'].decode('ascii')
+ print('INIT LOG:', message)
+ return True
+ except:
+ return None
+
+def vici_terminate(conn, child_sa, src_addr, dest_addr):
+ try:
+ session = vici.Session()
+ logs = session.terminate({
+ 'ike': conn,
+ 'child': child_sa,
+ 'timeout': '-1',
+ 'my-host': src_addr,
+ 'other-host': dest_addr
+ })
+ for log in logs:
+ message = log['msg'].decode('ascii')
+ print('TERM LOG:', message)
+ return True
+ except:
+ return None
+
+def iface_up(interface):
+ cmd(f'sudo ip route flush proto 42 dev {interface}')
+ cmd(f'sudo ip neigh flush dev {interface}')
+
+def peer_up(dmvpn_type, conn):
+ src_addr = os.getenv('NHRP_SRCADDR')
+ src_nbma = os.getenv('NHRP_SRCNBMA')
+ dest_addr = os.getenv('NHRP_DESTADDR')
+ dest_nbma = os.getenv('NHRP_DESTNBMA')
+ dest_mtu = os.getenv('NHRP_DESTMTU')
+
+ if dest_mtu:
+ args = cmd(f'sudo ip route get {dest_nbma} from {src_nbma}')
+ cmd(f'sudo ip route add {args} proto 42 mtu {dest_mtu}')
+
+ if conn and dmvpn_type == 'spoke' and process_named_running('charon'):
+ vici_terminate(conn, 'dmvpn', src_nbma, dest_nbma)
+ vici_initiate(conn, 'dmvpn', src_nbma, dest_nbma)
+
+def peer_down(dmvpn_type, conn):
+ src_nbma = os.getenv('NHRP_SRCNBMA')
+ dest_nbma = os.getenv('NHRP_DESTNBMA')
+
+ if conn and dmvpn_type == 'spoke' and process_named_running('charon'):
+ vici_terminate(conn, 'dmvpn', src_nbma, dest_nbma)
+
+ cmd(f'sudo ip route del {dest_nbma} src {src_nbma} proto 42')
+
+def route_up(interface):
+ dest_addr = os.getenv('NHRP_DESTADDR')
+ dest_prefix = os.getenv('NHRP_DESTPREFIX')
+ next_hop = os.getenv('NHRP_NEXTHOP')
+
+ cmd(f'sudo ip route replace {dest_addr}/{dest_prefix} proto 42 via {next_hop} dev {interface}')
+ cmd('sudo ip route flush cache')
+
+def route_down(interface):
+ dest_addr = os.getenv('NHRP_DESTADDR')
+ dest_prefix = os.getenv('NHRP_DESTPREFIX')
+
+ cmd(f'sudo ip route del {dest_addr}/{dest_prefix} proto 42')
+ cmd('sudo ip route flush cache')
+
+if __name__ == '__main__':
+ action = sys.argv[1]
+ interface = os.getenv('NHRP_INTERFACE')
+ dmvpn_type, profile_name = parse_type_ipsec(interface)
+
+ dmvpn_conn = None
+
+ if profile_name:
+ dmvpn_conn = f'dmvpn-{profile_name}-{interface}'
+
+ if action == 'interface-up':
+ iface_up(interface)
+ elif action == 'peer-register':
+ pass
+ elif action == 'peer-up':
+ peer_up(dmvpn_type, dmvpn_conn)
+ elif action == 'peer-down':
+ peer_down(dmvpn_type, dmvpn_conn)
+ elif action == 'route-up':
+ route_up(interface)
+ elif action == 'route-down':
+ route_down(interface)
diff --git a/src/systemd/opennhrp.service b/src/systemd/opennhrp.service
new file mode 100644
index 000000000..70235f89d
--- /dev/null
+++ b/src/systemd/opennhrp.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=OpenNHRP
+After=vyos-router.service
+ConditionPathExists=/run/opennhrp/opennhrp.conf
+StartLimitIntervalSec=0
+
+[Service]
+Type=forking
+ExecStart=/usr/sbin/opennhrp -d -v -a /run/opennhrp.socket -c /run/opennhrp/opennhrp.conf -s /etc/opennhrp/opennhrp-script.py -p /run/opennhrp.pid
+ExecReload=/usr/bin/kill -HUP $MAINPID
+PIDFile=/run/opennhrp.pid
+Restart=on-failure
+RestartSec=20