summaryrefslogtreecommitdiffstats
path: root/nhrpd
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:53:30 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:53:30 +0000
commit2c7cac91ed6e7db0f6937923d2b57f97dbdbc337 (patch)
treec05dc0f8e6aa3accc84e3e5cffc933ed94941383 /nhrpd
parentInitial commit. (diff)
downloadfrr-2c7cac91ed6e7db0f6937923d2b57f97dbdbc337.tar.xz
frr-2c7cac91ed6e7db0f6937923d2b57f97dbdbc337.zip
Adding upstream version 8.4.4.upstream/8.4.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'nhrpd')
-rw-r--r--nhrpd/.gitignore1
-rw-r--r--nhrpd/Makefile10
-rw-r--r--nhrpd/README.kernel146
-rw-r--r--nhrpd/README.nhrpd138
-rw-r--r--nhrpd/debug.h25
-rw-r--r--nhrpd/linux.c152
-rw-r--r--nhrpd/netlink.h22
-rw-r--r--nhrpd/netlink_arp.c202
-rw-r--r--nhrpd/nhrp_cache.c575
-rw-r--r--nhrpd/nhrp_errors.c43
-rw-r--r--nhrpd/nhrp_errors.h32
-rw-r--r--nhrpd/nhrp_event.c290
-rw-r--r--nhrpd/nhrp_interface.c558
-rw-r--r--nhrpd/nhrp_main.c172
-rw-r--r--nhrpd/nhrp_multicast.c298
-rw-r--r--nhrpd/nhrp_nhs.c467
-rw-r--r--nhrpd/nhrp_packet.c345
-rw-r--r--nhrpd/nhrp_peer.c1253
-rw-r--r--nhrpd/nhrp_protocol.h129
-rw-r--r--nhrpd/nhrp_route.c524
-rw-r--r--nhrpd/nhrp_shortcut.c543
-rw-r--r--nhrpd/nhrp_vc.c221
-rw-r--r--nhrpd/nhrp_vty.c1273
-rw-r--r--nhrpd/nhrpd.h536
-rw-r--r--nhrpd/os.h7
-rw-r--r--nhrpd/reqid.c52
-rw-r--r--nhrpd/subdir.am45
-rw-r--r--nhrpd/vici.c621
-rw-r--r--nhrpd/vici.h24
-rw-r--r--nhrpd/zbuf.c241
-rw-r--r--nhrpd/zbuf.h203
-rw-r--r--nhrpd/znl.c170
-rw-r--r--nhrpd/znl.h29
33 files changed, 9347 insertions, 0 deletions
diff --git a/nhrpd/.gitignore b/nhrpd/.gitignore
new file mode 100644
index 0000000..3d4d56d
--- /dev/null
+++ b/nhrpd/.gitignore
@@ -0,0 +1 @@
+nhrpd
diff --git a/nhrpd/Makefile b/nhrpd/Makefile
new file mode 100644
index 0000000..62c9546
--- /dev/null
+++ b/nhrpd/Makefile
@@ -0,0 +1,10 @@
+all: ALWAYS
+ @$(MAKE) -s -C .. nhrpd/nhrpd
+%: ALWAYS
+ @$(MAKE) -s -C .. nhrpd/$@
+
+Makefile:
+ #nothing
+ALWAYS:
+.PHONY: ALWAYS makefiles
+.SUFFIXES:
diff --git a/nhrpd/README.kernel b/nhrpd/README.kernel
new file mode 100644
index 0000000..067ff98
--- /dev/null
+++ b/nhrpd/README.kernel
@@ -0,0 +1,146 @@
+KERNEL REQUIREMENTS
+===================
+
+The linux kernel has had various major regressions, performance
+issues and subtle bugs (especially in pmtu). Here is a short list
+of some -stable kernels and the first point release that is supposedly
+working well with opennhrp/dmvpn:
+ 3.12.8 or later
+ 3.14.54 or later
+ 3.18.22 or later[1]
+
+[1] But you need to apply the following two backported commits:
+ 3cdaa5be9e ipv4: Don't increase PMTU with Datagram Too Big message
+ cb6ccf09d6 route: Use ipv4_mtu instead of raw rt_pmtu
+
+See below for list of known issues in various kernel versions.
+
+Kernels earlier than 3.12 need CONFIG_ARPD enabled in the configuration.
+Many distributions do not enable it by default, and you may need to
+compile your own kernel.
+
+KERNEL BUGS
+===========
+
+DMVPN and mGRE support in the kernel has been brittle. There are various
+regressions in multiple kernel versions.
+
+This list tries to collect them to one source of information:
+
+- forward pmtu is disabled intentionally (but tunnel devices rely on it)
+ Broken since 3.14-rc1:
+ commit "ipv4: introduce ip_dst_mtu_maybe_forward and protect forwarding path against pmtu spoofing"
+ Workaround:
+ Set sysctl net.ipv4.ip_forward_use_pmtu=1
+ See: https://marc.info/?t=143636239500003&r=1&w=2 for details
+ (Should fix kernel to have this by default on for tunnel devices)
+
+- subtle path mtu mishandling issues
+ Broken since (uncertain)
+ Fixed in 4.1-rc2:
+ commit "ipv4: Don't increase PMTU with Datagram Too Big message."
+ commit "route: Use ipv4_mtu instead of raw rt_pmtu"
+
+- fragmentation of large packets inside tunnel not working
+ Broken since 3.11-rc1
+ commit "ip_tunnels: Use skb-len to PMTU check."
+ Fixed in 3.14.54, 3.18.22, 4.1.9, 4.2-rc3
+ commit "ip_tunnel: fix ipv4 pmtu check to honor inner ip header df"
+
+- ipsec will crash during xfrm gc
+ Broke since 3.15-rc1
+ commit "flowcache: Make flow cache name space aware"
+ Fixed in 3.18.10, 4.0
+ commit "flowcache: Fix kernel panic in flow_cache_flush_task"
+
+- TSO on GRE tunnels failed, and resulted in very slow performance
+ Broke since 3.14.24, 3.18-rc3
+ commit "gre: Use inner mac length when computing tunnel length"
+ Fixed in 3.14.30, 3.18.4
+ commit "gre: fix the inner mac header in nbma tunnel xmit path"
+ commit "gre: Set inner mac header in gro complete"
+
+- NAPI GRO handling was broken; causing immediate crash (32-bit only?)
+ Broken since 3.13-rc1
+ commit "net: gro: allow to build full sized skb"
+ Fixed 3.14.5, 3.15-rc7
+ commit "net: gro: make sure skb->cb[] initial content has not to be zero"
+
+- ip_gre dst caching broke NBMA GRE tunnels
+ Broken since 3.14-rc1
+ Fixed in 3.14.5, 3.15-rc6
+ commit "ipv4: ip_tunnels: disable cache for nbma gre tunnels"
+
+- Few packets can be lost when neighbor entry is in NUD_PROBE state,
+ and there is continuous traffic to it.
+ Broken since dawn of time
+ Fixed in 3.15-rc1
+ commit "neigh: probe application via netlink in NUD_PROBE"
+
+- GRO was implemented for GRE, but the hw capabilities were not updated
+ correctly. In practice forwarding from non-GRE (physical) interface
+ to GRE interface with gro/gso/tx offloads enabled (also on the target
+ interface) does not work properly.
+ Broken around 3.9 to 3.11, need to check details.
+
+- recvfrom() returned incorrect NBMA address, breaking NAT detection
+ Broken since 3.10-rc1
+ commit "GRE: Refactor GRE tunneling code."
+ Fixed in 3.10.27, 3.12.8, 3.13-rc7
+ commit "ip_gre: fix msg_name parsing for recvfrom/recvmsg"
+
+- sendto() was broken causing opennhrp not work at all
+ Broken since 3.10-rc1
+ commit "GRE: Refactor GRE tunneling code."
+ Fixed in 3.10.12, 3.11-rc6
+ commit "ip_gre: fix ipgre_header to return correct offset"
+
+- PMTU was broken due to GRE driver rewrite
+ Broken since 3.10-rc1
+ commit "GRE: Refactor GRE tunneling code."
+ Fixed in 3.11-rc1
+ commit "ip_tunnels: Use skb-len to PMTU check."
+
+- PMTU was broken due to routing cache removal
+ Broken since 3.6-rc1
+ commit "ipv4: Cache input routes in fib_info nexthops"
+ Fixed in 3.11-rc1
+ commit "ipv4: use next hop exceptions also for input routes"
+ + 3 other commits
+ Patches exist for 3.10, but they were not approved to 3.10-stable.
+
+- Race condition during bootup: changing ARP flag did not flush
+ existing neighbor entries, causing problems if traffic was routed
+ to gre interface before opennhrp was running.
+ Broken since dawn of time
+ Fixed in 3.11-rc1
+ commit "arp: flush arp cache on IFF_NOARP change"
+
+- Crash in IPsec
+ Broken since 3.9-rc1
+ commit "xfrm: removes a superfluous check and add a statistic"
+ Fixed in 3.10-rc3
+ commit "xfrm: properly handle invalid states as an error"
+
+- An incorrect ip_gre change broke NHRP traffic over GRE
+ Broken since 3.8-rc2
+ commit "ip_gre: make ipgre_tunnel_xmit() not parse network header as IP unconditionally"
+ Fixed in 3.8.5, 3.9-rc4
+ commit "Revert "ip_gre: make ipgre_tunnel_xmit() not parse network header as IP unconditionally""
+
+- Multicast traffic over mGRE was broken.
+ Broken since 2.6.34-rc2
+ commit "gre: fix hard header destination address checking"
+ Fixed in 2.6.39-rc2
+ commit "net: gre: provide multicast mappings for ipv4 and ipv6"
+
+- Serious performance issues causing small throughput on medium to large DMVPN networks
+ Broken since dawn of time
+ Fixed in 2.6.35
+ multiple commits rewriting ipsec caching
+
+- Even though around 2.6.24 is the first version where opennhrp started
+ to work, there has been various PMTU, performance, and functionality
+ bugs before 2.6.34. That's one of the first version I consider stable
+ wrt. to opennhrp functionality.
+
diff --git a/nhrpd/README.nhrpd b/nhrpd/README.nhrpd
new file mode 100644
index 0000000..8bb5f69
--- /dev/null
+++ b/nhrpd/README.nhrpd
@@ -0,0 +1,138 @@
+Quagga / NHRP Design and Configuration Notes
+============================================
+
+Quagga/NHRP is an NHRP (RFC2332) implementation for Linux. The primary
+use case is to implement DMVPN. The aim is thus to be compatible with
+Cisco DMVPN (and potentially with FlexVPN in the future).
+
+
+Current Status
+--------------
+
+- IPsec integration with strongSwan (requires patched strongSwan)
+- IPv4 over IPv4 NBMA GRE
+- IPv6 over IPv4 NBMA GRE -- majority of code exist; but is not tested
+- Spoke (NHC) functionality complete
+- Hub (NHS) functionality complete
+- Multicast support is not done yet
+ (so OSPF will not work, use BGP for now)
+
+The code is not (yet) compatible with Cisco FlexVPN style DMVPN. It
+would require relaying IKEv2 routing messages from strongSwan to nhrpd
+and parsing that. It is doable, but not implemented for the time being.
+
+
+Routing Design
+--------------
+
+In contrast to opennhrp routing design, Quagga/NHRP routes each NHRP
+domain address individually (similar to Cisco FlexVPN).
+
+To create NBMA GRE tunnel you might use following:
+ ip tunnel add gre1 mode gre key 42 ttl 64 dev eth0
+ ip addr add 10.255.255.2/32 dev gre1
+ ip link set gre1 up
+
+This has two important differences compared to opennhrp setup:
+ 1. The 'tunnel add' now specifies physical device binding. Quagga/NHRP
+ wants to know stable protocol address to NBMA address mapping. Thus,
+ add 'dev <physdev>' binding, or specify 'local <nbma-address>'. If
+ neither of this is specified, NHRP will not be enabled on the interface.
+ Alternatively you can skip 'dev' binding on tunnel if you allow
+ nhrpd to manage it using 'tunnel source' command (see below).
+
+ 2. The 'addr add' now has host prefix. In opennhrp you would have used
+ the GRE subnet prefix length here instead, e.g. /24.
+
+Quagga/NHRP will automatically create additional host routes pointing to
+gre1 when a connection with these hosts is established. The gre1 subnet
+should be announced by routing protocol. This allows routing protocol
+to decide which is the closest hub and get the gre addresses' traffic.
+
+The second benefit is that hubs can then easily exchange host prefixes
+of directly connected gre addresses. And thus routing of gre addresses
+inside hubs is based on routing protocol's shortest path choice -- not
+on random choice from next hop server list.
+
+
+Configuring nhrpd
+-----------------
+
+The configuration is done using vtysh, and most commands do what they
+do in Cisco. As minimal configuration example one can do:
+ configure terminal
+ interface gre1
+ tunnel protection vici profile dmvpn
+ tunnel source eth0
+ ip nhrp network-id 1
+ ip nhrp shortcut
+ ip nhrp registration no-unique
+ ip nhrp nhs dynamic nbma hubs.example.com
+
+There's important notes about the "ip nhrp nhs" command:
+
+ 1. The 'dynamic' works only against Cisco (or nhrpd), but is not
+ compatible with opennhrp. To use dynamic detection of opennhrp hub's
+ protocol address use the GRE broadcast address there. For the above
+ example of 10.255.255.0/24 the configuration should read instead:
+ ip nhrp nhs 10.255.255.255 nbma hubs.example.com
+
+ 2. nbma <FQDN> works like opennhrp dynamic-map. That is, all of the
+ A-records are configured as NBMA addresses of different hubs, and
+ each hub protocol address will be dynamically detected.
+
+
+Hub functionality
+-----------------
+
+Sending Traffic Indication (redirect) notifications is now accomplished
+using NFLOG.
+
+Use:
+iptables -A FORWARD -i gre1 -o gre1 \
+ -m hashlimit --hashlimit-upto 4/minute --hashlimit-burst 1 \
+ --hashlimit-mode srcip,dstip --hashlimit-srcmask 16 --hashlimit-dstmask 16 \
+ --hashlimit-name loglimit-0 -j NFLOG --nflog-group 1 --nflog-range 128
+
+or similar to get rate-limited samples of the packets that match traffic
+flow needing redirection. This kernel NFLOG target's nflog-group is configured
+in global nhrp config with:
+ nhrp nflog-group 1
+
+To start sending these traffic notices out from hubs, use the nhrp per-interface
+directive:
+ ip nhrp redirect
+
+opennhrp used PF_PACKET and tried to create packet filter to get only
+the packets of interest. Though, this was bad if shortcut fails to
+establish (remote policy, or both are behind NAT or restrictive
+firewalls), all of the relayaed traffic would match always.
+
+
+Getting information via vtysh
+-----------------------------
+
+Some commands of interest:
+ - show dmvpn
+ - show ip nhrp cache
+ - show ip nhrp shortcut
+ - show ip route nhrp
+ - clear ip nhrp cache
+ - clear ip nhrp shortcut
+
+
+Integration with strongSwan
+---------------------------
+
+Contrary to opennhrp, Quagga/NHRP has tight integration with IKE daemon.
+Currently strongSwan is supported using the VICI protocol. strongSwan
+is connected using UNIX socket (default /var/run/charon.vici use configure
+argument --with-vici-socket= to change).
+Thus nhrpd needs to be run as user that can open that file.
+
+Currently, you will need patched strongSwan. The working tree is at:
+ http://git.alpinelinux.org/cgit/user/tteras/strongswan/log/?h=tteras
+
+And the branch with patches against latest release are:
+ http://git.alpinelinux.org/cgit/user/tteras/strongswan/log/?h=tteras-release
+
diff --git a/nhrpd/debug.h b/nhrpd/debug.h
new file mode 100644
index 0000000..e9428fa
--- /dev/null
+++ b/nhrpd/debug.h
@@ -0,0 +1,25 @@
+#include "log.h"
+
+#if defined(__GNUC__) && (__GNUC__ >= 3)
+#define likely(_x) __builtin_expect(!!(_x), 1)
+#define unlikely(_x) __builtin_expect(!!(_x), 0)
+#else
+#define likely(_x) !!(_x)
+#define unlikely(_x) !!(_x)
+#endif
+
+#define NHRP_DEBUG_COMMON (1 << 0)
+#define NHRP_DEBUG_KERNEL (1 << 1)
+#define NHRP_DEBUG_IF (1 << 2)
+#define NHRP_DEBUG_ROUTE (1 << 3)
+#define NHRP_DEBUG_VICI (1 << 4)
+#define NHRP_DEBUG_EVENT (1 << 5)
+#define NHRP_DEBUG_ALL (0xFFFF)
+
+extern unsigned int debug_flags;
+
+#define debugf(level, ...) \
+ do { \
+ if (unlikely(debug_flags & level)) \
+ zlog_debug(__VA_ARGS__); \
+ } while (0)
diff --git a/nhrpd/linux.c b/nhrpd/linux.c
new file mode 100644
index 0000000..75e9f37
--- /dev/null
+++ b/nhrpd/linux.c
@@ -0,0 +1,152 @@
+/* NHRP daemon Linux specific glue
+ * Copyright (c) 2014-2015 Timo Teräs
+ *
+ * This file is free software: you may copy, redistribute and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include "zebra.h"
+
+#include <errno.h>
+#include <linux/if_packet.h>
+
+#include "nhrp_protocol.h"
+#include "os.h"
+
+#ifndef HAVE_STRLCPY
+size_t strlcpy(char *__restrict dest,
+ const char *__restrict src, size_t destsize);
+#endif
+
+static int nhrp_socket_fd = -1;
+
+int os_socket(void)
+{
+ if (nhrp_socket_fd < 0)
+ nhrp_socket_fd =
+ socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_NHRP));
+ return nhrp_socket_fd;
+}
+
+int os_sendmsg(const uint8_t *buf, size_t len, int ifindex, const uint8_t *addr,
+ size_t addrlen, uint16_t protocol)
+{
+ struct sockaddr_ll lladdr;
+ struct iovec iov = {
+ .iov_base = (void *)buf, .iov_len = len,
+ };
+ struct msghdr msg = {
+ .msg_name = &lladdr,
+ .msg_namelen = sizeof(lladdr),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ };
+ int status, fd;
+
+ if (addrlen > sizeof(lladdr.sll_addr))
+ return -1;
+
+ memset(&lladdr, 0, sizeof(lladdr));
+ lladdr.sll_family = AF_PACKET;
+ lladdr.sll_protocol = htons(protocol);
+ lladdr.sll_ifindex = ifindex;
+ lladdr.sll_halen = addrlen;
+ memcpy(lladdr.sll_addr, addr, addrlen);
+
+ fd = os_socket();
+ if (fd < 0)
+ return -1;
+
+ status = sendmsg(fd, &msg, 0);
+ if (status < 0)
+ return -errno;
+
+ return status;
+}
+
+int os_recvmsg(uint8_t *buf, size_t *len, int *ifindex, uint8_t *addr,
+ size_t *addrlen)
+{
+ struct sockaddr_ll lladdr;
+ struct iovec iov = {
+ .iov_base = buf, .iov_len = *len,
+ };
+ struct msghdr msg = {
+ .msg_name = &lladdr,
+ .msg_namelen = sizeof(lladdr),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ };
+ int r;
+
+ r = recvmsg(nhrp_socket_fd, &msg, MSG_DONTWAIT);
+ if (r < 0)
+ return r;
+
+ *len = r;
+ *ifindex = lladdr.sll_ifindex;
+
+ if (*addrlen <= (size_t)lladdr.sll_addr) {
+ if (memcmp(lladdr.sll_addr, "\x00\x00\x00\x00", 4) != 0) {
+ memcpy(addr, lladdr.sll_addr, lladdr.sll_halen);
+ *addrlen = lladdr.sll_halen;
+ } else {
+ *addrlen = 0;
+ }
+ }
+
+ return 0;
+}
+
+static int linux_configure_arp(const char *iface, int on)
+{
+ struct ifreq ifr;
+
+ strlcpy(ifr.ifr_name, iface, IFNAMSIZ);
+ if (ioctl(nhrp_socket_fd, SIOCGIFFLAGS, &ifr))
+ return -1;
+
+ if (on)
+ ifr.ifr_flags &= ~IFF_NOARP;
+ else
+ ifr.ifr_flags |= IFF_NOARP;
+
+ if (ioctl(nhrp_socket_fd, SIOCSIFFLAGS, &ifr))
+ return -1;
+
+ return 0;
+}
+
+static int linux_icmp_redirect_off(const char *iface)
+{
+ char fname[PATH_MAX];
+ int fd, ret = -1;
+
+ snprintf(fname, sizeof(fname),
+ "/proc/sys/net/ipv4/conf/%s/send_redirects", iface);
+ fd = open(fname, O_WRONLY);
+ if (fd < 0)
+ return -1;
+ if (write(fd, "0\n", 2) == 2)
+ ret = 0;
+ close(fd);
+
+ return ret;
+}
+
+int os_configure_dmvpn(unsigned int ifindex, const char *ifname, int af)
+{
+ int ret = 0;
+
+ switch (af) {
+ case AF_INET:
+ ret |= linux_icmp_redirect_off("all");
+ ret |= linux_icmp_redirect_off(ifname);
+ break;
+ }
+ ret |= linux_configure_arp(ifname, 1);
+
+ return ret;
+}
diff --git a/nhrpd/netlink.h b/nhrpd/netlink.h
new file mode 100644
index 0000000..f1143a2
--- /dev/null
+++ b/nhrpd/netlink.h
@@ -0,0 +1,22 @@
+/* NHRP netlink/neighbor table API
+ * Copyright (c) 2014-2015 Timo Teräs
+ *
+ * This file is free software: you may copy, redistribute and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <zebra.h>
+#include <vrf.h>
+#include <if.h>
+
+
+extern int netlink_nflog_group;
+extern int netlink_mcast_nflog_group;
+
+int netlink_configure_arp(unsigned int ifindex, int pf);
+void netlink_update_binding(struct interface *ifp, union sockunion *proto,
+ union sockunion *nbma);
+void netlink_set_nflog_group(int nlgroup);
+
diff --git a/nhrpd/netlink_arp.c b/nhrpd/netlink_arp.c
new file mode 100644
index 0000000..cd7fb7a
--- /dev/null
+++ b/nhrpd/netlink_arp.c
@@ -0,0 +1,202 @@
+/* NHRP netlink/neighbor table arpd code
+ * Copyright (c) 2014-2016 Timo Teräs
+ *
+ * This file is free software: you may copy, redistribute and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <fcntl.h>
+#include <net/if.h>
+#include <netinet/if_ether.h>
+#include <linux/netlink.h>
+#include <linux/neighbour.h>
+#include <linux/netfilter/nfnetlink_log.h>
+
+#include "thread.h"
+#include "stream.h"
+#include "prefix.h"
+#include "nhrpd.h"
+#include "netlink.h"
+#include "znl.h"
+
+int netlink_nflog_group;
+static int netlink_log_fd = -1;
+static struct thread *netlink_log_thread;
+
+void netlink_update_binding(struct interface *ifp, union sockunion *proto,
+ union sockunion *nbma)
+{
+ nhrp_send_zebra_nbr(proto, nbma, ifp);
+}
+
+static void netlink_log_register(int fd, int group)
+{
+ struct nlmsghdr *n;
+ struct nfgenmsg *nf;
+ struct nfulnl_msg_config_cmd cmd;
+ struct zbuf *zb = zbuf_alloc(512);
+
+ n = znl_nlmsg_push(zb, (NFNL_SUBSYS_ULOG << 8) | NFULNL_MSG_CONFIG,
+ NLM_F_REQUEST | NLM_F_ACK);
+ nf = znl_push(zb, sizeof(*nf));
+ *nf = (struct nfgenmsg){
+ .nfgen_family = AF_UNSPEC,
+ .version = NFNETLINK_V0,
+ .res_id = htons(group),
+ };
+ cmd.command = NFULNL_CFG_CMD_BIND;
+ znl_rta_push(zb, NFULA_CFG_CMD, &cmd, sizeof(cmd));
+ znl_nlmsg_complete(zb, n);
+
+ zbuf_send(zb, fd);
+ zbuf_free(zb);
+}
+
+static void netlink_log_indication(struct nlmsghdr *msg, struct zbuf *zb)
+{
+ struct nfgenmsg *nf;
+ struct rtattr *rta;
+ struct zbuf rtapl, pktpl;
+ struct interface *ifp;
+ struct nfulnl_msg_packet_hdr *pkthdr = NULL;
+ uint32_t *in_ndx = NULL;
+
+ nf = znl_pull(zb, sizeof(*nf));
+ if (!nf)
+ return;
+
+ memset(&pktpl, 0, sizeof(pktpl));
+ while ((rta = znl_rta_pull(zb, &rtapl)) != NULL) {
+ switch (rta->rta_type) {
+ case NFULA_PACKET_HDR:
+ pkthdr = znl_pull(&rtapl, sizeof(*pkthdr));
+ break;
+ case NFULA_IFINDEX_INDEV:
+ in_ndx = znl_pull(&rtapl, sizeof(*in_ndx));
+ break;
+ case NFULA_PAYLOAD:
+ pktpl = rtapl;
+ break;
+ /* NFULA_HWHDR exists and is supposed to contain source
+ * hardware address. However, for ip_gre it seems to be
+ * the nexthop destination address if the packet matches
+ * route. */
+ }
+ }
+
+ if (!pkthdr || !in_ndx || !zbuf_used(&pktpl))
+ return;
+
+ ifp = if_lookup_by_index(htonl(*in_ndx), VRF_DEFAULT);
+ if (!ifp)
+ return;
+
+ nhrp_peer_send_indication(ifp, htons(pkthdr->hw_protocol), &pktpl);
+}
+
+static void netlink_log_recv(struct thread *t)
+{
+ uint8_t buf[ZNL_BUFFER_SIZE];
+ int fd = THREAD_FD(t);
+ struct zbuf payload, zb;
+ struct nlmsghdr *n;
+
+
+ zbuf_init(&zb, buf, sizeof(buf), 0);
+ while (zbuf_recv(&zb, fd) > 0) {
+ while ((n = znl_nlmsg_pull(&zb, &payload)) != NULL) {
+ debugf(NHRP_DEBUG_KERNEL,
+ "Netlink-log: Received msg_type %u, msg_flags %u",
+ n->nlmsg_type, n->nlmsg_flags);
+ switch (n->nlmsg_type) {
+ case (NFNL_SUBSYS_ULOG << 8) | NFULNL_MSG_PACKET:
+ netlink_log_indication(n, &payload);
+ break;
+ }
+ }
+ }
+
+ thread_add_read(master, netlink_log_recv, 0, netlink_log_fd,
+ &netlink_log_thread);
+}
+
+void netlink_set_nflog_group(int nlgroup)
+{
+ if (netlink_log_fd >= 0) {
+ thread_cancel(&netlink_log_thread);
+ close(netlink_log_fd);
+ netlink_log_fd = -1;
+ }
+ netlink_nflog_group = nlgroup;
+ if (nlgroup) {
+ netlink_log_fd = znl_open(NETLINK_NETFILTER, 0);
+ if (netlink_log_fd < 0)
+ return;
+
+ netlink_log_register(netlink_log_fd, nlgroup);
+ thread_add_read(master, netlink_log_recv, 0, netlink_log_fd,
+ &netlink_log_thread);
+ }
+}
+
+int nhrp_neighbor_operation(ZAPI_CALLBACK_ARGS)
+{
+ union sockunion addr = {}, lladdr = {};
+ struct interface *ifp;
+ int state, ndm_state;
+ struct nhrp_cache *c;
+ struct zapi_neigh_ip api = {};
+
+ zclient_neigh_ip_decode(zclient->ibuf, &api);
+ if (api.ip_in.ipa_type == AF_UNSPEC)
+ return 0;
+ sockunion_family(&addr) = api.ip_in.ipa_type;
+ memcpy((uint8_t *)sockunion_get_addr(&addr), &api.ip_in.ip.addr,
+ family2addrsize(api.ip_in.ipa_type));
+
+ sockunion_family(&lladdr) = api.ip_out.ipa_type;
+ if (api.ip_out.ipa_type != AF_UNSPEC)
+ memcpy((uint8_t *)sockunion_get_addr(&lladdr),
+ &api.ip_out.ip.addr,
+ family2addrsize(api.ip_out.ipa_type));
+
+ ifp = if_lookup_by_index(api.index, vrf_id);
+ ndm_state = api.ndm_state;
+
+ if (!ifp)
+ return 0;
+ c = nhrp_cache_get(ifp, &addr, 0);
+ if (!c)
+ return 0;
+ debugf(NHRP_DEBUG_KERNEL,
+ "Netlink: %s %pSU dev %s lladdr %pSU nud 0x%x cache used %u type %u",
+ (cmd == ZEBRA_NHRP_NEIGH_GET)
+ ? "who-has"
+ : (cmd == ZEBRA_NHRP_NEIGH_ADDED) ? "new-neigh"
+ : "del-neigh",
+ &addr, ifp->name, &lladdr, ndm_state, c->used, c->cur.type);
+ if (cmd == ZEBRA_NHRP_NEIGH_GET) {
+ if (c->cur.type >= NHRP_CACHE_CACHED) {
+ nhrp_cache_set_used(c, 1);
+ debugf(NHRP_DEBUG_KERNEL,
+ "Netlink: update binding for %pSU dev %s from c %pSU peer.vc.nbma %pSU to lladdr %pSU",
+ &addr, ifp->name, &c->cur.remote_nbma_natoa,
+ &c->cur.peer->vc->remote.nbma, &lladdr);
+ /* In case of shortcuts, nbma is given by lladdr, not
+ * vc->remote.nbma.
+ */
+ netlink_update_binding(ifp, &addr, &lladdr);
+ }
+ } else {
+ state = (cmd == ZEBRA_NHRP_NEIGH_ADDED) ? ndm_state
+ : ZEBRA_NEIGH_STATE_FAILED;
+ nhrp_cache_set_used(c, state == ZEBRA_NEIGH_STATE_REACHABLE);
+ }
+ return 0;
+}
diff --git a/nhrpd/nhrp_cache.c b/nhrpd/nhrp_cache.c
new file mode 100644
index 0000000..81d9bb2
--- /dev/null
+++ b/nhrpd/nhrp_cache.c
@@ -0,0 +1,575 @@
+/* NHRP cache
+ * Copyright (c) 2014-2015 Timo Teräs
+ *
+ * This file is free software: you may copy, redistribute and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include "zebra.h"
+#include "memory.h"
+#include "thread.h"
+#include "hash.h"
+#include "nhrpd.h"
+
+#include "netlink.h"
+
+DEFINE_MTYPE_STATIC(NHRPD, NHRP_CACHE, "NHRP cache entry");
+DEFINE_MTYPE_STATIC(NHRPD, NHRP_CACHE_CONFIG, "NHRP cache config entry");
+
+unsigned long nhrp_cache_counts[NHRP_CACHE_NUM_TYPES];
+
+const char *const nhrp_cache_type_str[] = {
+ [NHRP_CACHE_INVALID] = "invalid",
+ [NHRP_CACHE_INCOMPLETE] = "incomplete",
+ [NHRP_CACHE_NEGATIVE] = "negative",
+ [NHRP_CACHE_CACHED] = "cached",
+ [NHRP_CACHE_DYNAMIC] = "dynamic",
+ [NHRP_CACHE_NHS] = "nhs",
+ [NHRP_CACHE_STATIC] = "static",
+ [NHRP_CACHE_LOCAL] = "local",
+};
+
+static unsigned int nhrp_cache_protocol_key(const void *peer_data)
+{
+ const struct nhrp_cache *p = peer_data;
+ return sockunion_hash(&p->remote_addr);
+}
+
+static bool nhrp_cache_protocol_cmp(const void *cache_data,
+ const void *key_data)
+{
+ const struct nhrp_cache *a = cache_data;
+ const struct nhrp_cache *b = key_data;
+
+ return sockunion_same(&a->remote_addr, &b->remote_addr);
+}
+
+static void *nhrp_cache_alloc(void *data)
+{
+ struct nhrp_cache *p, *key = data;
+
+ p = XMALLOC(MTYPE_NHRP_CACHE, sizeof(struct nhrp_cache));
+
+ *p = (struct nhrp_cache){
+ .cur.type = NHRP_CACHE_INVALID,
+ .new.type = NHRP_CACHE_INVALID,
+ .remote_addr = key->remote_addr,
+ .ifp = key->ifp,
+ .notifier_list =
+ NOTIFIER_LIST_INITIALIZER(&p->notifier_list),
+ };
+ nhrp_cache_counts[p->cur.type]++;
+
+ return p;
+}
+
+static void nhrp_cache_free(struct nhrp_cache *c)
+{
+ struct nhrp_interface *nifp = c->ifp->info;
+
+ debugf(NHRP_DEBUG_COMMON, "Deleting cache entry");
+ nhrp_cache_counts[c->cur.type]--;
+ notifier_call(&c->notifier_list, NOTIFY_CACHE_DELETE);
+ assert(!notifier_active(&c->notifier_list));
+ hash_release(nifp->cache_hash, c);
+ THREAD_OFF(c->t_timeout);
+ THREAD_OFF(c->t_auth);
+ XFREE(MTYPE_NHRP_CACHE, c);
+}
+
+static unsigned int nhrp_cache_config_protocol_key(const void *peer_data)
+{
+ const struct nhrp_cache_config *p = peer_data;
+ return sockunion_hash(&p->remote_addr);
+}
+
+static bool nhrp_cache_config_protocol_cmp(const void *cache_data,
+ const void *key_data)
+{
+ const struct nhrp_cache_config *a = cache_data;
+ const struct nhrp_cache_config *b = key_data;
+
+ if (!sockunion_same(&a->remote_addr, &b->remote_addr))
+ return false;
+ if (a->ifp != b->ifp)
+ return false;
+ return true;
+}
+
+static void *nhrp_cache_config_alloc(void *data)
+{
+ struct nhrp_cache_config *p, *key = data;
+
+ p = XCALLOC(MTYPE_NHRP_CACHE_CONFIG, sizeof(struct nhrp_cache_config));
+
+ *p = (struct nhrp_cache_config){
+ .remote_addr = key->remote_addr,
+ .ifp = key->ifp,
+ };
+ return p;
+}
+
+void nhrp_cache_config_free(struct nhrp_cache_config *c)
+{
+ struct nhrp_interface *nifp = c->ifp->info;
+
+ hash_release(nifp->cache_config_hash, c);
+ XFREE(MTYPE_NHRP_CACHE_CONFIG, c);
+}
+
+struct nhrp_cache_config *nhrp_cache_config_get(struct interface *ifp,
+ union sockunion *remote_addr,
+ int create)
+{
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_cache_config key;
+
+ if (!nifp->cache_config_hash) {
+ nifp->cache_config_hash =
+ hash_create(nhrp_cache_config_protocol_key,
+ nhrp_cache_config_protocol_cmp,
+ "NHRP Config Cache");
+ if (!nifp->cache_config_hash)
+ return NULL;
+ }
+ key.remote_addr = *remote_addr;
+ key.ifp = ifp;
+
+ return hash_get(nifp->cache_config_hash, &key,
+ create ? nhrp_cache_config_alloc : NULL);
+}
+
+static void do_nhrp_cache_free(struct hash_bucket *hb,
+ void *arg __attribute__((__unused__)))
+{
+ struct nhrp_cache *c = hb->data;
+
+ nhrp_cache_free(c);
+}
+
+static void do_nhrp_cache_config_free(struct hash_bucket *hb,
+ void *arg __attribute__((__unused__)))
+{
+ struct nhrp_cache_config *cc = hb->data;
+
+ nhrp_cache_config_free(cc);
+}
+
+void nhrp_cache_interface_del(struct interface *ifp)
+{
+ struct nhrp_interface *nifp = ifp->info;
+
+ debugf(NHRP_DEBUG_COMMON, "Cleaning up undeleted cache entries (%lu)",
+ nifp->cache_hash ? nifp->cache_hash->count : 0);
+
+ if (nifp->cache_hash) {
+ hash_iterate(nifp->cache_hash, do_nhrp_cache_free, NULL);
+ hash_free(nifp->cache_hash);
+ }
+
+ if (nifp->cache_config_hash) {
+ hash_iterate(nifp->cache_config_hash, do_nhrp_cache_config_free,
+ NULL);
+ hash_free(nifp->cache_config_hash);
+ }
+}
+
+struct nhrp_cache *nhrp_cache_get(struct interface *ifp,
+ union sockunion *remote_addr, int create)
+{
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_cache key;
+
+ if (!nifp->cache_hash) {
+ nifp->cache_hash =
+ hash_create(nhrp_cache_protocol_key,
+ nhrp_cache_protocol_cmp, "NHRP Cache");
+ if (!nifp->cache_hash)
+ return NULL;
+ }
+
+ key.remote_addr = *remote_addr;
+ key.ifp = ifp;
+
+ return hash_get(nifp->cache_hash, &key,
+ create ? nhrp_cache_alloc : NULL);
+}
+
+static void nhrp_cache_do_free(struct thread *t)
+{
+ struct nhrp_cache *c = THREAD_ARG(t);
+
+ c->t_timeout = NULL;
+ nhrp_cache_free(c);
+}
+
+static void nhrp_cache_do_timeout(struct thread *t)
+{
+ struct nhrp_cache *c = THREAD_ARG(t);
+
+ c->t_timeout = NULL;
+ if (c->cur.type != NHRP_CACHE_INVALID)
+ nhrp_cache_update_binding(c, c->cur.type, -1, NULL, 0, NULL,
+ NULL);
+}
+
+static void nhrp_cache_update_route(struct nhrp_cache *c)
+{
+ struct prefix pfx;
+ struct nhrp_peer *p = c->cur.peer;
+ struct nhrp_interface *nifp;
+
+ if (!sockunion2hostprefix(&c->remote_addr, &pfx))
+ return;
+
+ if (p && nhrp_peer_check(p, 1)) {
+ if (sockunion_family(&c->cur.remote_nbma_natoa) != AF_UNSPEC) {
+ /* remote_nbma_natoa is already set. Therefore, binding
+ * should be updated to this value and not vc's remote
+ * nbma.
+ */
+ debugf(NHRP_DEBUG_COMMON,
+ "cache (remote_nbma_natoa set): Update binding for %pSU dev %s from (deleted) peer.vc.nbma %pSU to %pSU",
+ &c->remote_addr, p->ifp->name,
+ &p->vc->remote.nbma, &c->cur.remote_nbma_natoa);
+
+ netlink_update_binding(p->ifp, &c->remote_addr,
+ &c->cur.remote_nbma_natoa);
+ } else {
+ /* update binding to peer->vc->remote->nbma */
+ debugf(NHRP_DEBUG_COMMON,
+ "cache (remote_nbma_natoa unspec): Update binding for %pSU dev %s from (deleted) to peer.vc.nbma %pSU",
+ &c->remote_addr, p->ifp->name,
+ &p->vc->remote.nbma);
+
+ netlink_update_binding(p->ifp, &c->remote_addr,
+ &p->vc->remote.nbma);
+ }
+
+ nhrp_route_announce(1, c->cur.type, &pfx, c->ifp, NULL,
+ c->cur.mtu);
+ if (c->cur.type >= NHRP_CACHE_DYNAMIC) {
+ nhrp_route_update_nhrp(&pfx, c->ifp);
+ c->nhrp_route_installed = 1;
+ } else if (c->nhrp_route_installed) {
+ nhrp_route_update_nhrp(&pfx, NULL);
+ c->nhrp_route_installed = 0;
+ }
+ if (!c->route_installed) {
+ notifier_call(&c->notifier_list, NOTIFY_CACHE_UP);
+ c->route_installed = 1;
+ }
+ } else {
+ /* debug the reason for peer check fail */
+ if (p) {
+ nifp = p->ifp->info;
+ debugf(NHRP_DEBUG_COMMON,
+ "cache (peer check failed: online?%d requested?%d ipsec?%d)",
+ p->online, p->requested,
+ nifp->ipsec_profile ? 1 : 0);
+ } else
+ debugf(NHRP_DEBUG_COMMON,
+ "cache (peer check failed: no p)");
+
+ if (c->nhrp_route_installed) {
+ nhrp_route_update_nhrp(&pfx, NULL);
+ c->nhrp_route_installed = 0;
+ }
+ if (c->route_installed) {
+ assert(sockunion2hostprefix(&c->remote_addr, &pfx));
+ notifier_call(&c->notifier_list, NOTIFY_CACHE_DOWN);
+ nhrp_route_announce(0, c->cur.type, &pfx, NULL, NULL,
+ 0);
+ c->route_installed = 0;
+ }
+ }
+}
+
+static void nhrp_cache_peer_notifier(struct notifier_block *n,
+ unsigned long cmd)
+{
+ struct nhrp_cache *c =
+ container_of(n, struct nhrp_cache, peer_notifier);
+
+ switch (cmd) {
+ case NOTIFY_PEER_UP:
+ nhrp_cache_update_route(c);
+ break;
+ case NOTIFY_PEER_DOWN:
+ case NOTIFY_PEER_IFCONFIG_CHANGED:
+ notifier_call(&c->notifier_list, NOTIFY_CACHE_DOWN);
+ nhrp_cache_update_binding(c, c->cur.type, -1, NULL, 0, NULL,
+ NULL);
+ break;
+ case NOTIFY_PEER_NBMA_CHANGING:
+ if (c->cur.type == NHRP_CACHE_DYNAMIC)
+ c->cur.peer->vc->abort_migration = 1;
+ break;
+ }
+}
+
+static void nhrp_cache_reset_new(struct nhrp_cache *c)
+{
+ THREAD_OFF(c->t_auth);
+ if (notifier_list_anywhere(&c->newpeer_notifier))
+ nhrp_peer_notify_del(c->new.peer, &c->newpeer_notifier);
+ nhrp_peer_unref(c->new.peer);
+ memset(&c->new, 0, sizeof(c->new));
+ c->new.type = NHRP_CACHE_INVALID;
+}
+
+static void nhrp_cache_update_timers(struct nhrp_cache *c)
+{
+ THREAD_OFF(c->t_timeout);
+
+ switch (c->cur.type) {
+ case NHRP_CACHE_INVALID:
+ if (!c->t_auth)
+ thread_add_timer_msec(master, nhrp_cache_do_free, c, 10,
+ &c->t_timeout);
+ break;
+ default:
+ if (c->cur.expires)
+ thread_add_timer(master, nhrp_cache_do_timeout, c,
+ c->cur.expires - monotime(NULL),
+ &c->t_timeout);
+ break;
+ }
+}
+
+static void nhrp_cache_authorize_binding(struct nhrp_reqid *r, void *arg)
+{
+ struct nhrp_cache *c = container_of(r, struct nhrp_cache, eventid);
+ char buf[3][SU_ADDRSTRLEN];
+
+ debugf(NHRP_DEBUG_COMMON, "cache: %s %pSU: %s", c->ifp->name,
+ &c->remote_addr, (const char *)arg);
+
+ nhrp_reqid_free(&nhrp_event_reqid, r);
+
+ if (arg && strcmp(arg, "accept") == 0) {
+ if (c->cur.peer) {
+ netlink_update_binding(c->cur.peer->ifp,
+ &c->remote_addr, NULL);
+ nhrp_peer_notify_del(c->cur.peer, &c->peer_notifier);
+ nhrp_peer_unref(c->cur.peer);
+ }
+ nhrp_cache_counts[c->cur.type]--;
+ nhrp_cache_counts[c->new.type]++;
+ c->cur = c->new;
+ c->cur.peer = nhrp_peer_ref(c->cur.peer);
+ nhrp_cache_reset_new(c);
+ if (c->cur.peer)
+ nhrp_peer_notify_add(c->cur.peer, &c->peer_notifier,
+ nhrp_cache_peer_notifier);
+
+ if (sockunion_family(&c->cur.remote_nbma_natoa) != AF_UNSPEC) {
+ debugf(NHRP_DEBUG_COMMON,
+ "cache: update binding for %pSU dev %s from (deleted) peer.vc.nbma %s to %pSU",
+ &c->remote_addr, c->ifp->name,
+ (c->cur.peer ? sockunion2str(
+ &c->cur.peer->vc->remote.nbma, buf[1],
+ sizeof(buf[1]))
+ : "(no peer)"),
+ &c->cur.remote_nbma_natoa);
+
+ if (c->cur.peer)
+ netlink_update_binding(
+ c->cur.peer->ifp, &c->remote_addr,
+ &c->cur.remote_nbma_natoa);
+ }
+
+ nhrp_cache_update_route(c);
+ notifier_call(&c->notifier_list, NOTIFY_CACHE_BINDING_CHANGE);
+ } else {
+ nhrp_cache_reset_new(c);
+ }
+
+ nhrp_cache_update_timers(c);
+}
+
+static void nhrp_cache_do_auth_timeout(struct thread *t)
+{
+ struct nhrp_cache *c = THREAD_ARG(t);
+ c->t_auth = NULL;
+ nhrp_cache_authorize_binding(&c->eventid, (void *)"timeout");
+}
+
+static void nhrp_cache_newpeer_notifier(struct notifier_block *n,
+ unsigned long cmd)
+{
+ struct nhrp_cache *c =
+ container_of(n, struct nhrp_cache, newpeer_notifier);
+
+ switch (cmd) {
+ case NOTIFY_PEER_UP:
+ if (nhrp_peer_check(c->new.peer, 1)) {
+ evmgr_notify("authorize-binding", c,
+ nhrp_cache_authorize_binding);
+ thread_add_timer(master, nhrp_cache_do_auth_timeout, c,
+ 10, &c->t_auth);
+ }
+ break;
+ case NOTIFY_PEER_DOWN:
+ case NOTIFY_PEER_IFCONFIG_CHANGED:
+ nhrp_cache_reset_new(c);
+ break;
+ }
+}
+
+int nhrp_cache_update_binding(struct nhrp_cache *c, enum nhrp_cache_type type,
+ int holding_time, struct nhrp_peer *p,
+ uint32_t mtu, union sockunion *nbma_oa,
+ union sockunion *nbma_claimed)
+{
+ char buf[2][SU_ADDRSTRLEN];
+
+ if (c->cur.type > type || c->new.type > type) {
+ nhrp_peer_unref(p);
+ return 0;
+ }
+
+ /* Sanitize MTU */
+ switch (sockunion_family(&c->remote_addr)) {
+ case AF_INET:
+ if (mtu < 576 || mtu >= 1500)
+ mtu = 0;
+ /* Opennhrp announces nbma mtu, but we use protocol mtu.
+ * This heuristic tries to fix up it. */
+ if (mtu > 1420)
+ mtu = (mtu & -16) - 80;
+ break;
+ default:
+ mtu = 0;
+ break;
+ }
+
+ sockunion2str(&c->cur.remote_nbma_natoa, buf[0], sizeof(buf[0]));
+ if (nbma_oa)
+ sockunion2str(nbma_oa, buf[1], sizeof(buf[1]));
+
+ nhrp_cache_reset_new(c);
+ if (c->cur.type == type && c->cur.peer == p && c->cur.mtu == mtu) {
+ debugf(NHRP_DEBUG_COMMON,
+ "cache: same type %u, updating expiry and changing nbma addr from %s to %s",
+ type, buf[0], nbma_oa ? buf[1] : "(NULL)");
+ if (holding_time > 0)
+ c->cur.expires = monotime(NULL) + holding_time;
+
+ if (nbma_oa)
+ c->cur.remote_nbma_natoa = *nbma_oa;
+ else
+ memset(&c->cur.remote_nbma_natoa, 0,
+ sizeof(c->cur.remote_nbma_natoa));
+
+ if (nbma_claimed)
+ c->cur.remote_nbma_claimed = *nbma_claimed;
+ else
+ memset(&c->cur.remote_nbma_claimed, 0,
+ sizeof(c->cur.remote_nbma_claimed));
+
+ nhrp_peer_unref(p);
+ } else {
+ debugf(NHRP_DEBUG_COMMON,
+ "cache: new type %u/%u, or peer %s, or mtu %u/%u, nbma %s --> %s (map %d)",
+ c->cur.type, type, (c->cur.peer == p) ? "same" : "diff",
+ c->cur.mtu, mtu, buf[0], nbma_oa ? buf[1] : "(NULL)",
+ c->map);
+ c->new.type = type;
+ c->new.peer = p;
+ c->new.mtu = mtu;
+ c->new.holding_time = holding_time;
+ if (nbma_oa)
+ c->new.remote_nbma_natoa = *nbma_oa;
+
+ if (nbma_claimed)
+ c->new.remote_nbma_claimed = *nbma_claimed;
+
+ if (holding_time > 0)
+ c->new.expires = monotime(NULL) + holding_time;
+ else if (holding_time < 0)
+ nhrp_cache_reset_new(c);
+
+ if (c->new.type == NHRP_CACHE_INVALID
+ || c->new.type >= NHRP_CACHE_STATIC || c->map) {
+ nhrp_cache_authorize_binding(&c->eventid,
+ (void *)"accept");
+ } else {
+ nhrp_peer_notify_add(c->new.peer, &c->newpeer_notifier,
+ nhrp_cache_newpeer_notifier);
+ nhrp_cache_newpeer_notifier(&c->newpeer_notifier,
+ NOTIFY_PEER_UP);
+ thread_add_timer(master, nhrp_cache_do_auth_timeout, c,
+ 60, &c->t_auth);
+ }
+ }
+ nhrp_cache_update_timers(c);
+
+ return 1;
+}
+
+void nhrp_cache_set_used(struct nhrp_cache *c, int used)
+{
+ c->used = used;
+ if (c->used)
+ notifier_call(&c->notifier_list, NOTIFY_CACHE_USED);
+}
+
+struct nhrp_cache_iterator_ctx {
+ void (*cb)(struct nhrp_cache *, void *);
+ void *ctx;
+};
+
+struct nhrp_cache_config_iterator_ctx {
+ void (*cb)(struct nhrp_cache_config *, void *);
+ void *ctx;
+};
+
+static void nhrp_cache_iterator(struct hash_bucket *b, void *ctx)
+{
+ struct nhrp_cache_iterator_ctx *ic = ctx;
+ ic->cb(b->data, ic->ctx);
+}
+
+static void nhrp_cache_config_iterator(struct hash_bucket *b, void *ctx)
+{
+ struct nhrp_cache_config_iterator_ctx *ic = ctx;
+ ic->cb(b->data, ic->ctx);
+}
+
+void nhrp_cache_foreach(struct interface *ifp,
+ void (*cb)(struct nhrp_cache *, void *), void *ctx)
+{
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_cache_iterator_ctx ic = {
+ .cb = cb, .ctx = ctx,
+ };
+
+ if (nifp->cache_hash)
+ hash_iterate(nifp->cache_hash, nhrp_cache_iterator, &ic);
+}
+
+void nhrp_cache_config_foreach(struct interface *ifp,
+ void (*cb)(struct nhrp_cache_config *, void *), void *ctx)
+{
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_cache_config_iterator_ctx ic = {
+ .cb = cb, .ctx = ctx,
+ };
+
+ if (nifp->cache_config_hash)
+ hash_iterate(nifp->cache_config_hash, nhrp_cache_config_iterator, &ic);
+}
+
+void nhrp_cache_notify_add(struct nhrp_cache *c, struct notifier_block *n,
+ notifier_fn_t fn)
+{
+ notifier_add(n, &c->notifier_list, fn);
+}
+
+void nhrp_cache_notify_del(struct nhrp_cache *c, struct notifier_block *n)
+{
+ notifier_del(n, &c->notifier_list);
+}
diff --git a/nhrpd/nhrp_errors.c b/nhrpd/nhrp_errors.c
new file mode 100644
index 0000000..741e64d
--- /dev/null
+++ b/nhrpd/nhrp_errors.c
@@ -0,0 +1,43 @@
+/*
+ * NHRP-specific error messages.
+ * Copyright (C) 2018 Cumulus Networks, Inc.
+ * Donald Sharp
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * 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; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <zebra.h>
+
+#include "lib/ferr.h"
+#include "nhrp_errors.h"
+
+/* clang-format off */
+static struct log_ref ferr_nhrp_err[] = {
+ {
+ .code = EC_NHRP_SWAN,
+ .title = "NHRP Strong Swan Error",
+ .description = "NHRP has detected a error with the Strongswan code",
+ .suggestion = "Ensure that StrongSwan is configured correctly. Restart StrongSwan and FRR"
+ },
+ {
+ .code = END_FERR,
+ }
+};
+/* clang-format on */
+
+void nhrp_error_init(void)
+{
+ log_ref_add(ferr_nhrp_err);
+}
diff --git a/nhrpd/nhrp_errors.h b/nhrpd/nhrp_errors.h
new file mode 100644
index 0000000..d495835
--- /dev/null
+++ b/nhrpd/nhrp_errors.h
@@ -0,0 +1,32 @@
+/*
+ * NHRP-specific error messages.
+ * Copyright (C) 2018 Cumulus Networks, Inc.
+ * Donald Sharp
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * 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; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __NHRP_ERRORS_H__
+#define __NHRP_ERRORS_H__
+
+#include "lib/ferr.h"
+
+enum nhrp_log_refs {
+ EC_NHRP_SWAN = NHRP_FERR_START,
+};
+
+extern void nhrp_error_init(void);
+
+#endif
diff --git a/nhrpd/nhrp_event.c b/nhrpd/nhrp_event.c
new file mode 100644
index 0000000..e46a6d1
--- /dev/null
+++ b/nhrpd/nhrp_event.c
@@ -0,0 +1,290 @@
+/* NHRP event manager
+ * Copyright (c) 2014-2015 Timo Teräs
+ *
+ * This file is free software: you may copy, redistribute and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "thread.h"
+#include "zbuf.h"
+#include "log.h"
+#include "nhrpd.h"
+
+const char *nhrp_event_socket_path;
+struct nhrp_reqid_pool nhrp_event_reqid;
+
+struct event_manager {
+ struct thread *t_reconnect, *t_read, *t_write;
+ struct zbuf ibuf;
+ struct zbuf_queue obuf;
+ int fd;
+ uint8_t ibuf_data[4 * 1024];
+};
+
+static void evmgr_reconnect(struct thread *t);
+
+static void evmgr_connection_error(struct event_manager *evmgr)
+{
+ THREAD_OFF(evmgr->t_read);
+ THREAD_OFF(evmgr->t_write);
+ zbuf_reset(&evmgr->ibuf);
+ zbufq_reset(&evmgr->obuf);
+
+ if (evmgr->fd >= 0)
+ close(evmgr->fd);
+ evmgr->fd = -1;
+ if (nhrp_event_socket_path)
+ thread_add_timer_msec(master, evmgr_reconnect, evmgr, 10,
+ &evmgr->t_reconnect);
+}
+
+static void evmgr_recv_message(struct event_manager *evmgr, struct zbuf *zb)
+{
+ struct zbuf zl;
+ uint32_t eventid = 0;
+ size_t len;
+ char buf[256], result[64] = "";
+
+ while (zbuf_may_pull_until(zb, "\n", &zl)) {
+ len = zbuf_used(&zl) - 1;
+ if (len >= sizeof(buf) - 1)
+ continue;
+ memcpy(buf, zbuf_pulln(&zl, len), len);
+ buf[len] = 0;
+
+ debugf(NHRP_DEBUG_EVENT, "evmgr: msg: %s", buf);
+ if (sscanf(buf, "eventid=%" SCNu32, &eventid) == 1)
+ continue;
+ if (sscanf(buf, "result=%63s", result) == 1)
+ continue;
+ }
+ debugf(NHRP_DEBUG_EVENT, "evmgr: received: eventid=%d result=%s",
+ eventid, result);
+ if (eventid && result[0]) {
+ struct nhrp_reqid *r =
+ nhrp_reqid_lookup(&nhrp_event_reqid, eventid);
+ if (r)
+ r->cb(r, result);
+ }
+}
+
+static void evmgr_read(struct thread *t)
+{
+ struct event_manager *evmgr = THREAD_ARG(t);
+ struct zbuf *ibuf = &evmgr->ibuf;
+ struct zbuf msg;
+
+ if (zbuf_read(ibuf, evmgr->fd, (size_t)-1) < 0) {
+ evmgr_connection_error(evmgr);
+ return;
+ }
+
+ /* Process all messages in buffer */
+ while (zbuf_may_pull_until(ibuf, "\n\n", &msg))
+ evmgr_recv_message(evmgr, &msg);
+
+ thread_add_read(master, evmgr_read, evmgr, evmgr->fd, &evmgr->t_read);
+}
+
+static void evmgr_write(struct thread *t)
+{
+ struct event_manager *evmgr = THREAD_ARG(t);
+ int r;
+
+ r = zbufq_write(&evmgr->obuf, evmgr->fd);
+ if (r > 0) {
+ thread_add_write(master, evmgr_write, evmgr, evmgr->fd,
+ &evmgr->t_write);
+ } else if (r < 0) {
+ evmgr_connection_error(evmgr);
+ }
+}
+
+static void evmgr_hexdump(struct zbuf *zb, const uint8_t *val, size_t vallen)
+{
+ static const char xd[] = "0123456789abcdef";
+ size_t i;
+ char *ptr;
+
+ ptr = zbuf_pushn(zb, 2 * vallen);
+ if (!ptr)
+ return;
+
+ for (i = 0; i < vallen; i++) {
+ uint8_t b = val[i];
+ *(ptr++) = xd[b >> 4];
+ *(ptr++) = xd[b & 0xf];
+ }
+}
+
+static void evmgr_put(struct zbuf *zb, const char *fmt, ...)
+{
+ const char *pos, *nxt, *str;
+ const uint8_t *bin;
+ const union sockunion *su;
+ int len;
+ va_list va;
+
+ va_start(va, fmt);
+ for (pos = fmt; (nxt = strchr(pos, '%')) != NULL; pos = nxt + 2) {
+ zbuf_put(zb, pos, nxt - pos);
+ switch (nxt[1]) {
+ case '%':
+ zbuf_put8(zb, '%');
+ break;
+ case 'u':
+ zb->tail +=
+ snprintf((char *)zb->tail, zbuf_tailroom(zb),
+ "%u", va_arg(va, uint32_t));
+ break;
+ case 's':
+ str = va_arg(va, const char *);
+ zbuf_put(zb, str, strlen(str));
+ break;
+ case 'U':
+ su = va_arg(va, const union sockunion *);
+ if (sockunion2str(su, (char *)zb->tail,
+ zbuf_tailroom(zb)))
+ zb->tail += strlen((char *)zb->tail);
+ else
+ zbuf_set_werror(zb);
+ break;
+ case 'H':
+ bin = va_arg(va, const uint8_t *);
+ len = va_arg(va, int);
+ evmgr_hexdump(zb, bin, len);
+ break;
+ }
+ }
+ va_end(va);
+ zbuf_put(zb, pos, strlen(pos));
+}
+
+static void evmgr_submit(struct event_manager *evmgr, struct zbuf *obuf)
+{
+ if (obuf->error) {
+ zbuf_free(obuf);
+ return;
+ }
+ zbuf_put(obuf, "\n", 1);
+ zbufq_queue(&evmgr->obuf, obuf);
+ if (evmgr->fd >= 0)
+ thread_add_write(master, evmgr_write, evmgr, evmgr->fd,
+ &evmgr->t_write);
+}
+
+static void evmgr_reconnect(struct thread *t)
+{
+ struct event_manager *evmgr = THREAD_ARG(t);
+ int fd;
+
+ if (evmgr->fd >= 0 || !nhrp_event_socket_path)
+ return;
+
+ fd = sock_open_unix(nhrp_event_socket_path);
+ if (fd < 0) {
+ zlog_warn("%s: failure connecting nhrp-event socket: %s",
+ __func__, strerror(errno));
+ zbufq_reset(&evmgr->obuf);
+ thread_add_timer(master, evmgr_reconnect, evmgr, 10,
+ &evmgr->t_reconnect);
+ return;
+ }
+
+ zlog_info("Connected to Event Manager");
+ evmgr->fd = fd;
+ thread_add_read(master, evmgr_read, evmgr, evmgr->fd, &evmgr->t_read);
+}
+
+static struct event_manager evmgr_connection;
+
+void evmgr_init(void)
+{
+ struct event_manager *evmgr = &evmgr_connection;
+
+ evmgr->fd = -1;
+ zbuf_init(&evmgr->ibuf, evmgr->ibuf_data, sizeof(evmgr->ibuf_data), 0);
+ zbufq_init(&evmgr->obuf);
+ thread_add_timer_msec(master, evmgr_reconnect, evmgr, 10,
+ &evmgr->t_reconnect);
+}
+
+void evmgr_set_socket(const char *socket)
+{
+ if (nhrp_event_socket_path) {
+ free((char *)nhrp_event_socket_path);
+ nhrp_event_socket_path = NULL;
+ }
+ if (socket)
+ nhrp_event_socket_path = strdup(socket);
+ evmgr_connection_error(&evmgr_connection);
+}
+
+void evmgr_terminate(void)
+{
+}
+
+void evmgr_notify(const char *name, struct nhrp_cache *c,
+ void (*cb)(struct nhrp_reqid *, void *))
+{
+ struct event_manager *evmgr = &evmgr_connection;
+ struct nhrp_vc *vc;
+ struct nhrp_interface *nifp = c->ifp->info;
+ struct zbuf *zb;
+ afi_t afi = family2afi(sockunion_family(&c->remote_addr));
+
+ if (!nhrp_event_socket_path) {
+ cb(&c->eventid, (void *)"accept");
+ return;
+ }
+
+ debugf(NHRP_DEBUG_EVENT, "evmgr: sending event %s", name);
+
+ vc = c->new.peer ? c->new.peer->vc : NULL;
+ zb = zbuf_alloc(
+ 1024 + (vc ? (vc->local.certlen + vc->remote.certlen) * 2 : 0));
+
+ if (cb) {
+ nhrp_reqid_free(&nhrp_event_reqid, &c->eventid);
+ evmgr_put(zb, "eventid=%u\n",
+ nhrp_reqid_alloc(&nhrp_event_reqid, &c->eventid, cb));
+ }
+
+ evmgr_put(zb,
+ "event=%s\n"
+ "type=%s\n"
+ "old_type=%s\n"
+ "num_nhs=%u\n"
+ "interface=%s\n"
+ "local_addr=%U\n",
+ name, nhrp_cache_type_str[c->new.type],
+ nhrp_cache_type_str[c->cur.type],
+ (unsigned int)nhrp_cache_counts[NHRP_CACHE_NHS], c->ifp->name,
+ &nifp->afi[afi].addr);
+
+ if (vc) {
+ evmgr_put(zb,
+ "vc_initiated=%s\n"
+ "local_nbma=%U\n"
+ "local_cert=%H\n"
+ "remote_addr=%U\n"
+ "remote_nbma=%U\n"
+ "remote_cert=%H\n",
+ c->new.peer->requested ? "yes" : "no",
+ &vc->local.nbma, vc->local.cert, vc->local.certlen,
+ &c->remote_addr, &vc->remote.nbma, vc->remote.cert,
+ vc->remote.certlen);
+ }
+
+ evmgr_submit(evmgr, zb);
+}
diff --git a/nhrpd/nhrp_interface.c b/nhrpd/nhrp_interface.c
new file mode 100644
index 0000000..4ac30a7
--- /dev/null
+++ b/nhrpd/nhrp_interface.c
@@ -0,0 +1,558 @@
+/* NHRP interface
+ * Copyright (c) 2014-2015 Timo Teräs
+ *
+ * This file is free software: you may copy, redistribute and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <net/if_arp.h>
+#include "zebra.h"
+#include "linklist.h"
+#include "memory.h"
+#include "thread.h"
+
+#include "nhrpd.h"
+#include "os.h"
+#include "hash.h"
+
+DEFINE_MTYPE_STATIC(NHRPD, NHRP_IF, "NHRP interface");
+DEFINE_MTYPE_STATIC(NHRPD, NHRP_IF_GRE, "NHRP GRE interface");
+
+struct hash *nhrp_gre_list;
+
+static void nhrp_interface_update_cache_config(struct interface *ifp,
+ bool available,
+ uint8_t family);
+
+static unsigned int nhrp_gre_info_key(const void *data)
+{
+ const struct nhrp_gre_info *r = data;
+
+ return r->ifindex;
+}
+
+static bool nhrp_gre_info_cmp(const void *data, const void *key)
+{
+ const struct nhrp_gre_info *a = data, *b = key;
+
+ if (a->ifindex == b->ifindex)
+ return true;
+ return false;
+}
+
+static void *nhrp_interface_gre_alloc(void *data)
+{
+ struct nhrp_gre_info *a;
+ struct nhrp_gre_info *b = data;
+
+ a = XMALLOC(MTYPE_NHRP_IF_GRE, sizeof(struct nhrp_gre_info));
+ memcpy(a, b, sizeof(struct nhrp_gre_info));
+ return a;
+}
+
+struct nhrp_gre_info *nhrp_gre_info_alloc(struct nhrp_gre_info *p)
+{
+ struct nhrp_gre_info *a;
+
+ a = (struct nhrp_gre_info *)hash_get(nhrp_gre_list, p,
+ nhrp_interface_gre_alloc);
+ return a;
+}
+
+static int nhrp_if_new_hook(struct interface *ifp)
+{
+ struct nhrp_interface *nifp;
+ afi_t afi;
+
+ nifp = XCALLOC(MTYPE_NHRP_IF, sizeof(struct nhrp_interface));
+
+ ifp->info = nifp;
+ nifp->ifp = ifp;
+
+ notifier_init(&nifp->notifier_list);
+ for (afi = 0; afi < AFI_MAX; afi++) {
+ struct nhrp_afi_data *ad = &nifp->afi[afi];
+ ad->holdtime = NHRPD_DEFAULT_HOLDTIME;
+ nhrp_nhslist_init(&ad->nhslist_head);
+ nhrp_mcastlist_init(&ad->mcastlist_head);
+ }
+
+ return 0;
+}
+
+static int nhrp_if_delete_hook(struct interface *ifp)
+{
+ struct nhrp_interface *nifp = ifp->info;
+
+ debugf(NHRP_DEBUG_IF, "Deleted interface (%s)", ifp->name);
+
+ nhrp_cache_interface_del(ifp);
+ nhrp_nhs_interface_del(ifp);
+ nhrp_multicast_interface_del(ifp);
+ nhrp_peer_interface_del(ifp);
+
+ if (nifp->ipsec_profile)
+ free(nifp->ipsec_profile);
+ if (nifp->ipsec_fallback_profile)
+ free(nifp->ipsec_fallback_profile);
+ if (nifp->source)
+ free(nifp->source);
+
+ XFREE(MTYPE_NHRP_IF, ifp->info);
+ return 0;
+}
+
+void nhrp_interface_init(void)
+{
+ hook_register_prio(if_add, 0, nhrp_if_new_hook);
+ hook_register_prio(if_del, 0, nhrp_if_delete_hook);
+
+ nhrp_gre_list = hash_create(nhrp_gre_info_key, nhrp_gre_info_cmp,
+ "NHRP GRE list Hash");
+}
+
+void nhrp_interface_update_mtu(struct interface *ifp, afi_t afi)
+{
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_afi_data *if_ad = &nifp->afi[afi];
+ unsigned short new_mtu;
+
+ if (if_ad->configured_mtu < 0)
+ new_mtu = nifp->nbmaifp ? nifp->nbmaifp->mtu : 0;
+ else
+ new_mtu = if_ad->configured_mtu;
+ if (new_mtu >= 1500)
+ new_mtu = 0;
+
+ if (new_mtu != if_ad->mtu) {
+ debugf(NHRP_DEBUG_IF, "%s: MTU changed to %d", ifp->name,
+ new_mtu);
+ if_ad->mtu = new_mtu;
+ notifier_call(&nifp->notifier_list,
+ NOTIFY_INTERFACE_MTU_CHANGED);
+ }
+}
+
+static void nhrp_interface_update_source(struct interface *ifp)
+{
+ struct nhrp_interface *nifp = ifp->info;
+
+ if (!nifp->source || !nifp->nbmaifp
+ || ((ifindex_t)nifp->link_idx == nifp->nbmaifp->ifindex
+ && (nifp->link_vrf_id == nifp->nbmaifp->vrf->vrf_id)))
+ return;
+
+ nifp->link_idx = nifp->nbmaifp->ifindex;
+ nifp->link_vrf_id = nifp->nbmaifp->vrf->vrf_id;
+ debugf(NHRP_DEBUG_IF, "%s: bound device index changed to %d, vr %u",
+ ifp->name, nifp->link_idx, nifp->link_vrf_id);
+ nhrp_send_zebra_gre_source_set(ifp, nifp->link_idx, nifp->link_vrf_id);
+}
+
+static void nhrp_interface_interface_notifier(struct notifier_block *n,
+ unsigned long cmd)
+{
+ struct nhrp_interface *nifp =
+ container_of(n, struct nhrp_interface, nbmanifp_notifier);
+ struct interface *nbmaifp = nifp->nbmaifp;
+ struct nhrp_interface *nbmanifp = nbmaifp->info;
+
+ switch (cmd) {
+ case NOTIFY_INTERFACE_CHANGED:
+ nhrp_interface_update_nbma(nifp->ifp, NULL);
+ break;
+ case NOTIFY_INTERFACE_ADDRESS_CHANGED:
+ nifp->nbma = nbmanifp->afi[AFI_IP].addr;
+ nhrp_interface_update(nifp->ifp);
+ notifier_call(&nifp->notifier_list,
+ NOTIFY_INTERFACE_NBMA_CHANGED);
+ debugf(NHRP_DEBUG_IF, "%s: NBMA change: address %pSU",
+ nifp->ifp->name, &nifp->nbma);
+ break;
+ }
+}
+
+void nhrp_interface_update_nbma(struct interface *ifp,
+ struct nhrp_gre_info *gre_info)
+{
+ struct nhrp_interface *nifp = ifp->info, *nbmanifp = NULL;
+ struct interface *nbmaifp = NULL;
+ union sockunion nbma;
+
+ sockunion_family(&nbma) = AF_UNSPEC;
+
+ if (nifp->source)
+ nbmaifp = if_lookup_by_name(nifp->source, nifp->link_vrf_id);
+
+ switch (ifp->ll_type) {
+ case ZEBRA_LLT_IPGRE: {
+ struct in_addr saddr = {0};
+
+ if (!gre_info) {
+ nhrp_send_zebra_gre_request(ifp);
+ return;
+ }
+ nifp->i_grekey = gre_info->ikey;
+ nifp->o_grekey = gre_info->okey;
+ nifp->link_idx = gre_info->ifindex_link;
+ nifp->link_vrf_id = gre_info->vrfid_link;
+ saddr.s_addr = gre_info->vtep_ip.s_addr;
+
+ debugf(NHRP_DEBUG_IF, "%s: GRE: %x %x %x", ifp->name,
+ nifp->i_grekey, nifp->link_idx, saddr.s_addr);
+ if (saddr.s_addr)
+ sockunion_set(&nbma, AF_INET,
+ (uint8_t *)&saddr.s_addr,
+ sizeof(saddr.s_addr));
+ else if (!nbmaifp && nifp->link_idx != IFINDEX_INTERNAL)
+ nbmaifp =
+ if_lookup_by_index(nifp->link_idx,
+ nifp->link_vrf_id);
+ } break;
+ default:
+ break;
+ }
+
+ if (nbmaifp)
+ nbmanifp = nbmaifp->info;
+
+ if (nbmaifp != nifp->nbmaifp) {
+ if (nifp->nbmaifp) {
+ struct nhrp_interface *prev_nifp = nifp->nbmaifp->info;
+
+ notifier_del(&nifp->nbmanifp_notifier,
+ &prev_nifp->notifier_list);
+ }
+ nifp->nbmaifp = nbmaifp;
+ if (nbmaifp) {
+ notifier_add(&nifp->nbmanifp_notifier,
+ &nbmanifp->notifier_list,
+ nhrp_interface_interface_notifier);
+ debugf(NHRP_DEBUG_IF, "%s: bound to %s", ifp->name,
+ nbmaifp->name);
+ }
+ }
+
+ if (nbmaifp) {
+ if (sockunion_family(&nbma) == AF_UNSPEC)
+ nbma = nbmanifp->afi[AFI_IP].addr;
+ nhrp_interface_update_mtu(ifp, AFI_IP);
+ nhrp_interface_update_source(ifp);
+ }
+
+ if (!sockunion_same(&nbma, &nifp->nbma)) {
+ nifp->nbma = nbma;
+ nhrp_interface_update(nifp->ifp);
+ debugf(NHRP_DEBUG_IF, "%s: NBMA address changed", ifp->name);
+ notifier_call(&nifp->notifier_list,
+ NOTIFY_INTERFACE_NBMA_CHANGED);
+ }
+
+ nhrp_interface_update(ifp);
+}
+
+static void nhrp_interface_update_address(struct interface *ifp, afi_t afi,
+ int force)
+{
+ const int family = afi2family(afi);
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_afi_data *if_ad = &nifp->afi[afi];
+ struct nhrp_cache *nc;
+ struct connected *c, *best;
+ struct listnode *cnode;
+ union sockunion addr;
+ char buf[PREFIX_STRLEN];
+
+ /* Select new best match preferring primary address */
+ best = NULL;
+ for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, c)) {
+ if (PREFIX_FAMILY(c->address) != family)
+ continue;
+ if (best == NULL) {
+ best = c;
+ continue;
+ }
+ if ((best->flags & ZEBRA_IFA_SECONDARY)
+ && !(c->flags & ZEBRA_IFA_SECONDARY)) {
+ best = c;
+ continue;
+ }
+ if (!(best->flags & ZEBRA_IFA_SECONDARY)
+ && (c->flags & ZEBRA_IFA_SECONDARY))
+ continue;
+ if (best->address->prefixlen > c->address->prefixlen) {
+ best = c;
+ continue;
+ }
+ if (best->address->prefixlen < c->address->prefixlen)
+ continue;
+ }
+
+ /* On NHRP interfaces a host prefix is required */
+ if (best && if_ad->configured
+ && best->address->prefixlen != 8 * prefix_blen(best->address)) {
+ zlog_notice("%s: %pFX is not a host prefix", ifp->name,
+ best->address);
+ best = NULL;
+ }
+
+ /* Update address if it changed */
+ if (best)
+ prefix2sockunion(best->address, &addr);
+ else
+ memset(&addr, 0, sizeof(addr));
+
+ if (!force && sockunion_same(&if_ad->addr, &addr))
+ return;
+
+ if (sockunion_family(&if_ad->addr) != AF_UNSPEC) {
+ nc = nhrp_cache_get(ifp, &if_ad->addr, 0);
+ if (nc)
+ nhrp_cache_update_binding(nc, NHRP_CACHE_LOCAL, -1,
+ NULL, 0, NULL, NULL);
+ }
+
+ debugf(NHRP_DEBUG_KERNEL, "%s: IPv%d address changed to %s", ifp->name,
+ afi == AFI_IP ? 4 : 6,
+ best ? prefix2str(best->address, buf, sizeof(buf)) : "(none)");
+ if_ad->addr = addr;
+
+ if (if_ad->configured && sockunion_family(&if_ad->addr) != AF_UNSPEC) {
+ nc = nhrp_cache_get(ifp, &addr, 1);
+ if (nc)
+ nhrp_cache_update_binding(nc, NHRP_CACHE_LOCAL, 0, NULL,
+ 0, NULL, NULL);
+ }
+
+ notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_ADDRESS_CHANGED);
+}
+
+void nhrp_interface_update(struct interface *ifp)
+{
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_afi_data *if_ad;
+ afi_t afi;
+ int enabled = 0;
+
+ notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_CHANGED);
+
+ for (afi = 0; afi < AFI_MAX; afi++) {
+ if_ad = &nifp->afi[afi];
+
+ if (sockunion_family(&nifp->nbma) == AF_UNSPEC
+ || ifp->ifindex == IFINDEX_INTERNAL || !if_is_up(ifp)
+ || !if_ad->network_id) {
+ if (if_ad->configured) {
+ if_ad->configured = 0;
+ nhrp_interface_update_address(ifp, afi, 1);
+ }
+ continue;
+ }
+
+ if (!if_ad->configured) {
+ os_configure_dmvpn(ifp->ifindex, ifp->name,
+ afi2family(afi));
+ nhrp_send_zebra_configure_arp(ifp, afi2family(afi));
+ if_ad->configured = 1;
+ nhrp_interface_update_address(ifp, afi, 1);
+ }
+
+ enabled = 1;
+ }
+
+ if (enabled != nifp->enabled) {
+ nifp->enabled = enabled;
+ notifier_call(&nifp->notifier_list,
+ enabled ? NOTIFY_INTERFACE_UP
+ : NOTIFY_INTERFACE_DOWN);
+ }
+}
+
+int nhrp_ifp_create(struct interface *ifp)
+{
+ debugf(NHRP_DEBUG_IF, "if-add: %s, ifindex: %u, hw_type: %d %s",
+ ifp->name, ifp->ifindex, ifp->ll_type,
+ if_link_type_str(ifp->ll_type));
+
+ nhrp_interface_update_nbma(ifp, NULL);
+
+ return 0;
+}
+
+int nhrp_ifp_destroy(struct interface *ifp)
+{
+ debugf(NHRP_DEBUG_IF, "if-delete: %s", ifp->name);
+
+ nhrp_interface_update_cache_config(ifp, false, AF_INET);
+ nhrp_interface_update_cache_config(ifp, false, AF_INET6);
+ nhrp_interface_update(ifp);
+
+ return 0;
+}
+
+struct map_ctx {
+ int family;
+ bool enabled;
+};
+
+static void interface_config_update_nhrp_map(struct nhrp_cache_config *cc,
+ void *data)
+{
+ struct map_ctx *ctx = data;
+ struct interface *ifp = cc->ifp;
+ struct nhrp_cache *c;
+ union sockunion nbma_addr;
+
+ if (sockunion_family(&cc->remote_addr) != ctx->family)
+ return;
+
+ /* gre layer not ready */
+ if (ifp->vrf->vrf_id == VRF_UNKNOWN)
+ return;
+
+ c = nhrp_cache_get(ifp, &cc->remote_addr, ctx->enabled ? 1 : 0);
+ if (!c && !ctx->enabled)
+ return;
+
+ /* suppress */
+ if (!ctx->enabled) {
+ if (c && c->map) {
+ nhrp_cache_update_binding(
+ c, c->cur.type, -1,
+ nhrp_peer_get(ifp, &nbma_addr), 0, NULL, NULL);
+ }
+ return;
+ }
+
+ /* Newly created */
+ assert(c != NULL);
+
+ c->map = 1;
+ if (cc->type == NHRP_CACHE_LOCAL)
+ nhrp_cache_update_binding(c, NHRP_CACHE_LOCAL, 0, NULL, 0,
+ NULL, NULL);
+ else {
+ nhrp_cache_update_binding(c, NHRP_CACHE_STATIC, 0,
+ nhrp_peer_get(ifp, &cc->nbma), 0,
+ NULL, NULL);
+ }
+}
+
+static void nhrp_interface_update_cache_config(struct interface *ifp, bool available, uint8_t family)
+{
+ struct map_ctx mapctx;
+
+ mapctx = (struct map_ctx){
+ .family = family,
+ .enabled = available
+ };
+ nhrp_cache_config_foreach(ifp, interface_config_update_nhrp_map,
+ &mapctx);
+
+}
+
+int nhrp_ifp_up(struct interface *ifp)
+{
+ debugf(NHRP_DEBUG_IF, "if-up: %s", ifp->name);
+ nhrp_interface_update_nbma(ifp, NULL);
+
+ return 0;
+}
+
+int nhrp_ifp_down(struct interface *ifp)
+{
+ debugf(NHRP_DEBUG_IF, "if-down: %s", ifp->name);
+ nhrp_interface_update(ifp);
+
+ return 0;
+}
+
+int nhrp_interface_address_add(ZAPI_CALLBACK_ARGS)
+{
+ struct connected *ifc;
+
+ ifc = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id);
+ if (ifc == NULL)
+ return 0;
+
+ debugf(NHRP_DEBUG_IF, "if-addr-add: %s: %pFX", ifc->ifp->name,
+ ifc->address);
+
+ nhrp_interface_update_address(
+ ifc->ifp, family2afi(PREFIX_FAMILY(ifc->address)), 0);
+ nhrp_interface_update_cache_config(ifc->ifp, true, PREFIX_FAMILY(ifc->address));
+ return 0;
+}
+
+int nhrp_interface_address_delete(ZAPI_CALLBACK_ARGS)
+{
+ struct connected *ifc;
+
+ ifc = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id);
+ if (ifc == NULL)
+ return 0;
+
+ debugf(NHRP_DEBUG_IF, "if-addr-del: %s: %pFX", ifc->ifp->name,
+ ifc->address);
+
+ nhrp_interface_update_address(
+ ifc->ifp, family2afi(PREFIX_FAMILY(ifc->address)), 0);
+ connected_free(&ifc);
+
+ return 0;
+}
+
+void nhrp_interface_notify_add(struct interface *ifp, struct notifier_block *n,
+ notifier_fn_t fn)
+{
+ struct nhrp_interface *nifp = ifp->info;
+
+ notifier_add(n, &nifp->notifier_list, fn);
+}
+
+void nhrp_interface_notify_del(struct interface *ifp, struct notifier_block *n)
+{
+ struct nhrp_interface *nifp = ifp->info;
+
+ notifier_del(n, &nifp->notifier_list);
+}
+
+void nhrp_interface_set_protection(struct interface *ifp, const char *profile,
+ const char *fallback_profile)
+{
+ struct nhrp_interface *nifp = ifp->info;
+
+ if (nifp->ipsec_profile) {
+ vici_terminate_vc_by_profile_name(nifp->ipsec_profile);
+ nhrp_vc_reset();
+ free(nifp->ipsec_profile);
+ }
+ nifp->ipsec_profile = profile ? strdup(profile) : NULL;
+
+ if (nifp->ipsec_fallback_profile) {
+ vici_terminate_vc_by_profile_name(nifp->ipsec_fallback_profile);
+ nhrp_vc_reset();
+ free(nifp->ipsec_fallback_profile);
+ }
+ nifp->ipsec_fallback_profile =
+ fallback_profile ? strdup(fallback_profile) : NULL;
+
+ notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_IPSEC_CHANGED);
+}
+
+void nhrp_interface_set_source(struct interface *ifp, const char *ifname)
+{
+ struct nhrp_interface *nifp = ifp->info;
+
+ if (nifp->source)
+ free(nifp->source);
+ nifp->source = ifname ? strdup(ifname) : NULL;
+
+ nhrp_interface_update_nbma(ifp, NULL);
+}
diff --git a/nhrpd/nhrp_main.c b/nhrpd/nhrp_main.c
new file mode 100644
index 0000000..bece89c
--- /dev/null
+++ b/nhrpd/nhrp_main.c
@@ -0,0 +1,172 @@
+/* NHRP daemon main functions
+ * Copyright (c) 2014-2015 Timo Teräs
+ *
+ * This file is free software: you may copy, redistribute and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <unistd.h>
+
+#include "zebra.h"
+#include "privs.h"
+#include "getopt.h"
+#include "thread.h"
+#include "sigevent.h"
+#include "lib/version.h"
+#include "log.h"
+#include "memory.h"
+#include "command.h"
+#include "libfrr.h"
+#include "filter.h"
+
+#include "nhrpd.h"
+#include "nhrp_errors.h"
+
+DEFINE_MGROUP(NHRPD, "NHRP");
+
+unsigned int debug_flags = 0;
+
+struct thread_master *master;
+struct timeval current_time;
+
+/* nhrpd options. */
+struct option longopts[] = {{0}};
+
+/* nhrpd privileges */
+static zebra_capabilities_t _caps_p[] = {
+ ZCAP_NET_RAW, ZCAP_NET_ADMIN,
+ ZCAP_DAC_OVERRIDE, /* for now needed to write to
+ /proc/sys/net/ipv4/<if>/send_redirect */
+};
+
+struct zebra_privs_t nhrpd_privs = {
+#if defined(FRR_USER) && defined(FRR_GROUP)
+ .user = FRR_USER,
+ .group = FRR_GROUP,
+#endif
+#ifdef VTY_GROUP
+ .vty_group = VTY_GROUP,
+#endif
+ .caps_p = _caps_p,
+ .cap_num_p = array_size(_caps_p),
+ .cap_num_i = 0
+};
+
+
+static void parse_arguments(int argc, char **argv)
+{
+ int opt;
+
+ while (1) {
+ opt = frr_getopt(argc, argv, 0);
+ if (opt < 0)
+ break;
+
+ switch (opt) {
+ case 0:
+ break;
+ default:
+ frr_help_exit(1);
+ }
+ }
+}
+
+static void nhrp_sigusr1(void)
+{
+ zlog_rotate();
+}
+
+static void nhrp_request_stop(void)
+{
+ debugf(NHRP_DEBUG_COMMON, "Exiting...");
+ frr_early_fini();
+
+ nhrp_shortcut_terminate();
+ nhrp_nhs_terminate();
+ nhrp_zebra_terminate();
+ vici_terminate();
+ evmgr_terminate();
+ nhrp_vc_terminate();
+ vrf_terminate();
+
+ debugf(NHRP_DEBUG_COMMON, "Done.");
+ frr_fini();
+
+ exit(0);
+}
+
+static struct frr_signal_t sighandlers[] = {
+ {
+ .signal = SIGUSR1,
+ .handler = &nhrp_sigusr1,
+ },
+ {
+ .signal = SIGINT,
+ .handler = &nhrp_request_stop,
+ },
+ {
+ .signal = SIGTERM,
+ .handler = &nhrp_request_stop,
+ },
+};
+
+static const struct frr_yang_module_info *const nhrpd_yang_modules[] = {
+ &frr_filter_info,
+ &frr_interface_info,
+ &frr_vrf_info,
+};
+
+FRR_DAEMON_INFO(nhrpd, NHRP, .vty_port = NHRP_VTY_PORT,
+
+ .proghelp = "Implementation of the NHRP routing protocol.",
+
+ .signals = sighandlers, .n_signals = array_size(sighandlers),
+
+ .privs = &nhrpd_privs, .yang_modules = nhrpd_yang_modules,
+ .n_yang_modules = array_size(nhrpd_yang_modules),
+);
+
+int main(int argc, char **argv)
+{
+ frr_preinit(&nhrpd_di, argc, argv);
+ frr_opt_add("", longopts, "");
+
+ parse_arguments(argc, argv);
+
+ /* Library inits. */
+ master = frr_init();
+ nhrp_error_init();
+ vrf_init(NULL, NULL, NULL, NULL);
+ nhrp_interface_init();
+ resolver_init(master);
+
+ /*
+ * Run with elevated capabilities, as for all netlink activity
+ * we need privileges anyway.
+ * The assert is for clang SA code where it does
+ * not see the change function being set in lib
+ */
+ assert(nhrpd_privs.change);
+ nhrpd_privs.change(ZPRIVS_RAISE);
+
+ evmgr_init();
+ nhrp_vc_init();
+ nhrp_packet_init();
+ vici_init();
+ if_zapi_callbacks(nhrp_ifp_create, nhrp_ifp_up,
+ nhrp_ifp_down, nhrp_ifp_destroy);
+ nhrp_zebra_init();
+ nhrp_shortcut_init();
+
+ nhrp_config_init();
+
+ frr_config_fork();
+ frr_run(master);
+ return 0;
+}
diff --git a/nhrpd/nhrp_multicast.c b/nhrpd/nhrp_multicast.c
new file mode 100644
index 0000000..89be13b
--- /dev/null
+++ b/nhrpd/nhrp_multicast.c
@@ -0,0 +1,298 @@
+/* NHRP Multicast Support
+ * Copyright (c) 2020-2021 4RF Limited
+ *
+ * This file is free software: you may copy, redistribute and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <fcntl.h>
+#include <net/if.h>
+#include <net/ethernet.h>
+#include <netinet/if_ether.h>
+#include <linux/netlink.h>
+#include <linux/neighbour.h>
+#include <linux/netfilter/nfnetlink_log.h>
+#include <linux/if_packet.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include "thread.h"
+#include "nhrpd.h"
+#include "netlink.h"
+#include "znl.h"
+#include "os.h"
+
+DEFINE_MTYPE_STATIC(NHRPD, NHRP_MULTICAST, "NHRP Multicast");
+
+int netlink_mcast_nflog_group;
+static int netlink_mcast_log_fd = -1;
+static struct thread *netlink_mcast_log_thread;
+
+struct mcast_ctx {
+ struct interface *ifp;
+ struct zbuf *pkt;
+};
+
+static void nhrp_multicast_send(struct nhrp_peer *p, struct zbuf *zb)
+{
+ size_t addrlen;
+ int ret;
+
+ addrlen = sockunion_get_addrlen(&p->vc->remote.nbma);
+ ret = os_sendmsg(zb->head, zbuf_used(zb), p->ifp->ifindex,
+ sockunion_get_addr(&p->vc->remote.nbma), addrlen,
+ addrlen == 4 ? ETH_P_IP : ETH_P_IPV6);
+
+ debugf(NHRP_DEBUG_COMMON,
+ "Multicast Packet: %pSU -> %pSU, ret = %d, size = %zu, addrlen = %zu",
+ &p->vc->local.nbma, &p->vc->remote.nbma, ret, zbuf_used(zb),
+ addrlen);
+}
+
+static void nhrp_multicast_forward_nbma(union sockunion *nbma_addr,
+ struct interface *ifp, struct zbuf *pkt)
+{
+ struct nhrp_peer *p = nhrp_peer_get(ifp, nbma_addr);
+
+ if (p && p->online) {
+ /* Send packet */
+ nhrp_multicast_send(p, pkt);
+ }
+ nhrp_peer_unref(p);
+}
+
+static void nhrp_multicast_forward_cache(struct nhrp_cache *c, void *pctx)
+{
+ struct mcast_ctx *ctx = (struct mcast_ctx *)pctx;
+
+ if (c->cur.type == NHRP_CACHE_DYNAMIC && c->cur.peer)
+ nhrp_multicast_forward_nbma(&c->cur.peer->vc->remote.nbma,
+ ctx->ifp, ctx->pkt);
+}
+
+static void nhrp_multicast_forward(struct nhrp_multicast *mcast, void *pctx)
+{
+ struct mcast_ctx *ctx = (struct mcast_ctx *)pctx;
+ struct nhrp_interface *nifp = ctx->ifp->info;
+
+ if (!nifp->enabled)
+ return;
+
+ /* dynamic */
+ if (sockunion_family(&mcast->nbma_addr) == AF_UNSPEC) {
+ nhrp_cache_foreach(ctx->ifp, nhrp_multicast_forward_cache,
+ pctx);
+ return;
+ }
+
+ /* Fixed IP Address */
+ nhrp_multicast_forward_nbma(&mcast->nbma_addr, ctx->ifp, ctx->pkt);
+}
+
+static void netlink_mcast_log_handler(struct nlmsghdr *msg, struct zbuf *zb)
+{
+ struct nfgenmsg *nf;
+ struct rtattr *rta;
+ struct zbuf rtapl;
+ uint32_t *out_ndx = NULL;
+ afi_t afi;
+ struct mcast_ctx ctx;
+
+ nf = znl_pull(zb, sizeof(*nf));
+ if (!nf)
+ return;
+
+ ctx.pkt = NULL;
+ while ((rta = znl_rta_pull(zb, &rtapl)) != NULL) {
+ switch (rta->rta_type) {
+ case NFULA_IFINDEX_OUTDEV:
+ out_ndx = znl_pull(&rtapl, sizeof(*out_ndx));
+ break;
+ case NFULA_PAYLOAD:
+ ctx.pkt = &rtapl;
+ break;
+ /* NFULA_HWHDR exists and is supposed to contain source
+ * hardware address. However, for ip_gre it seems to be
+ * the nexthop destination address if the packet matches
+ * route.
+ */
+ }
+ }
+
+ if (!out_ndx || !ctx.pkt)
+ return;
+
+ ctx.ifp = if_lookup_by_index(htonl(*out_ndx), VRF_DEFAULT);
+ if (!ctx.ifp)
+ return;
+
+ debugf(NHRP_DEBUG_COMMON,
+ "Intercepted multicast packet leaving %s len %zu",
+ ctx.ifp->name, zbuf_used(ctx.pkt));
+
+ for (afi = 0; afi < AFI_MAX; afi++) {
+ nhrp_multicast_foreach(ctx.ifp, afi, nhrp_multicast_forward,
+ (void *)&ctx);
+ }
+}
+
+static void netlink_mcast_log_recv(struct thread *t)
+{
+ uint8_t buf[65535]; /* Max OSPF Packet size */
+ int fd = THREAD_FD(t);
+ struct zbuf payload, zb;
+ struct nlmsghdr *n;
+
+
+ zbuf_init(&zb, buf, sizeof(buf), 0);
+ while (zbuf_recv(&zb, fd) > 0) {
+ while ((n = znl_nlmsg_pull(&zb, &payload)) != NULL) {
+ debugf(NHRP_DEBUG_COMMON,
+ "Netlink-mcast-log: Received msg_type %u, msg_flags %u",
+ n->nlmsg_type, n->nlmsg_flags);
+ switch (n->nlmsg_type) {
+ case (NFNL_SUBSYS_ULOG << 8) | NFULNL_MSG_PACKET:
+ netlink_mcast_log_handler(n, &payload);
+ break;
+ }
+ }
+ }
+
+ thread_add_read(master, netlink_mcast_log_recv, 0, netlink_mcast_log_fd,
+ &netlink_mcast_log_thread);
+}
+
+static void netlink_mcast_log_register(int fd, int group)
+{
+ struct nlmsghdr *n;
+ struct nfgenmsg *nf;
+ struct nfulnl_msg_config_cmd cmd;
+ struct zbuf *zb = zbuf_alloc(512);
+
+ n = znl_nlmsg_push(zb, (NFNL_SUBSYS_ULOG << 8) | NFULNL_MSG_CONFIG,
+ NLM_F_REQUEST | NLM_F_ACK);
+ nf = znl_push(zb, sizeof(*nf));
+ *nf = (struct nfgenmsg){
+ .nfgen_family = AF_UNSPEC,
+ .version = NFNETLINK_V0,
+ .res_id = htons(group),
+ };
+ cmd.command = NFULNL_CFG_CMD_BIND;
+ znl_rta_push(zb, NFULA_CFG_CMD, &cmd, sizeof(cmd));
+ znl_nlmsg_complete(zb, n);
+
+ zbuf_send(zb, fd);
+ zbuf_free(zb);
+}
+
+void netlink_mcast_set_nflog_group(int nlgroup)
+{
+ if (netlink_mcast_log_fd >= 0) {
+ THREAD_OFF(netlink_mcast_log_thread);
+ close(netlink_mcast_log_fd);
+ netlink_mcast_log_fd = -1;
+ debugf(NHRP_DEBUG_COMMON, "De-register nflog group");
+ }
+ netlink_mcast_nflog_group = nlgroup;
+ if (nlgroup) {
+ netlink_mcast_log_fd = znl_open(NETLINK_NETFILTER, 0);
+ if (netlink_mcast_log_fd < 0)
+ return;
+
+ netlink_mcast_log_register(netlink_mcast_log_fd, nlgroup);
+ thread_add_read(master, netlink_mcast_log_recv, 0,
+ netlink_mcast_log_fd,
+ &netlink_mcast_log_thread);
+ debugf(NHRP_DEBUG_COMMON, "Register nflog group: %d",
+ netlink_mcast_nflog_group);
+ }
+}
+
+static int nhrp_multicast_free(struct interface *ifp,
+ struct nhrp_multicast *mcast)
+{
+ struct nhrp_interface *nifp = ifp->info;
+
+ nhrp_mcastlist_del(&nifp->afi[mcast->afi].mcastlist_head, mcast);
+ XFREE(MTYPE_NHRP_MULTICAST, mcast);
+ return 0;
+}
+
+int nhrp_multicast_add(struct interface *ifp, afi_t afi,
+ union sockunion *nbma_addr)
+{
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_multicast *mcast;
+
+ frr_each (nhrp_mcastlist, &nifp->afi[afi].mcastlist_head, mcast) {
+ if (sockunion_same(&mcast->nbma_addr, nbma_addr))
+ return NHRP_ERR_ENTRY_EXISTS;
+ }
+
+ mcast = XMALLOC(MTYPE_NHRP_MULTICAST, sizeof(struct nhrp_multicast));
+
+ *mcast = (struct nhrp_multicast){
+ .afi = afi, .ifp = ifp, .nbma_addr = *nbma_addr,
+ };
+ nhrp_mcastlist_add_tail(&nifp->afi[afi].mcastlist_head, mcast);
+
+ debugf(NHRP_DEBUG_COMMON, "Adding multicast entry (%pSU)", nbma_addr);
+
+ return NHRP_OK;
+}
+
+int nhrp_multicast_del(struct interface *ifp, afi_t afi,
+ union sockunion *nbma_addr)
+{
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_multicast *mcast;
+
+ frr_each_safe (nhrp_mcastlist, &nifp->afi[afi].mcastlist_head, mcast) {
+ if (!sockunion_same(&mcast->nbma_addr, nbma_addr))
+ continue;
+
+ debugf(NHRP_DEBUG_COMMON, "Deleting multicast entry (%pSU)",
+ nbma_addr);
+
+ nhrp_multicast_free(ifp, mcast);
+
+ return NHRP_OK;
+ }
+
+ return NHRP_ERR_ENTRY_NOT_FOUND;
+}
+
+void nhrp_multicast_interface_del(struct interface *ifp)
+{
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_multicast *mcast;
+ afi_t afi;
+
+ for (afi = 0; afi < AFI_MAX; afi++) {
+ debugf(NHRP_DEBUG_COMMON, "Cleaning up multicast entries (%zu)",
+ nhrp_mcastlist_count(&nifp->afi[afi].mcastlist_head));
+
+ frr_each_safe (nhrp_mcastlist, &nifp->afi[afi].mcastlist_head,
+ mcast) {
+ nhrp_multicast_free(ifp, mcast);
+ }
+ }
+}
+
+void nhrp_multicast_foreach(struct interface *ifp, afi_t afi,
+ void (*cb)(struct nhrp_multicast *, void *),
+ void *ctx)
+{
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_multicast *mcast;
+
+ frr_each (nhrp_mcastlist, &nifp->afi[afi].mcastlist_head, mcast) {
+ cb(mcast, ctx);
+ }
+}
diff --git a/nhrpd/nhrp_nhs.c b/nhrpd/nhrp_nhs.c
new file mode 100644
index 0000000..03b4b53
--- /dev/null
+++ b/nhrpd/nhrp_nhs.c
@@ -0,0 +1,467 @@
+/* NHRP NHC nexthop server functions (registration)
+ * Copyright (c) 2014-2015 Timo Teräs
+ *
+ * This file is free software: you may copy, redistribute and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include "zebra.h"
+#include "zbuf.h"
+#include "memory.h"
+#include "thread.h"
+#include "nhrpd.h"
+#include "nhrp_protocol.h"
+
+DEFINE_MTYPE_STATIC(NHRPD, NHRP_NHS, "NHRP next hop server");
+DEFINE_MTYPE_STATIC(NHRPD, NHRP_REGISTRATION, "NHRP registration entries");
+
+static void nhrp_nhs_resolve(struct thread *t);
+static void nhrp_reg_send_req(struct thread *t);
+
+static void nhrp_reg_reply(struct nhrp_reqid *reqid, void *arg)
+{
+ struct nhrp_packet_parser *p = arg;
+ struct nhrp_registration *r =
+ container_of(reqid, struct nhrp_registration, reqid);
+ struct nhrp_nhs *nhs = r->nhs;
+ struct interface *ifp = nhs->ifp;
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_extension_header *ext;
+ struct nhrp_cie_header *cie;
+ struct nhrp_cache *c;
+ struct zbuf extpl;
+ union sockunion cie_nbma, cie_nbma_nhs, cie_proto, cie_proto_nhs,
+ *proto;
+ int ok = 0, holdtime;
+ unsigned short mtu = 0;
+
+ nhrp_reqid_free(&nhrp_packet_reqid, &r->reqid);
+
+ if (p->hdr->type != NHRP_PACKET_REGISTRATION_REPLY) {
+ debugf(NHRP_DEBUG_COMMON, "NHS: Registration failed");
+ return;
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "NHS: Reg.reply received");
+
+ ok = 1;
+ while ((cie = nhrp_cie_pull(&p->payload, p->hdr, &cie_nbma, &cie_proto))
+ != NULL) {
+ proto = sockunion_family(&cie_proto) != AF_UNSPEC
+ ? &cie_proto
+ : &p->src_proto;
+ debugf(NHRP_DEBUG_COMMON, "NHS: CIE registration: %pSU: %d",
+ proto, cie->code);
+ if (!((cie->code == NHRP_CODE_SUCCESS)
+ || (cie->code == NHRP_CODE_ADMINISTRATIVELY_PROHIBITED
+ && nhs->hub)))
+ ok = 0;
+ mtu = ntohs(cie->mtu);
+ debugf(NHRP_DEBUG_COMMON, "NHS: CIE MTU: %d", mtu);
+ }
+
+ if (!ok)
+ return;
+
+ /* Parse extensions */
+ sockunion_family(&nifp->nat_nbma) = AF_UNSPEC;
+ sockunion_family(&cie_nbma_nhs) = AF_UNSPEC;
+ while ((ext = nhrp_ext_pull(&p->extensions, &extpl)) != NULL) {
+ switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) {
+ case NHRP_EXTENSION_NAT_ADDRESS:
+ /* NHS adds second CIE if NAT is detected */
+ if (nhrp_cie_pull(&extpl, p->hdr, &cie_nbma, &cie_proto)
+ && nhrp_cie_pull(&extpl, p->hdr, &cie_nbma,
+ &cie_proto)) {
+ nifp->nat_nbma = cie_nbma;
+ debugf(NHRP_DEBUG_IF,
+ "%s: NAT detected, real NBMA address: %pSU",
+ ifp->name, &nifp->nbma);
+ }
+ break;
+ case NHRP_EXTENSION_RESPONDER_ADDRESS:
+ /* NHS adds its own record as responder address */
+ nhrp_cie_pull(&extpl, p->hdr, &cie_nbma_nhs,
+ &cie_proto_nhs);
+ break;
+ }
+ }
+
+ /* Success - schedule next registration, and route NHS */
+ r->timeout = 2;
+ holdtime = nifp->afi[nhs->afi].holdtime;
+ THREAD_OFF(r->t_register);
+
+ /* RFC 2332 5.2.3 - Registration is recommend to be renewed
+ * every one third of holdtime */
+ thread_add_timer(master, nhrp_reg_send_req, r, holdtime / 3,
+ &r->t_register);
+
+ r->proto_addr = p->dst_proto;
+ c = nhrp_cache_get(ifp, &p->dst_proto, 1);
+ if (c)
+ nhrp_cache_update_binding(c, NHRP_CACHE_NHS, holdtime,
+ nhrp_peer_ref(r->peer), mtu, NULL,
+ &cie_nbma_nhs);
+}
+
+static void nhrp_reg_timeout(struct thread *t)
+{
+ struct nhrp_registration *r = THREAD_ARG(t);
+ struct nhrp_cache *c;
+
+
+ if (r->timeout >= 16 && sockunion_family(&r->proto_addr) != AF_UNSPEC) {
+ nhrp_reqid_free(&nhrp_packet_reqid, &r->reqid);
+ c = nhrp_cache_get(r->nhs->ifp, &r->proto_addr, 0);
+ if (c)
+ nhrp_cache_update_binding(c, NHRP_CACHE_NHS, -1, NULL,
+ 0, NULL, NULL);
+ sockunion_family(&r->proto_addr) = AF_UNSPEC;
+ }
+
+ r->timeout <<= 1;
+ if (r->timeout > 64) {
+ /* If registration fails repeatedly, this may be because the
+ * IPSec connection is not working. Close the connection so it
+ * can be re-established correctly
+ */
+ if (r->peer && r->peer->vc && r->peer->vc->ike_uniqueid) {
+ debugf(NHRP_DEBUG_COMMON,
+ "Terminating IPSec Connection for %d",
+ r->peer->vc->ike_uniqueid);
+ vici_terminate_vc_by_ike_id(r->peer->vc->ike_uniqueid);
+ r->peer->vc->ike_uniqueid = 0;
+ }
+ r->timeout = 2;
+ }
+ thread_add_timer_msec(master, nhrp_reg_send_req, r, 10, &r->t_register);
+}
+
+static void nhrp_reg_peer_notify(struct notifier_block *n, unsigned long cmd)
+{
+ struct nhrp_registration *r =
+ container_of(n, struct nhrp_registration, peer_notifier);
+
+ switch (cmd) {
+ case NOTIFY_PEER_UP:
+ case NOTIFY_PEER_DOWN:
+ case NOTIFY_PEER_IFCONFIG_CHANGED:
+ case NOTIFY_PEER_MTU_CHANGED:
+ debugf(NHRP_DEBUG_COMMON, "NHS: Flush timer for %pSU",
+ &r->peer->vc->remote.nbma);
+ THREAD_OFF(r->t_register);
+ thread_add_timer_msec(master, nhrp_reg_send_req, r, 10,
+ &r->t_register);
+ break;
+ }
+}
+
+static void nhrp_reg_send_req(struct thread *t)
+{
+ struct nhrp_registration *r = THREAD_ARG(t);
+ struct nhrp_nhs *nhs = r->nhs;
+ struct interface *ifp = nhs->ifp;
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_afi_data *if_ad = &nifp->afi[nhs->afi];
+ union sockunion *dst_proto, nhs_proto;
+ struct zbuf *zb;
+ struct nhrp_packet_header *hdr;
+ struct nhrp_extension_header *ext;
+ struct nhrp_cie_header *cie;
+
+ if (!nhrp_peer_check(r->peer, 2)) {
+ debugf(NHRP_DEBUG_COMMON, "NHS: Waiting link for %pSU",
+ &r->peer->vc->remote.nbma);
+ thread_add_timer(master, nhrp_reg_send_req, r, 120,
+ &r->t_register);
+ return;
+ }
+
+ thread_add_timer(master, nhrp_reg_timeout, r, r->timeout,
+ &r->t_register);
+
+ /* RFC2332 5.2.3 NHC uses it's own address as dst if NHS is unknown */
+ dst_proto = &nhs->proto_addr;
+ if (sockunion_family(dst_proto) == AF_UNSPEC)
+ dst_proto = &if_ad->addr;
+
+ debugf(NHRP_DEBUG_COMMON, "NHS: Register %pSU -> %pSU (timeout %d)",
+ &if_ad->addr, dst_proto, r->timeout);
+
+ /* No protocol address configured for tunnel interface */
+ if (sockunion_family(&if_ad->addr) == AF_UNSPEC)
+ return;
+
+ zb = zbuf_alloc(1400);
+ hdr = nhrp_packet_push(zb, NHRP_PACKET_REGISTRATION_REQUEST,
+ &nifp->nbma, &if_ad->addr, dst_proto);
+ hdr->hop_count = 1;
+ if (!(if_ad->flags & NHRP_IFF_REG_NO_UNIQUE))
+ hdr->flags |= htons(NHRP_FLAG_REGISTRATION_UNIQUE);
+
+ hdr->u.request_id = htonl(nhrp_reqid_alloc(&nhrp_packet_reqid,
+ &r->reqid, nhrp_reg_reply));
+
+ /* FIXME: push CIE for each local protocol address */
+ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, NULL, NULL);
+ /* RFC2332 5.2.1 if unique is set then prefix length must be 0xff */
+ cie->prefix_length = (if_ad->flags & NHRP_IFF_REG_NO_UNIQUE)
+ ? 8 * sockunion_get_addrlen(dst_proto)
+ : 0xff;
+ cie->holding_time = htons(if_ad->holdtime);
+ cie->mtu = htons(if_ad->mtu);
+
+ nhrp_ext_request(zb, hdr, ifp);
+
+ /* Cisco NAT detection extension */
+ if (sockunion_family(&r->proto_addr) != AF_UNSPEC) {
+ nhs_proto = r->proto_addr;
+ } else if (sockunion_family(&nhs->proto_addr) != AF_UNSPEC) {
+ nhs_proto = nhs->proto_addr;
+ } else {
+ /* cisco magic: If NHS is not known then use all 0s as
+ * client protocol address in NAT Extension header
+ */
+ memset(&nhs_proto, 0, sizeof(nhs_proto));
+ sockunion_family(&nhs_proto) = afi2family(nhs->afi);
+ }
+
+ hdr->flags |= htons(NHRP_FLAG_REGISTRATION_NAT);
+ ext = nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS);
+ /* push NHS details */
+ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &r->peer->vc->remote.nbma,
+ &nhs_proto);
+ cie->prefix_length = 8 * sockunion_get_addrlen(&if_ad->addr);
+ cie->mtu = htons(if_ad->mtu);
+ nhrp_ext_complete(zb, ext);
+
+ nhrp_packet_complete(zb, hdr);
+ nhrp_peer_send(r->peer, zb);
+ zbuf_free(zb);
+}
+
+static void nhrp_reg_delete(struct nhrp_registration *r)
+{
+ nhrp_peer_notify_del(r->peer, &r->peer_notifier);
+ nhrp_peer_unref(r->peer);
+ nhrp_reglist_del(&r->nhs->reglist_head, r);
+ THREAD_OFF(r->t_register);
+ XFREE(MTYPE_NHRP_REGISTRATION, r);
+}
+
+static struct nhrp_registration *
+nhrp_reg_by_nbma(struct nhrp_nhs *nhs, const union sockunion *nbma_addr)
+{
+ struct nhrp_registration *r;
+
+ frr_each (nhrp_reglist, &nhs->reglist_head, r)
+ if (sockunion_same(&r->peer->vc->remote.nbma, nbma_addr))
+ return r;
+ return NULL;
+}
+
+static void nhrp_nhs_resolve_cb(struct resolver_query *q, const char *errstr,
+ int n, union sockunion *addrs)
+{
+ struct nhrp_nhs *nhs = container_of(q, struct nhrp_nhs, dns_resolve);
+ struct nhrp_interface *nifp = nhs->ifp->info;
+ struct nhrp_registration *reg;
+ int i;
+
+ if (n < 0) {
+ /* Failed, retry in a moment */
+ thread_add_timer(master, nhrp_nhs_resolve, nhs, 5,
+ &nhs->t_resolve);
+ return;
+ }
+
+ thread_add_timer(master, nhrp_nhs_resolve, nhs, 2 * 60 * 60,
+ &nhs->t_resolve);
+
+ frr_each (nhrp_reglist, &nhs->reglist_head, reg)
+ reg->mark = 1;
+
+ nhs->hub = 0;
+ for (i = 0; i < n; i++) {
+ if (sockunion_same(&addrs[i], &nifp->nbma)) {
+ nhs->hub = 1;
+ continue;
+ }
+
+ reg = nhrp_reg_by_nbma(nhs, &addrs[i]);
+ if (reg) {
+ reg->mark = 0;
+ continue;
+ }
+
+ reg = XCALLOC(MTYPE_NHRP_REGISTRATION, sizeof(*reg));
+ reg->peer = nhrp_peer_get(nhs->ifp, &addrs[i]);
+ reg->nhs = nhs;
+ reg->timeout = 1;
+ nhrp_reglist_add_tail(&nhs->reglist_head, reg);
+ nhrp_peer_notify_add(reg->peer, &reg->peer_notifier,
+ nhrp_reg_peer_notify);
+ thread_add_timer_msec(master, nhrp_reg_send_req, reg, 50,
+ &reg->t_register);
+ }
+
+ frr_each_safe (nhrp_reglist, &nhs->reglist_head, reg)
+ if (reg->mark)
+ nhrp_reg_delete(reg);
+}
+
+static void nhrp_nhs_resolve(struct thread *t)
+{
+ struct nhrp_nhs *nhs = THREAD_ARG(t);
+
+ resolver_resolve(&nhs->dns_resolve, AF_INET, VRF_DEFAULT,
+ nhs->nbma_fqdn, nhrp_nhs_resolve_cb);
+}
+
+int nhrp_nhs_add(struct interface *ifp, afi_t afi, union sockunion *proto_addr,
+ const char *nbma_fqdn)
+{
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_nhs *nhs;
+
+ if (sockunion_family(proto_addr) != AF_UNSPEC
+ && sockunion_family(proto_addr) != afi2family(afi))
+ return NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH;
+
+ frr_each (nhrp_nhslist, &nifp->afi[afi].nhslist_head, nhs) {
+ if (sockunion_family(&nhs->proto_addr) != AF_UNSPEC
+ && sockunion_family(proto_addr) != AF_UNSPEC
+ && sockunion_same(&nhs->proto_addr, proto_addr))
+ return NHRP_ERR_ENTRY_EXISTS;
+
+ if (strcmp(nhs->nbma_fqdn, nbma_fqdn) == 0)
+ return NHRP_ERR_ENTRY_EXISTS;
+ }
+
+ nhs = XMALLOC(MTYPE_NHRP_NHS, sizeof(struct nhrp_nhs));
+
+ *nhs = (struct nhrp_nhs){
+ .afi = afi,
+ .ifp = ifp,
+ .proto_addr = *proto_addr,
+ .nbma_fqdn = strdup(nbma_fqdn),
+ .reglist_head = INIT_DLIST(nhs->reglist_head),
+ };
+ nhrp_nhslist_add_tail(&nifp->afi[afi].nhslist_head, nhs);
+ thread_add_timer_msec(master, nhrp_nhs_resolve, nhs, 1000,
+ &nhs->t_resolve);
+
+ return NHRP_OK;
+}
+
+int nhrp_nhs_del(struct interface *ifp, afi_t afi, union sockunion *proto_addr,
+ const char *nbma_fqdn)
+{
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_nhs *nhs;
+ int ret = NHRP_ERR_ENTRY_NOT_FOUND;
+
+ if (sockunion_family(proto_addr) != AF_UNSPEC
+ && sockunion_family(proto_addr) != afi2family(afi))
+ return NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH;
+
+ frr_each_safe (nhrp_nhslist, &nifp->afi[afi].nhslist_head, nhs) {
+ if (!sockunion_same(&nhs->proto_addr, proto_addr))
+ continue;
+ if (strcmp(nhs->nbma_fqdn, nbma_fqdn) != 0)
+ continue;
+
+ nhrp_nhs_free(nifp, afi, nhs);
+ ret = NHRP_OK;
+ }
+
+ return ret;
+}
+
+int nhrp_nhs_free(struct nhrp_interface *nifp, afi_t afi, struct nhrp_nhs *nhs)
+{
+ struct nhrp_registration *r;
+
+ frr_each_safe (nhrp_reglist, &nhs->reglist_head, r)
+ nhrp_reg_delete(r);
+ THREAD_OFF(nhs->t_resolve);
+ nhrp_nhslist_del(&nifp->afi[afi].nhslist_head, nhs);
+ free((void *)nhs->nbma_fqdn);
+ XFREE(MTYPE_NHRP_NHS, nhs);
+ return 0;
+}
+
+void nhrp_nhs_interface_del(struct interface *ifp)
+{
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_nhs *nhs;
+ afi_t afi;
+
+ for (afi = 0; afi < AFI_MAX; afi++) {
+ debugf(NHRP_DEBUG_COMMON, "Cleaning up nhs entries (%zu)",
+ nhrp_nhslist_count(&nifp->afi[afi].nhslist_head));
+
+ frr_each_safe (nhrp_nhslist, &nifp->afi[afi].nhslist_head, nhs)
+ nhrp_nhs_free(nifp, afi, nhs);
+ }
+}
+
+void nhrp_nhs_terminate(void)
+{
+ struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT);
+ struct interface *ifp;
+ struct nhrp_interface *nifp;
+ struct nhrp_nhs *nhs;
+ afi_t afi;
+
+ FOR_ALL_INTERFACES (vrf, ifp) {
+ nifp = ifp->info;
+ for (afi = 0; afi < AFI_MAX; afi++) {
+ frr_each_safe (nhrp_nhslist,
+ &nifp->afi[afi].nhslist_head, nhs)
+ nhrp_nhs_free(nifp, afi, nhs);
+ }
+ }
+}
+
+void nhrp_nhs_foreach(struct interface *ifp, afi_t afi,
+ void (*cb)(struct nhrp_nhs *, struct nhrp_registration *,
+ void *),
+ void *ctx)
+{
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_nhs *nhs;
+ struct nhrp_registration *reg;
+
+ frr_each (nhrp_nhslist, &nifp->afi[afi].nhslist_head, nhs) {
+ if (nhrp_reglist_count(&nhs->reglist_head)) {
+ frr_each (nhrp_reglist, &nhs->reglist_head, reg)
+ cb(nhs, reg, ctx);
+ } else
+ cb(nhs, 0, ctx);
+ }
+}
+
+int nhrp_nhs_match_ip(union sockunion *in_ip, struct nhrp_interface *nifp)
+{
+ int i;
+ struct nhrp_nhs *nhs;
+ struct nhrp_registration *reg;
+
+ for (i = 0; i < AFI_MAX; i++) {
+ frr_each (nhrp_nhslist, &nifp->afi[i].nhslist_head, nhs) {
+ if (!nhrp_reglist_count(&nhs->reglist_head))
+ continue;
+
+ frr_each (nhrp_reglist, &nhs->reglist_head, reg) {
+ if (!sockunion_cmp(in_ip,
+ &reg->peer->vc->remote.nbma))
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
diff --git a/nhrpd/nhrp_packet.c b/nhrpd/nhrp_packet.c
new file mode 100644
index 0000000..2407a8d
--- /dev/null
+++ b/nhrpd/nhrp_packet.c
@@ -0,0 +1,345 @@
+/* NHRP packet handling functions
+ * Copyright (c) 2014-2015 Timo Teräs
+ *
+ * This file is free software: you may copy, redistribute and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <netinet/if_ether.h>
+#include "nhrpd.h"
+#include "zbuf.h"
+#include "thread.h"
+#include "hash.h"
+
+#include "nhrp_protocol.h"
+#include "os.h"
+
+struct nhrp_reqid_pool nhrp_packet_reqid;
+
+static uint16_t family2proto(int family)
+{
+ switch (family) {
+ case AF_INET:
+ return ETH_P_IP;
+ case AF_INET6:
+ return ETH_P_IPV6;
+ }
+ return 0;
+}
+
+static int proto2family(uint16_t proto)
+{
+ switch (proto) {
+ case ETH_P_IP:
+ return AF_INET;
+ case ETH_P_IPV6:
+ return AF_INET6;
+ }
+ return AF_UNSPEC;
+}
+
+struct nhrp_packet_header *nhrp_packet_push(struct zbuf *zb, uint8_t type,
+ const union sockunion *src_nbma,
+ const union sockunion *src_proto,
+ const union sockunion *dst_proto)
+{
+ struct nhrp_packet_header *hdr;
+
+ hdr = zbuf_push(zb, struct nhrp_packet_header);
+ if (!hdr)
+ return NULL;
+
+ *hdr = (struct nhrp_packet_header){
+ .afnum = htons(family2afi(sockunion_family(src_nbma))),
+ .protocol_type =
+ htons(family2proto(sockunion_family(src_proto))),
+ .version = NHRP_VERSION_RFC2332,
+ .type = type,
+ .hop_count = 64,
+ .src_nbma_address_len = sockunion_get_addrlen(src_nbma),
+ .src_protocol_address_len = sockunion_get_addrlen(src_proto),
+ .dst_protocol_address_len = sockunion_get_addrlen(dst_proto),
+ };
+
+ zbuf_put(zb, sockunion_get_addr(src_nbma), hdr->src_nbma_address_len);
+ zbuf_put(zb, sockunion_get_addr(src_proto),
+ hdr->src_protocol_address_len);
+ zbuf_put(zb, sockunion_get_addr(dst_proto),
+ hdr->dst_protocol_address_len);
+
+ return hdr;
+}
+
+struct nhrp_packet_header *nhrp_packet_pull(struct zbuf *zb,
+ union sockunion *src_nbma,
+ union sockunion *src_proto,
+ union sockunion *dst_proto)
+{
+ struct nhrp_packet_header *hdr;
+
+ hdr = zbuf_pull(zb, struct nhrp_packet_header);
+ if (!hdr)
+ return NULL;
+
+ sockunion_set(src_nbma, afi2family(htons(hdr->afnum)),
+ zbuf_pulln(zb,
+ hdr->src_nbma_address_len
+ + hdr->src_nbma_subaddress_len),
+ hdr->src_nbma_address_len + hdr->src_nbma_subaddress_len);
+ sockunion_set(src_proto, proto2family(htons(hdr->protocol_type)),
+ zbuf_pulln(zb, hdr->src_protocol_address_len),
+ hdr->src_protocol_address_len);
+ sockunion_set(dst_proto, proto2family(htons(hdr->protocol_type)),
+ zbuf_pulln(zb, hdr->dst_protocol_address_len),
+ hdr->dst_protocol_address_len);
+
+ return hdr;
+}
+
+uint16_t nhrp_packet_calculate_checksum(const uint8_t *pdu, uint16_t len)
+{
+ const uint16_t *pdu16 = (const uint16_t *)pdu;
+ uint32_t csum = 0;
+ int i;
+
+ for (i = 0; i < len / 2; i++)
+ csum += pdu16[i];
+ if (len & 1)
+ csum += htons(pdu[len - 1]);
+
+ while (csum & 0xffff0000)
+ csum = (csum & 0xffff) + (csum >> 16);
+
+ return (~csum) & 0xffff;
+}
+
+void nhrp_packet_complete(struct zbuf *zb, struct nhrp_packet_header *hdr)
+{
+ unsigned short size;
+
+ if (hdr->extension_offset)
+ nhrp_ext_push(zb, hdr,
+ NHRP_EXTENSION_END
+ | NHRP_EXTENSION_FLAG_COMPULSORY);
+
+ size = zb->tail - (uint8_t *)hdr;
+ hdr->packet_size = htons(size);
+ hdr->checksum = 0;
+ hdr->checksum = nhrp_packet_calculate_checksum((uint8_t *)hdr, size);
+}
+
+struct nhrp_cie_header *nhrp_cie_push(struct zbuf *zb, uint8_t code,
+ const union sockunion *nbma,
+ const union sockunion *proto)
+{
+ struct nhrp_cie_header *cie;
+
+ cie = zbuf_push(zb, struct nhrp_cie_header);
+ *cie = (struct nhrp_cie_header){
+ .code = code,
+ };
+ if (nbma) {
+ cie->nbma_address_len = sockunion_get_addrlen(nbma);
+ zbuf_put(zb, sockunion_get_addr(nbma), cie->nbma_address_len);
+ }
+ if (proto) {
+ cie->protocol_address_len = sockunion_get_addrlen(proto);
+ zbuf_put(zb, sockunion_get_addr(proto),
+ cie->protocol_address_len);
+ }
+
+ return cie;
+}
+
+struct nhrp_cie_header *nhrp_cie_pull(struct zbuf *zb,
+ struct nhrp_packet_header *hdr,
+ union sockunion *nbma,
+ union sockunion *proto)
+{
+ struct nhrp_cie_header *cie;
+
+ cie = zbuf_pull(zb, struct nhrp_cie_header);
+ if (!cie)
+ return NULL;
+
+ if (cie->nbma_address_len + cie->nbma_subaddress_len > 0) {
+ sockunion_set(nbma, afi2family(htons(hdr->afnum)),
+ zbuf_pulln(zb,
+ cie->nbma_address_len
+ + cie->nbma_subaddress_len),
+ cie->nbma_address_len + cie->nbma_subaddress_len);
+ } else {
+ sockunion_family(nbma) = AF_UNSPEC;
+ }
+
+ if (cie->protocol_address_len) {
+ sockunion_set(proto, proto2family(htons(hdr->protocol_type)),
+ zbuf_pulln(zb, cie->protocol_address_len),
+ cie->protocol_address_len);
+ } else {
+ sockunion_family(proto) = AF_UNSPEC;
+ }
+
+ return cie;
+}
+
+struct nhrp_extension_header *
+nhrp_ext_push(struct zbuf *zb, struct nhrp_packet_header *hdr, uint16_t type)
+{
+ struct nhrp_extension_header *ext;
+ ext = zbuf_push(zb, struct nhrp_extension_header);
+ if (!ext)
+ return NULL;
+
+ if (!hdr->extension_offset)
+ hdr->extension_offset =
+ htons(zb->tail - (uint8_t *)hdr
+ - sizeof(struct nhrp_extension_header));
+
+ *ext = (struct nhrp_extension_header){
+ .type = htons(type), .length = 0,
+ };
+ return ext;
+}
+
+void nhrp_ext_complete(struct zbuf *zb, struct nhrp_extension_header *ext)
+{
+ ext->length = htons(zb->tail - (uint8_t *)ext
+ - sizeof(struct nhrp_extension_header));
+}
+
+struct nhrp_extension_header *nhrp_ext_pull(struct zbuf *zb,
+ struct zbuf *payload)
+{
+ struct nhrp_extension_header *ext;
+ uint16_t plen;
+
+ ext = zbuf_pull(zb, struct nhrp_extension_header);
+ if (!ext)
+ return NULL;
+
+ plen = htons(ext->length);
+ zbuf_init(payload, zbuf_pulln(zb, plen), plen, plen);
+ return ext;
+}
+
+void nhrp_ext_request(struct zbuf *zb, struct nhrp_packet_header *hdr,
+ struct interface *ifp)
+{
+ /* Place holders for standard extensions */
+ nhrp_ext_push(zb, hdr,
+ NHRP_EXTENSION_FORWARD_TRANSIT_NHS
+ | NHRP_EXTENSION_FLAG_COMPULSORY);
+ nhrp_ext_push(zb, hdr,
+ NHRP_EXTENSION_REVERSE_TRANSIT_NHS
+ | NHRP_EXTENSION_FLAG_COMPULSORY);
+ nhrp_ext_push(zb, hdr,
+ NHRP_EXTENSION_RESPONDER_ADDRESS
+ | NHRP_EXTENSION_FLAG_COMPULSORY);
+}
+
+int nhrp_ext_reply(struct zbuf *zb, struct nhrp_packet_header *hdr,
+ struct interface *ifp, struct nhrp_extension_header *ext,
+ struct zbuf *extpayload)
+{
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_afi_data *ad = &nifp->afi[htons(hdr->afnum)];
+ struct nhrp_extension_header *dst;
+ struct nhrp_cie_header *cie;
+ uint16_t type;
+
+ type = htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY;
+ if (type == NHRP_EXTENSION_END)
+ return 0;
+
+ dst = nhrp_ext_push(zb, hdr, htons(ext->type));
+ if (!dst)
+ goto err;
+
+ switch (type) {
+ case NHRP_EXTENSION_RESPONDER_ADDRESS:
+ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nbma,
+ &ad->addr);
+ if (!cie)
+ goto err;
+ cie->mtu = htons(ad->mtu);
+ cie->holding_time = htons(ad->holdtime);
+ break;
+ default:
+ if (type & NHRP_EXTENSION_FLAG_COMPULSORY)
+ goto err;
+ /* fallthru */
+ case NHRP_EXTENSION_FORWARD_TRANSIT_NHS:
+ case NHRP_EXTENSION_REVERSE_TRANSIT_NHS:
+ /* Supported compulsory extensions, and any
+ * non-compulsory that is not explicitly handled,
+ * should be just copied. */
+ zbuf_copy(zb, extpayload, zbuf_used(extpayload));
+ break;
+ }
+ nhrp_ext_complete(zb, dst);
+ return 0;
+err:
+ zbuf_set_werror(zb);
+ return -1;
+}
+
+static void nhrp_packet_recvraw(struct thread *t)
+{
+ int fd = THREAD_FD(t), ifindex;
+ struct zbuf *zb;
+ struct interface *ifp;
+ struct nhrp_peer *p;
+ union sockunion remote_nbma;
+ uint8_t addr[64];
+ size_t len, addrlen;
+
+ thread_add_read(master, nhrp_packet_recvraw, 0, fd, NULL);
+
+ zb = zbuf_alloc(1500);
+ if (!zb)
+ return;
+
+ len = zbuf_size(zb);
+ addrlen = sizeof(addr);
+ if (os_recvmsg(zb->buf, &len, &ifindex, addr, &addrlen) < 0)
+ goto err;
+
+ zb->head = zb->buf;
+ zb->tail = zb->buf + len;
+
+ switch (addrlen) {
+ case 4:
+ sockunion_set(&remote_nbma, AF_INET, addr, addrlen);
+ break;
+ default:
+ goto err;
+ }
+
+ ifp = if_lookup_by_index(ifindex, VRF_DEFAULT);
+ if (!ifp)
+ goto err;
+
+ p = nhrp_peer_get(ifp, &remote_nbma);
+ if (!p)
+ goto err;
+
+ nhrp_peer_recv(p, zb);
+ nhrp_peer_unref(p);
+ return;
+
+err:
+ zbuf_free(zb);
+}
+
+int nhrp_packet_init(void)
+{
+ thread_add_read(master, nhrp_packet_recvraw, 0, os_socket(), NULL);
+ return 0;
+}
diff --git a/nhrpd/nhrp_peer.c b/nhrpd/nhrp_peer.c
new file mode 100644
index 0000000..e7f2eaf
--- /dev/null
+++ b/nhrpd/nhrp_peer.c
@@ -0,0 +1,1253 @@
+/* NHRP peer functions
+ * Copyright (c) 2014-2015 Timo Teräs
+ *
+ * This file is free software: you may copy, redistribute and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <netinet/if_ether.h>
+
+#include "zebra.h"
+#include "memory.h"
+#include "thread.h"
+#include "hash.h"
+#include "network.h"
+
+#include "nhrpd.h"
+#include "nhrp_protocol.h"
+#include "os.h"
+
+DEFINE_MTYPE_STATIC(NHRPD, NHRP_PEER, "NHRP peer entry");
+
+struct ipv6hdr {
+ uint8_t priority_version;
+ uint8_t flow_lbl[3];
+ uint16_t payload_len;
+ uint8_t nexthdr;
+ uint8_t hop_limit;
+ struct in6_addr saddr;
+ struct in6_addr daddr;
+};
+
+static void nhrp_packet_debug(struct zbuf *zb, const char *dir);
+
+static void nhrp_peer_check_delete(struct nhrp_peer *p)
+{
+ struct nhrp_interface *nifp = p->ifp->info;
+
+ if (p->ref || notifier_active(&p->notifier_list))
+ return;
+
+ debugf(NHRP_DEBUG_COMMON, "Deleting peer ref:%d remote:%pSU local:%pSU",
+ p->ref, &p->vc->remote.nbma, &p->vc->local.nbma);
+
+ THREAD_OFF(p->t_fallback);
+ THREAD_OFF(p->t_timer);
+ hash_release(nifp->peer_hash, p);
+ nhrp_interface_notify_del(p->ifp, &p->ifp_notifier);
+ nhrp_vc_notify_del(p->vc, &p->vc_notifier);
+ XFREE(MTYPE_NHRP_PEER, p);
+}
+
+static void nhrp_peer_notify_up(struct thread *t)
+{
+ struct nhrp_peer *p = THREAD_ARG(t);
+ struct nhrp_vc *vc = p->vc;
+ struct interface *ifp = p->ifp;
+ struct nhrp_interface *nifp = ifp->info;
+
+ p->t_fallback = NULL;
+ if (nifp->enabled && (!nifp->ipsec_profile || vc->ipsec)) {
+ p->online = 1;
+ nhrp_peer_ref(p);
+ notifier_call(&p->notifier_list, NOTIFY_PEER_UP);
+ nhrp_peer_unref(p);
+ }
+}
+
+static void __nhrp_peer_check(struct nhrp_peer *p)
+{
+ struct nhrp_vc *vc = p->vc;
+ struct interface *ifp = p->ifp;
+ struct nhrp_interface *nifp = ifp->info;
+ unsigned online;
+
+ online = nifp->enabled && (!nifp->ipsec_profile || vc->ipsec);
+ if (p->online != online) {
+ THREAD_OFF(p->t_fallback);
+ if (online && notifier_active(&p->notifier_list)) {
+ /* If we requested the IPsec connection, delay
+ * the up notification a bit to allow things
+ * settle down. This allows IKE to install
+ * SPDs and SAs. */
+ thread_add_timer_msec(master, nhrp_peer_notify_up, p,
+ 50, &p->t_fallback);
+ } else {
+ nhrp_peer_ref(p);
+ p->online = online;
+ if (online) {
+ notifier_call(&p->notifier_list,
+ NOTIFY_PEER_UP);
+ } else {
+ p->requested = p->fallback_requested = 0;
+ notifier_call(&p->notifier_list,
+ NOTIFY_PEER_DOWN);
+ }
+ nhrp_peer_unref(p);
+ }
+ }
+}
+
+static void nhrp_peer_vc_notify(struct notifier_block *n, unsigned long cmd)
+{
+ struct nhrp_peer *p = container_of(n, struct nhrp_peer, vc_notifier);
+
+ switch (cmd) {
+ case NOTIFY_VC_IPSEC_CHANGED:
+ __nhrp_peer_check(p);
+ break;
+ case NOTIFY_VC_IPSEC_UPDATE_NBMA:
+ nhrp_peer_ref(p);
+ notifier_call(&p->notifier_list, NOTIFY_PEER_NBMA_CHANGING);
+ nhrp_peer_unref(p);
+ break;
+ }
+}
+
+static void nhrp_peer_ifp_notify(struct notifier_block *n, unsigned long cmd)
+{
+ struct nhrp_peer *p = container_of(n, struct nhrp_peer, ifp_notifier);
+ struct nhrp_interface *nifp;
+ struct nhrp_vc *vc;
+
+ nhrp_peer_ref(p);
+ switch (cmd) {
+ case NOTIFY_INTERFACE_UP:
+ case NOTIFY_INTERFACE_DOWN:
+ __nhrp_peer_check(p);
+ break;
+ case NOTIFY_INTERFACE_NBMA_CHANGED:
+ /* Source NBMA changed, rebind to new VC */
+ nifp = p->ifp->info;
+ vc = nhrp_vc_get(&nifp->nbma, &p->vc->remote.nbma, 1);
+ if (vc && p->vc != vc) {
+ nhrp_vc_notify_del(p->vc, &p->vc_notifier);
+ p->vc = vc;
+ nhrp_vc_notify_add(p->vc, &p->vc_notifier,
+ nhrp_peer_vc_notify);
+ __nhrp_peer_check(p);
+ }
+ /* fallthru */ /* to post config update */
+ case NOTIFY_INTERFACE_ADDRESS_CHANGED:
+ notifier_call(&p->notifier_list, NOTIFY_PEER_IFCONFIG_CHANGED);
+ break;
+ case NOTIFY_INTERFACE_IPSEC_CHANGED:
+ __nhrp_peer_check(p);
+ notifier_call(&p->notifier_list, NOTIFY_PEER_IFCONFIG_CHANGED);
+ break;
+ case NOTIFY_INTERFACE_MTU_CHANGED:
+ notifier_call(&p->notifier_list, NOTIFY_PEER_MTU_CHANGED);
+ break;
+ }
+ nhrp_peer_unref(p);
+}
+
+static unsigned int nhrp_peer_key(const void *peer_data)
+{
+ const struct nhrp_peer *p = peer_data;
+ return sockunion_hash(&p->vc->remote.nbma);
+}
+
+static bool nhrp_peer_cmp(const void *cache_data, const void *key_data)
+{
+ const struct nhrp_peer *a = cache_data;
+ const struct nhrp_peer *b = key_data;
+
+ return a->ifp == b->ifp && a->vc == b->vc;
+}
+
+static void *nhrp_peer_create(void *data)
+{
+ struct nhrp_peer *p, *key = data;
+
+ p = XMALLOC(MTYPE_NHRP_PEER, sizeof(*p));
+
+ *p = (struct nhrp_peer){
+ .ref = 0,
+ .ifp = key->ifp,
+ .vc = key->vc,
+ .notifier_list = NOTIFIER_LIST_INITIALIZER(&p->notifier_list),
+ };
+ nhrp_vc_notify_add(p->vc, &p->vc_notifier, nhrp_peer_vc_notify);
+ nhrp_interface_notify_add(p->ifp, &p->ifp_notifier,
+ nhrp_peer_ifp_notify);
+
+ return p;
+}
+
+static void do_peer_hash_free(void *hb_data)
+{
+ struct nhrp_peer *p = (struct nhrp_peer *)hb_data;
+
+ nhrp_peer_check_delete(p);
+}
+
+void nhrp_peer_interface_del(struct interface *ifp)
+{
+ struct nhrp_interface *nifp = ifp->info;
+
+ debugf(NHRP_DEBUG_COMMON, "Cleaning up undeleted peer entries (%lu)",
+ nifp->peer_hash ? nifp->peer_hash->count : 0);
+
+ if (nifp->peer_hash) {
+ hash_clean(nifp->peer_hash, do_peer_hash_free);
+ assert(nifp->peer_hash->count == 0);
+ hash_free(nifp->peer_hash);
+ nifp->peer_hash = NULL;
+ }
+}
+
+struct nhrp_peer *nhrp_peer_get(struct interface *ifp,
+ const union sockunion *remote_nbma)
+{
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_peer key, *p;
+ struct nhrp_vc *vc;
+
+ if (!nifp->peer_hash) {
+ nifp->peer_hash = hash_create(nhrp_peer_key, nhrp_peer_cmp,
+ "NHRP Peer Hash");
+ if (!nifp->peer_hash)
+ return NULL;
+ }
+
+ vc = nhrp_vc_get(&nifp->nbma, remote_nbma, 1);
+ if (!vc)
+ return NULL;
+
+ key.ifp = ifp;
+ key.vc = vc;
+
+ p = hash_get(nifp->peer_hash, &key, nhrp_peer_create);
+ nhrp_peer_ref(p);
+ if (p->ref == 1)
+ __nhrp_peer_check(p);
+
+ return p;
+}
+
+struct nhrp_peer *nhrp_peer_ref(struct nhrp_peer *p)
+{
+ if (p)
+ p->ref++;
+ return p;
+}
+
+void nhrp_peer_unref(struct nhrp_peer *p)
+{
+ if (p) {
+ p->ref--;
+ nhrp_peer_check_delete(p);
+ }
+}
+
+static void nhrp_peer_request_timeout(struct thread *t)
+{
+ struct nhrp_peer *p = THREAD_ARG(t);
+ struct nhrp_vc *vc = p->vc;
+ struct interface *ifp = p->ifp;
+ struct nhrp_interface *nifp = ifp->info;
+
+
+ if (p->online)
+ return;
+
+ if (nifp->ipsec_fallback_profile && !p->prio
+ && !p->fallback_requested) {
+ p->fallback_requested = 1;
+ vici_request_vc(nifp->ipsec_fallback_profile, &vc->local.nbma,
+ &vc->remote.nbma, p->prio);
+ thread_add_timer(master, nhrp_peer_request_timeout, p, 30,
+ &p->t_fallback);
+ } else {
+ p->requested = p->fallback_requested = 0;
+ }
+}
+
+static void nhrp_peer_defer_vici_request(struct thread *t)
+{
+ struct nhrp_peer *p = THREAD_ARG(t);
+ struct nhrp_vc *vc = p->vc;
+ struct interface *ifp = p->ifp;
+ struct nhrp_interface *nifp = ifp->info;
+
+ THREAD_OFF(p->t_timer);
+
+ if (p->online) {
+ debugf(NHRP_DEBUG_COMMON,
+ "IPsec connection to %pSU already established",
+ &vc->remote.nbma);
+ } else {
+ vici_request_vc(nifp->ipsec_profile, &vc->local.nbma,
+ &vc->remote.nbma, p->prio);
+ thread_add_timer(
+ master, nhrp_peer_request_timeout, p,
+ (nifp->ipsec_fallback_profile && !p->prio) ? 15 : 30,
+ &p->t_fallback);
+ }
+}
+
+int nhrp_peer_check(struct nhrp_peer *p, int establish)
+{
+ struct nhrp_vc *vc = p->vc;
+ struct interface *ifp = p->ifp;
+ struct nhrp_interface *nifp = ifp->info;
+
+ if (p->online)
+ return 1;
+ if (!establish)
+ return 0;
+ if (p->requested)
+ return 0;
+ if (!nifp->ipsec_profile)
+ return 0;
+ if (sockunion_family(&vc->local.nbma) == AF_UNSPEC)
+ return 0;
+ if (vc->ipsec)
+ return 1;
+
+ p->prio = establish > 1;
+ p->requested = 1;
+
+ /* All NHRP registration requests are prioritized */
+ if (p->prio) {
+ vici_request_vc(nifp->ipsec_profile, &vc->local.nbma,
+ &vc->remote.nbma, p->prio);
+ thread_add_timer(
+ master, nhrp_peer_request_timeout, p,
+ (nifp->ipsec_fallback_profile && !p->prio) ? 15 : 30,
+ &p->t_fallback);
+ } else {
+ /* Maximum timeout is 1 second */
+ int r_time_ms = frr_weak_random() % 1000;
+
+ debugf(NHRP_DEBUG_COMMON,
+ "Initiating IPsec connection request to %pSU after %d ms:",
+ &vc->remote.nbma, r_time_ms);
+ thread_add_timer_msec(master, nhrp_peer_defer_vici_request,
+ p, r_time_ms, &p->t_timer);
+ }
+
+ return 0;
+}
+
+void nhrp_peer_notify_add(struct nhrp_peer *p, struct notifier_block *n,
+ notifier_fn_t fn)
+{
+ notifier_add(n, &p->notifier_list, fn);
+}
+
+void nhrp_peer_notify_del(struct nhrp_peer *p, struct notifier_block *n)
+{
+ notifier_del(n, &p->notifier_list);
+ nhrp_peer_check_delete(p);
+}
+
+void nhrp_peer_send(struct nhrp_peer *p, struct zbuf *zb)
+{
+ nhrp_packet_debug(zb, "Send");
+
+ if (!p->online)
+ return;
+
+ debugf(NHRP_DEBUG_KERNEL, "PACKET: Send %pSU -> %pSU",
+ &p->vc->local.nbma, &p->vc->remote.nbma);
+
+ os_sendmsg(zb->head, zbuf_used(zb), p->ifp->ifindex,
+ sockunion_get_addr(&p->vc->remote.nbma),
+ sockunion_get_addrlen(&p->vc->remote.nbma), ETH_P_NHRP);
+ zbuf_reset(zb);
+}
+
+static void nhrp_process_nat_extension(struct nhrp_packet_parser *pp,
+ union sockunion *proto,
+ union sockunion *cie_nbma)
+{
+ union sockunion cie_proto;
+ struct zbuf payload;
+ struct nhrp_extension_header *ext;
+ struct zbuf *extensions;
+
+ if (!cie_nbma)
+ return;
+
+ sockunion_family(cie_nbma) = AF_UNSPEC;
+
+ if (!proto || sockunion_family(proto) == AF_UNSPEC)
+ return;
+
+ /* Handle extensions */
+ extensions = zbuf_alloc(zbuf_used(&pp->extensions));
+ if (extensions) {
+ zbuf_copy_peek(extensions, &pp->extensions,
+ zbuf_used(&pp->extensions));
+ while ((ext = nhrp_ext_pull(extensions, &payload)) != NULL) {
+ switch (htons(ext->type)
+ & ~NHRP_EXTENSION_FLAG_COMPULSORY) {
+ case NHRP_EXTENSION_NAT_ADDRESS:
+ /* Process the NBMA and proto address in NAT
+ * extension and update the cache without which
+ * the neighbor table in the kernel contains the
+ * source NBMA address which is not reachable
+ * since it is behind a NAT device
+ */
+ debugf(NHRP_DEBUG_COMMON,
+ "shortcut res_resp: Processing NAT Extension for %pSU",
+ proto);
+ while (nhrp_cie_pull(&payload, pp->hdr,
+ cie_nbma, &cie_proto)) {
+ if (sockunion_family(&cie_proto)
+ == AF_UNSPEC)
+ continue;
+
+ if (!sockunion_cmp(proto, &cie_proto)) {
+ debugf(NHRP_DEBUG_COMMON,
+ "cie_nbma for proto %pSU is %pSU",
+ proto, cie_nbma);
+ break;
+ }
+ }
+ }
+ }
+ zbuf_free(extensions);
+ }
+}
+
+static void nhrp_handle_resolution_req(struct nhrp_packet_parser *pp)
+{
+ struct interface *ifp = pp->ifp;
+ struct zbuf *zb, payload;
+ struct nhrp_packet_header *hdr;
+ struct nhrp_cie_header *cie;
+ struct nhrp_extension_header *ext;
+ struct nhrp_cache *c;
+ union sockunion cie_nbma, cie_nbma_nat, cie_proto, *proto_addr,
+ *nbma_addr, *claimed_nbma_addr;
+ int holdtime, prefix_len, hostprefix_len;
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_peer *peer;
+ size_t paylen;
+
+ if (!(pp->if_ad->flags & NHRP_IFF_SHORTCUT)) {
+ debugf(NHRP_DEBUG_COMMON, "Shortcuts disabled");
+ /* FIXME: Send error indication? */
+ return;
+ }
+
+ if (pp->if_ad->network_id && pp->route_type == NHRP_ROUTE_OFF_NBMA
+ && pp->route_prefix.prefixlen < 8) {
+ debugf(NHRP_DEBUG_COMMON,
+ "Shortcut to more generic than /8 dropped");
+ return;
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "Parsing and replying to Resolution Req");
+
+ if (nhrp_route_address(ifp, &pp->src_proto, NULL, &peer)
+ != NHRP_ROUTE_NBMA_NEXTHOP)
+ return;
+
+ /* Copy payload CIE */
+ hostprefix_len = 8 * sockunion_get_addrlen(&pp->if_ad->addr);
+ paylen = zbuf_used(&pp->payload);
+ debugf(NHRP_DEBUG_COMMON, "shortcut res_rep: paylen %zu", paylen);
+
+ while ((cie = nhrp_cie_pull(&pp->payload, pp->hdr, &cie_nbma,
+ &cie_proto))
+ != NULL) {
+ prefix_len = cie->prefix_length;
+ debugf(NHRP_DEBUG_COMMON,
+ "shortcut res_rep: parsing CIE with prefixlen=%u",
+ prefix_len);
+ if (prefix_len == 0 || prefix_len >= hostprefix_len)
+ prefix_len = hostprefix_len;
+
+ if (prefix_len != hostprefix_len
+ && !(pp->hdr->flags
+ & htons(NHRP_FLAG_REGISTRATION_UNIQUE))) {
+ cie->code = NHRP_CODE_BINDING_NON_UNIQUE;
+ continue;
+ }
+
+ /* We currently support only unique prefix registrations */
+ if (prefix_len != hostprefix_len) {
+ cie->code = NHRP_CODE_ADMINISTRATIVELY_PROHIBITED;
+ continue;
+ }
+
+ proto_addr = (sockunion_family(&cie_proto) == AF_UNSPEC)
+ ? &pp->src_proto
+ : &cie_proto;
+
+ /* Check for this proto_addr in NHRP_NAT_EXTENSION */
+ nhrp_process_nat_extension(pp, proto_addr, &cie_nbma_nat);
+
+ if (sockunion_family(&cie_nbma_nat) == AF_UNSPEC) {
+ /* It may be possible that this resolution reply is
+ * coming directly from NATTED Spoke and there is not
+ * NAT Extension present
+ */
+ debugf(NHRP_DEBUG_COMMON,
+ "shortcut res_rep: No NAT Extension for %pSU",
+ proto_addr);
+
+ if (!sockunion_same(&pp->src_nbma,
+ &pp->peer->vc->remote.nbma)
+ && !nhrp_nhs_match_ip(&pp->peer->vc->remote.nbma,
+ nifp)) {
+ cie_nbma_nat = pp->peer->vc->remote.nbma;
+ debugf(NHRP_DEBUG_COMMON,
+ "shortcut res_rep: NAT detected using %pSU as cie_nbma",
+ &cie_nbma_nat);
+ }
+ }
+
+ if (sockunion_family(&cie_nbma_nat) != AF_UNSPEC)
+ nbma_addr = &cie_nbma_nat;
+ else if (sockunion_family(&cie_nbma) != AF_UNSPEC)
+ nbma_addr = &cie_nbma;
+ else
+ nbma_addr = &pp->src_nbma;
+
+ if (sockunion_family(&cie_nbma) != AF_UNSPEC)
+ claimed_nbma_addr = &cie_nbma;
+ else
+ claimed_nbma_addr = &pp->src_nbma;
+
+ holdtime = htons(cie->holding_time);
+ debugf(NHRP_DEBUG_COMMON,
+ "shortcut res_rep: holdtime is %u (if 0, using %u)",
+ holdtime, pp->if_ad->holdtime);
+ if (!holdtime)
+ holdtime = pp->if_ad->holdtime;
+
+ c = nhrp_cache_get(ifp, proto_addr, 1);
+ if (!c) {
+ debugf(NHRP_DEBUG_COMMON,
+ "shortcut res_rep: no cache found");
+ cie->code = NHRP_CODE_INSUFFICIENT_RESOURCES;
+ continue;
+ }
+
+ debugf(NHRP_DEBUG_COMMON,
+ "shortcut res_rep: updating binding for nmba addr %pSU",
+ nbma_addr);
+ if (!nhrp_cache_update_binding(
+ c, NHRP_CACHE_DYNAMIC, holdtime,
+ nhrp_peer_get(pp->ifp, nbma_addr), htons(cie->mtu),
+ nbma_addr, claimed_nbma_addr)) {
+ cie->code = NHRP_CODE_ADMINISTRATIVELY_PROHIBITED;
+ continue;
+ }
+
+ cie->code = NHRP_CODE_SUCCESS;
+ }
+
+ /* Create reply */
+ zb = zbuf_alloc(1500);
+ hdr = nhrp_packet_push(zb, NHRP_PACKET_RESOLUTION_REPLY, &pp->src_nbma,
+ &pp->src_proto, &pp->dst_proto);
+
+ /* Copied information from request */
+ hdr->flags = pp->hdr->flags
+ & htons(NHRP_FLAG_RESOLUTION_SOURCE_IS_ROUTER
+ | NHRP_FLAG_RESOLUTION_SOURCE_STABLE);
+ hdr->flags |= htons(NHRP_FLAG_RESOLUTION_DESTINATION_STABLE
+ | NHRP_FLAG_RESOLUTION_AUTHORATIVE);
+ hdr->u.request_id = pp->hdr->u.request_id;
+
+ /* CIE payload for the reply packet */
+ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nbma,
+ &pp->if_ad->addr);
+ cie->holding_time = htons(pp->if_ad->holdtime);
+ cie->mtu = htons(pp->if_ad->mtu);
+ if (pp->if_ad->network_id && pp->route_type == NHRP_ROUTE_OFF_NBMA)
+ cie->prefix_length = pp->route_prefix.prefixlen;
+ else
+ cie->prefix_length =
+ 8 * sockunion_get_addrlen(&pp->if_ad->addr);
+
+ /* Handle extensions */
+ while ((ext = nhrp_ext_pull(&pp->extensions, &payload)) != NULL) {
+ switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) {
+ case NHRP_EXTENSION_NAT_ADDRESS:
+ ext = nhrp_ext_push(zb, hdr,
+ NHRP_EXTENSION_NAT_ADDRESS);
+ if (!ext)
+ goto err;
+ if (sockunion_family(&nifp->nat_nbma) != AF_UNSPEC) {
+ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS,
+ &nifp->nat_nbma,
+ &pp->if_ad->addr);
+ if (!cie)
+ goto err;
+ cie->prefix_length =
+ 8 * sockunion_get_addrlen(
+ &pp->if_ad->addr);
+
+ cie->mtu = htons(pp->if_ad->mtu);
+ nhrp_ext_complete(zb, ext);
+ }
+ break;
+ default:
+ if (nhrp_ext_reply(zb, hdr, ifp, ext, &payload) < 0)
+ goto err;
+ break;
+ }
+ }
+ nhrp_packet_complete(zb, hdr);
+ nhrp_peer_send(peer, zb);
+err:
+ nhrp_peer_unref(peer);
+ zbuf_free(zb);
+}
+
+static void nhrp_handle_registration_request(struct nhrp_packet_parser *p)
+{
+ struct interface *ifp = p->ifp;
+ struct zbuf *zb, payload;
+ struct nhrp_packet_header *hdr;
+ struct nhrp_cie_header *cie;
+ struct nhrp_extension_header *ext;
+ struct nhrp_cache *c;
+ union sockunion cie_nbma, cie_proto, *proto_addr, *nbma_addr,
+ *nbma_natoa;
+ int holdtime, prefix_len, hostprefix_len, natted = 0;
+ size_t paylen;
+ void *pay;
+
+ debugf(NHRP_DEBUG_COMMON, "Parsing and replying to Registration Req");
+ hostprefix_len = 8 * sockunion_get_addrlen(&p->if_ad->addr);
+
+ if (!sockunion_same(&p->src_nbma, &p->peer->vc->remote.nbma))
+ natted = 1;
+
+ /* Create reply */
+ zb = zbuf_alloc(1500);
+ hdr = nhrp_packet_push(zb, NHRP_PACKET_REGISTRATION_REPLY, &p->src_nbma,
+ &p->src_proto, &p->if_ad->addr);
+
+ /* Copied information from request */
+ hdr->flags = p->hdr->flags & htons(NHRP_FLAG_REGISTRATION_UNIQUE
+ | NHRP_FLAG_REGISTRATION_NAT);
+ hdr->u.request_id = p->hdr->u.request_id;
+
+ /* Copy payload CIEs */
+ paylen = zbuf_used(&p->payload);
+ pay = zbuf_pushn(zb, paylen);
+ if (!pay)
+ goto err;
+ memcpy(pay, zbuf_pulln(&p->payload, paylen), paylen);
+ zbuf_init(&payload, pay, paylen, paylen);
+
+ while ((cie = nhrp_cie_pull(&payload, hdr, &cie_nbma, &cie_proto))
+ != NULL) {
+ prefix_len = cie->prefix_length;
+ if (prefix_len == 0 || prefix_len >= hostprefix_len)
+ prefix_len = hostprefix_len;
+
+ if (prefix_len != hostprefix_len
+ && !(p->hdr->flags
+ & htons(NHRP_FLAG_REGISTRATION_UNIQUE))) {
+ cie->code = NHRP_CODE_BINDING_NON_UNIQUE;
+ continue;
+ }
+
+ /* We currently support only unique prefix registrations */
+ if (prefix_len != hostprefix_len) {
+ cie->code = NHRP_CODE_ADMINISTRATIVELY_PROHIBITED;
+ continue;
+ }
+
+ proto_addr = (sockunion_family(&cie_proto) == AF_UNSPEC)
+ ? &p->src_proto
+ : &cie_proto;
+ nbma_addr = (sockunion_family(&cie_nbma) == AF_UNSPEC)
+ ? &p->src_nbma
+ : &cie_nbma;
+ nbma_natoa = NULL;
+ if (natted) {
+ nbma_natoa =
+ (sockunion_family(&p->peer->vc->remote.nbma)
+ == AF_UNSPEC)
+ ? nbma_addr
+ : &p->peer->vc->remote.nbma;
+ }
+
+ holdtime = htons(cie->holding_time);
+ if (!holdtime)
+ holdtime = p->if_ad->holdtime;
+
+ c = nhrp_cache_get(ifp, proto_addr, 1);
+ if (!c) {
+ cie->code = NHRP_CODE_INSUFFICIENT_RESOURCES;
+ continue;
+ }
+
+ if (!nhrp_cache_update_binding(c, NHRP_CACHE_DYNAMIC, holdtime,
+ nhrp_peer_ref(p->peer),
+ htons(cie->mtu), nbma_natoa,
+ nbma_addr)) {
+ cie->code = NHRP_CODE_ADMINISTRATIVELY_PROHIBITED;
+ continue;
+ }
+
+ cie->code = NHRP_CODE_SUCCESS;
+ }
+
+ /* Handle extensions */
+ while ((ext = nhrp_ext_pull(&p->extensions, &payload)) != NULL) {
+ switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) {
+ case NHRP_EXTENSION_NAT_ADDRESS:
+ ext = nhrp_ext_push(zb, hdr,
+ NHRP_EXTENSION_NAT_ADDRESS);
+ if (!ext)
+ goto err;
+ zbuf_copy(zb, &payload, zbuf_used(&payload));
+ if (natted) {
+ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS,
+ &p->peer->vc->remote.nbma,
+ &p->src_proto);
+ cie->prefix_length =
+ 8 * sockunion_get_addrlen(
+ &p->if_ad->addr);
+ cie->mtu = htons(p->if_ad->mtu);
+ }
+ nhrp_ext_complete(zb, ext);
+ break;
+ default:
+ if (nhrp_ext_reply(zb, hdr, ifp, ext, &payload) < 0)
+ goto err;
+ break;
+ }
+ }
+
+ nhrp_packet_complete(zb, hdr);
+ nhrp_peer_send(p->peer, zb);
+err:
+ zbuf_free(zb);
+}
+
+static int parse_ether_packet(struct zbuf *zb, uint16_t protocol_type,
+ union sockunion *src, union sockunion *dst)
+{
+ switch (protocol_type) {
+ case ETH_P_IP: {
+ struct iphdr *iph = zbuf_pull(zb, struct iphdr);
+ if (iph) {
+ if (src)
+ sockunion_set(src, AF_INET,
+ (uint8_t *)&iph->saddr,
+ sizeof(iph->saddr));
+ if (dst)
+ sockunion_set(dst, AF_INET,
+ (uint8_t *)&iph->daddr,
+ sizeof(iph->daddr));
+ }
+ } break;
+ case ETH_P_IPV6: {
+ struct ipv6hdr *iph = zbuf_pull(zb, struct ipv6hdr);
+ if (iph) {
+ if (src)
+ sockunion_set(src, AF_INET6,
+ (uint8_t *)&iph->saddr,
+ sizeof(iph->saddr));
+ if (dst)
+ sockunion_set(dst, AF_INET6,
+ (uint8_t *)&iph->daddr,
+ sizeof(iph->daddr));
+ }
+ } break;
+ default:
+ return 0;
+ }
+ return 1;
+}
+
+void nhrp_peer_send_indication(struct interface *ifp, uint16_t protocol_type,
+ struct zbuf *pkt)
+{
+ union sockunion dst;
+ struct zbuf *zb, payload;
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_afi_data *if_ad;
+ struct nhrp_packet_header *hdr;
+ struct nhrp_peer *p;
+
+ if (!nifp->enabled)
+ return;
+
+ payload = *pkt;
+ if (!parse_ether_packet(&payload, protocol_type, &dst, NULL))
+ return;
+
+ if (nhrp_route_address(ifp, &dst, NULL, &p) != NHRP_ROUTE_NBMA_NEXTHOP)
+ return;
+
+ if_ad = &nifp->afi[family2afi(sockunion_family(&dst))];
+ if (!(if_ad->flags & NHRP_IFF_REDIRECT)) {
+ debugf(NHRP_DEBUG_COMMON,
+ "Send Traffic Indication to %pSU about packet to %pSU ignored",
+ &p->vc->remote.nbma, &dst);
+ return;
+ }
+
+ debugf(NHRP_DEBUG_COMMON,
+ "Send Traffic Indication to %pSU (online=%d) about packet to %pSU",
+ &p->vc->remote.nbma, p->online, &dst);
+
+ /* Create reply */
+ zb = zbuf_alloc(1500);
+ hdr = nhrp_packet_push(zb, NHRP_PACKET_TRAFFIC_INDICATION, &nifp->nbma,
+ &if_ad->addr, &dst);
+ hdr->hop_count = 1;
+
+ /* Payload is the packet causing indication */
+ zbuf_copy(zb, pkt, zbuf_used(pkt));
+ nhrp_packet_complete(zb, hdr);
+ nhrp_peer_send(p, zb);
+ nhrp_peer_unref(p);
+ zbuf_free(zb);
+}
+
+static void nhrp_handle_error_ind(struct nhrp_packet_parser *pp)
+{
+ struct zbuf origmsg = pp->payload;
+ struct nhrp_packet_header *hdr;
+ struct nhrp_reqid *reqid;
+ union sockunion src_nbma, src_proto, dst_proto;
+
+ hdr = nhrp_packet_pull(&origmsg, &src_nbma, &src_proto, &dst_proto);
+ if (!hdr)
+ return;
+
+ debugf(NHRP_DEBUG_COMMON,
+ "Error Indication from %pSU about packet to %pSU ignored",
+ &pp->src_proto, &dst_proto);
+
+ reqid = nhrp_reqid_lookup(&nhrp_packet_reqid, htonl(hdr->u.request_id));
+ if (reqid)
+ reqid->cb(reqid, pp);
+}
+
+static void nhrp_handle_traffic_ind(struct nhrp_packet_parser *p)
+{
+ union sockunion dst;
+
+ if (!parse_ether_packet(&p->payload, htons(p->hdr->protocol_type), NULL,
+ &dst))
+ return;
+
+ debugf(NHRP_DEBUG_COMMON,
+ "Traffic Indication from %pSU about packet to %pSU: %s",
+ &p->src_proto, &dst,
+ (p->if_ad->flags & NHRP_IFF_SHORTCUT) ? "trying shortcut"
+ : "ignored");
+
+ if (p->if_ad->flags & NHRP_IFF_SHORTCUT)
+ nhrp_shortcut_initiate(&dst);
+}
+
+enum packet_type_t {
+ PACKET_UNKNOWN = 0,
+ PACKET_REQUEST,
+ PACKET_REPLY,
+ PACKET_INDICATION,
+};
+
+static struct {
+ enum packet_type_t type;
+ const char *name;
+ void (*handler)(struct nhrp_packet_parser *);
+} packet_types[] = {[0] =
+ {
+ .type = PACKET_UNKNOWN,
+ .name = "UNKNOWN",
+ },
+ [NHRP_PACKET_RESOLUTION_REQUEST] =
+ {
+ .type = PACKET_REQUEST,
+ .name = "Resolution-Request",
+ .handler = nhrp_handle_resolution_req,
+ },
+ [NHRP_PACKET_RESOLUTION_REPLY] =
+ {
+ .type = PACKET_REPLY,
+ .name = "Resolution-Reply",
+ },
+ [NHRP_PACKET_REGISTRATION_REQUEST] =
+ {
+ .type = PACKET_REQUEST,
+ .name = "Registration-Request",
+ .handler = nhrp_handle_registration_request,
+ },
+ [NHRP_PACKET_REGISTRATION_REPLY] =
+ {
+ .type = PACKET_REPLY,
+ .name = "Registration-Reply",
+ },
+ [NHRP_PACKET_PURGE_REQUEST] =
+ {
+ .type = PACKET_REQUEST,
+ .name = "Purge-Request",
+ },
+ [NHRP_PACKET_PURGE_REPLY] =
+ {
+ .type = PACKET_REPLY,
+ .name = "Purge-Reply",
+ },
+ [NHRP_PACKET_ERROR_INDICATION] =
+ {
+ .type = PACKET_INDICATION,
+ .name = "Error-Indication",
+ .handler = nhrp_handle_error_ind,
+ },
+ [NHRP_PACKET_TRAFFIC_INDICATION] = {
+ .type = PACKET_INDICATION,
+ .name = "Traffic-Indication",
+ .handler = nhrp_handle_traffic_ind,
+ }};
+
+static void nhrp_peer_forward(struct nhrp_peer *p,
+ struct nhrp_packet_parser *pp)
+{
+ struct zbuf *zb, *zb_copy, extpl;
+ struct nhrp_packet_header *hdr;
+ struct nhrp_extension_header *ext, *dst;
+ struct nhrp_cie_header *cie;
+ struct nhrp_interface *nifp = pp->ifp->info;
+ struct nhrp_afi_data *if_ad = pp->if_ad;
+ union sockunion cie_nbma, cie_protocol, cie_protocol_mandatory, *proto;
+ uint16_t type, len;
+ struct nhrp_cache *c;
+
+ if (pp->hdr->hop_count == 0)
+ return;
+
+ /* Create forward packet - copy header */
+ zb = zbuf_alloc(1500);
+ zb_copy = zbuf_alloc(1500);
+
+ hdr = nhrp_packet_push(zb, pp->hdr->type, &pp->src_nbma, &pp->src_proto,
+ &pp->dst_proto);
+ hdr->flags = pp->hdr->flags;
+ hdr->hop_count = pp->hdr->hop_count - 1;
+ hdr->u.request_id = pp->hdr->u.request_id;
+
+ /* Copy payload */
+ zbuf_copy_peek(zb_copy, &pp->payload, zbuf_used(&pp->payload));
+ zbuf_copy(zb, &pp->payload, zbuf_used(&pp->payload));
+
+ /* Get CIE Extension from Mandatory part */
+ sockunion_family(&cie_protocol_mandatory) = AF_UNSPEC;
+ nhrp_cie_pull(zb_copy, pp->hdr, &cie_nbma, &cie_protocol_mandatory);
+
+ /* Copy extensions */
+ while ((ext = nhrp_ext_pull(&pp->extensions, &extpl)) != NULL) {
+ type = htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY;
+ len = htons(ext->length);
+
+ if (type == NHRP_EXTENSION_END)
+ break;
+
+ dst = nhrp_ext_push(zb, hdr, htons(ext->type));
+ if (!dst)
+ goto err;
+
+ switch (type) {
+ case NHRP_EXTENSION_FORWARD_TRANSIT_NHS:
+ case NHRP_EXTENSION_REVERSE_TRANSIT_NHS:
+ zbuf_put(zb, extpl.head, len);
+ if ((type == NHRP_EXTENSION_REVERSE_TRANSIT_NHS)
+ == (packet_types[hdr->type].type == PACKET_REPLY)) {
+ /* Check NHS list for forwarding loop */
+ while (nhrp_cie_pull(&extpl, pp->hdr,
+ &cie_nbma,
+ &cie_protocol) != NULL) {
+ if (sockunion_same(&p->vc->remote.nbma,
+ &cie_nbma))
+ goto err;
+ }
+ /* Append our selves to the list */
+ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS,
+ &nifp->nbma, &if_ad->addr);
+ if (!cie)
+ goto err;
+ cie->mtu = htons(if_ad->mtu);
+ cie->holding_time = htons(if_ad->holdtime);
+ }
+ break;
+ case NHRP_EXTENSION_NAT_ADDRESS:
+ c = NULL;
+ proto = NULL;
+
+ /* If NAT extension is empty then attempt to populate
+ * it with cached NBMA information
+ */
+ if (len == 0) {
+ if (packet_types[hdr->type].type
+ == PACKET_REQUEST) {
+ debugf(NHRP_DEBUG_COMMON,
+ "Processing NHRP_EXTENSION_NAT_ADDRESS while forwarding the request packet");
+ proto = &pp->src_proto;
+ } else if (packet_types[hdr->type].type
+ == PACKET_REPLY) {
+ debugf(NHRP_DEBUG_COMMON,
+ "Processing NHRP_EXTENSION_NAT_ADDRESS while forwarding the reply packet");
+ /* For reply packet use protocol
+ * specified in CIE of mandatory part
+ * for cache lookup
+ */
+ if (sockunion_family(
+ &cie_protocol_mandatory)
+ != AF_UNSPEC)
+ proto = &cie_protocol_mandatory;
+ }
+ }
+
+ if (proto) {
+ debugf(NHRP_DEBUG_COMMON, "Proto is %pSU",
+ proto);
+ c = nhrp_cache_get(nifp->ifp, proto, 0);
+ }
+
+ if (c) {
+ debugf(NHRP_DEBUG_COMMON,
+ "c->cur.remote_nbma_natoa is %pSU",
+ &c->cur.remote_nbma_natoa);
+ if (sockunion_family(&c->cur.remote_nbma_natoa)
+ != AF_UNSPEC) {
+ cie = nhrp_cie_push(
+ zb,
+ NHRP_CODE_SUCCESS,
+ &c->cur.remote_nbma_natoa,
+ proto);
+ if (!cie)
+ goto err;
+ }
+ } else {
+ if (proto)
+ debugf(NHRP_DEBUG_COMMON,
+ "No cache entry for proto %pSU",
+ proto);
+ /* Copy existing NAT extension to new packet if
+ * either it was already not-empty, or we do not
+ * have valid cache information
+ */
+ zbuf_put(zb, extpl.head, len);
+ }
+ break;
+ default:
+ if (htons(ext->type) & NHRP_EXTENSION_FLAG_COMPULSORY)
+ /* FIXME: RFC says to just copy, but not
+ * append our selves to the transit NHS list
+ */
+ goto err;
+ /* fallthru */
+ case NHRP_EXTENSION_RESPONDER_ADDRESS:
+ /* Supported compulsory extensions, and any
+ * non-compulsory that is not explicitly handled,
+ * should be just copied.
+ */
+ zbuf_copy(zb, &extpl, len);
+ break;
+ }
+ nhrp_ext_complete(zb, dst);
+ }
+
+ nhrp_packet_complete(zb, hdr);
+ nhrp_peer_send(p, zb);
+ zbuf_free(zb);
+ zbuf_free(zb_copy);
+ return;
+err:
+ nhrp_packet_debug(pp->pkt, "FWD-FAIL");
+ zbuf_free(zb);
+ zbuf_free(zb_copy);
+}
+
+static void nhrp_packet_debug(struct zbuf *zb, const char *dir)
+{
+ union sockunion src_nbma, src_proto, dst_proto;
+ struct nhrp_packet_header *hdr;
+ struct zbuf zhdr;
+ int reply;
+
+ if (likely(!(debug_flags & NHRP_DEBUG_COMMON)))
+ return;
+
+ zbuf_init(&zhdr, zb->buf, zb->tail - zb->buf, zb->tail - zb->buf);
+ hdr = nhrp_packet_pull(&zhdr, &src_nbma, &src_proto, &dst_proto);
+
+ reply = packet_types[hdr->type].type == PACKET_REPLY;
+ debugf(NHRP_DEBUG_COMMON, "%s %s(%d) %pSU -> %pSU", dir,
+ (packet_types[hdr->type].name ? packet_types[hdr->type].name
+ : "Unknown"),
+ hdr->type, reply ? &dst_proto : &src_proto,
+ reply ? &src_proto : &dst_proto);
+}
+
+static int proto2afi(uint16_t proto)
+{
+ switch (proto) {
+ case ETH_P_IP:
+ return AFI_IP;
+ case ETH_P_IPV6:
+ return AFI_IP6;
+ }
+ return AF_UNSPEC;
+}
+
+struct nhrp_route_info {
+ int local;
+ struct interface *ifp;
+ struct nhrp_vc *vc;
+};
+
+void nhrp_peer_recv(struct nhrp_peer *p, struct zbuf *zb)
+{
+ struct nhrp_packet_header *hdr;
+ struct nhrp_vc *vc = p->vc;
+ struct interface *ifp = p->ifp;
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_packet_parser pp;
+ struct nhrp_peer *peer = NULL;
+ struct nhrp_reqid *reqid;
+ const char *info = NULL;
+ union sockunion *target_addr;
+ unsigned paylen, extoff, extlen, realsize;
+ afi_t nbma_afi, proto_afi;
+
+ debugf(NHRP_DEBUG_KERNEL, "PACKET: Recv %pSU -> %pSU", &vc->remote.nbma,
+ &vc->local.nbma);
+
+ if (!p->online) {
+ info = "peer not online";
+ goto drop;
+ }
+
+ if (nhrp_packet_calculate_checksum(zb->head, zbuf_used(zb)) != 0) {
+ info = "bad checksum";
+ goto drop;
+ }
+
+ realsize = zbuf_used(zb);
+ hdr = nhrp_packet_pull(zb, &pp.src_nbma, &pp.src_proto, &pp.dst_proto);
+ if (!hdr) {
+ info = "corrupt header";
+ goto drop;
+ }
+
+ pp.ifp = ifp;
+ pp.pkt = zb;
+ pp.hdr = hdr;
+ pp.peer = p;
+
+ nbma_afi = htons(hdr->afnum);
+ proto_afi = proto2afi(htons(hdr->protocol_type));
+ if (hdr->type > NHRP_PACKET_MAX || hdr->version != NHRP_VERSION_RFC2332
+ || nbma_afi >= AFI_MAX || proto_afi == AF_UNSPEC
+ || packet_types[hdr->type].type == PACKET_UNKNOWN
+ || htons(hdr->packet_size) > realsize) {
+ zlog_info(
+ "From %pSU: error: packet type %d, version %d, AFI %d, proto %x, size %d (real size %d)",
+ &vc->remote.nbma, (int)hdr->type, (int)hdr->version,
+ (int)nbma_afi, (int)htons(hdr->protocol_type),
+ (int)htons(hdr->packet_size), (int)realsize);
+ goto drop;
+ }
+ pp.if_ad = &((struct nhrp_interface *)ifp->info)->afi[proto_afi];
+
+ extoff = htons(hdr->extension_offset);
+ if (extoff) {
+ assert(zb->head > zb->buf);
+ uint32_t header_offset = zb->head - zb->buf;
+ if (extoff >= realsize) {
+ info = "extoff larger than packet";
+ goto drop;
+ }
+ if (extoff < header_offset) {
+ info = "extoff smaller than header offset";
+ goto drop;
+ }
+ paylen = extoff - header_offset;
+ } else {
+ paylen = zbuf_used(zb);
+ }
+ zbuf_init(&pp.payload, zbuf_pulln(zb, paylen), paylen, paylen);
+ extlen = zbuf_used(zb);
+ zbuf_init(&pp.extensions, zbuf_pulln(zb, extlen), extlen, extlen);
+
+ if (!nifp->afi[proto_afi].network_id) {
+ info = "nhrp not enabled";
+ goto drop;
+ }
+
+ nhrp_packet_debug(zb, "Recv");
+
+ /* FIXME: Check authentication here. This extension needs to be
+ * pre-handled. */
+
+ /* Figure out if this is local */
+ target_addr = (packet_types[hdr->type].type == PACKET_REPLY)
+ ? &pp.src_proto
+ : &pp.dst_proto;
+
+ if (sockunion_same(&pp.src_proto, &pp.dst_proto))
+ pp.route_type = NHRP_ROUTE_LOCAL;
+ else
+ pp.route_type = nhrp_route_address(pp.ifp, target_addr,
+ &pp.route_prefix, &peer);
+
+ switch (pp.route_type) {
+ case NHRP_ROUTE_LOCAL:
+ nhrp_packet_debug(zb, "!LOCAL");
+ if (packet_types[hdr->type].type == PACKET_REPLY) {
+ reqid = nhrp_reqid_lookup(&nhrp_packet_reqid,
+ htonl(hdr->u.request_id));
+ if (reqid) {
+ reqid->cb(reqid, &pp);
+ break;
+ } else {
+ nhrp_packet_debug(zb, "!UNKNOWN-REQID");
+ /* FIXME: send error-indication */
+ }
+ }
+ /* fallthru */ /* FIXME: double check, is this correct? */
+ case NHRP_ROUTE_OFF_NBMA:
+ if (packet_types[hdr->type].handler) {
+ packet_types[hdr->type].handler(&pp);
+ break;
+ }
+ break;
+ case NHRP_ROUTE_NBMA_NEXTHOP:
+ nhrp_peer_forward(peer, &pp);
+ break;
+ case NHRP_ROUTE_BLACKHOLE:
+ break;
+ }
+
+drop:
+ if (info) {
+ zlog_info("From %pSU: error: %s", &vc->remote.nbma, info);
+ }
+ if (peer)
+ nhrp_peer_unref(peer);
+ zbuf_free(zb);
+}
diff --git a/nhrpd/nhrp_protocol.h b/nhrpd/nhrp_protocol.h
new file mode 100644
index 0000000..3b94c81
--- /dev/null
+++ b/nhrpd/nhrp_protocol.h
@@ -0,0 +1,129 @@
+/* nhrp_protocol.h - NHRP protocol definitions
+ *
+ * Copyright (c) 2007-2012 Timo Teräs <timo.teras@iki.fi>
+ *
+ * This software is licensed under the MIT License.
+ * See MIT-LICENSE.txt for additional details.
+ */
+
+#ifndef NHRP_PROTOCOL_H
+#define NHRP_PROTOCOL_H
+
+#include <stdint.h>
+
+/* NHRP Ethernet protocol number */
+#define ETH_P_NHRP 0x2001
+
+/* NHRP Version */
+#define NHRP_VERSION_RFC2332 1
+
+/* NHRP Packet Types */
+#define NHRP_PACKET_RESOLUTION_REQUEST 1
+#define NHRP_PACKET_RESOLUTION_REPLY 2
+#define NHRP_PACKET_REGISTRATION_REQUEST 3
+#define NHRP_PACKET_REGISTRATION_REPLY 4
+#define NHRP_PACKET_PURGE_REQUEST 5
+#define NHRP_PACKET_PURGE_REPLY 6
+#define NHRP_PACKET_ERROR_INDICATION 7
+#define NHRP_PACKET_TRAFFIC_INDICATION 8
+#define NHRP_PACKET_MAX 8
+
+/* NHRP Extension Types */
+#define NHRP_EXTENSION_FLAG_COMPULSORY 0x8000
+#define NHRP_EXTENSION_END 0
+#define NHRP_EXTENSION_PAYLOAD 0
+#define NHRP_EXTENSION_RESPONDER_ADDRESS 3
+#define NHRP_EXTENSION_FORWARD_TRANSIT_NHS 4
+#define NHRP_EXTENSION_REVERSE_TRANSIT_NHS 5
+#define NHRP_EXTENSION_AUTHENTICATION 7
+#define NHRP_EXTENSION_VENDOR 8
+#define NHRP_EXTENSION_NAT_ADDRESS 9
+
+/* NHRP Error Indication Codes */
+#define NHRP_ERROR_UNRECOGNIZED_EXTENSION 1
+#define NHRP_ERROR_LOOP_DETECTED 2
+#define NHRP_ERROR_PROTOCOL_ADDRESS_UNREACHABLE 6
+#define NHRP_ERROR_PROTOCOL_ERROR 7
+#define NHRP_ERROR_SDU_SIZE_EXCEEDED 8
+#define NHRP_ERROR_INVALID_EXTENSION 9
+#define NHRP_ERROR_INVALID_RESOLUTION_REPLY 10
+#define NHRP_ERROR_AUTHENTICATION_FAILURE 11
+#define NHRP_ERROR_HOP_COUNT_EXCEEDED 15
+
+/* NHRP CIE Codes */
+#define NHRP_CODE_SUCCESS 0
+#define NHRP_CODE_ADMINISTRATIVELY_PROHIBITED 4
+#define NHRP_CODE_INSUFFICIENT_RESOURCES 5
+#define NHRP_CODE_NO_BINDING_EXISTS 11
+#define NHRP_CODE_BINDING_NON_UNIQUE 13
+#define NHRP_CODE_UNIQUE_ADDRESS_REGISTERED 14
+
+/* NHRP Flags for Resolution request/reply */
+#define NHRP_FLAG_RESOLUTION_SOURCE_IS_ROUTER 0x8000
+#define NHRP_FLAG_RESOLUTION_AUTHORATIVE 0x4000
+#define NHRP_FLAG_RESOLUTION_DESTINATION_STABLE 0x2000
+#define NHRP_FLAG_RESOLUTION_UNIQUE 0x1000
+#define NHRP_FLAG_RESOLUTION_SOURCE_STABLE 0x0800
+#define NHRP_FLAG_RESOLUTION_NAT 0x0002
+
+/* NHRP Flags for Registration request/reply */
+#define NHRP_FLAG_REGISTRATION_UNIQUE 0x8000
+#define NHRP_FLAG_REGISTRATION_NAT 0x0002
+
+/* NHRP Flags for Purge request/reply */
+#define NHRP_FLAG_PURGE_NO_REPLY 0x8000
+
+/* NHRP Authentication extension types (ala Cisco) */
+#define NHRP_AUTHENTICATION_PLAINTEXT 0x00000001
+
+/* NHRP Packet Structures */
+struct nhrp_packet_header {
+ /* Fixed header */
+ uint16_t afnum;
+ uint16_t protocol_type;
+ uint8_t snap[5];
+ uint8_t hop_count;
+ uint16_t packet_size;
+ uint16_t checksum;
+ uint16_t extension_offset;
+ uint8_t version;
+ uint8_t type;
+ uint8_t src_nbma_address_len;
+ uint8_t src_nbma_subaddress_len;
+
+ /* Mandatory header */
+ uint8_t src_protocol_address_len;
+ uint8_t dst_protocol_address_len;
+ uint16_t flags;
+ union {
+ uint32_t request_id;
+ struct {
+ uint16_t code;
+ uint16_t offset;
+ } error;
+ } u;
+} __attribute__((packed));
+
+struct nhrp_cie_header {
+ uint8_t code;
+ uint8_t prefix_length;
+ uint16_t unused;
+ uint16_t mtu;
+ uint16_t holding_time;
+ uint8_t nbma_address_len;
+ uint8_t nbma_subaddress_len;
+ uint8_t protocol_address_len;
+ uint8_t preference;
+} __attribute__((packed));
+
+struct nhrp_extension_header {
+ uint16_t type;
+ uint16_t length;
+} __attribute__((packed));
+
+struct nhrp_cisco_authentication_extension {
+ uint32_t type;
+ uint8_t secret[8];
+} __attribute__((packed));
+
+#endif
diff --git a/nhrpd/nhrp_route.c b/nhrpd/nhrp_route.c
new file mode 100644
index 0000000..698c6d0
--- /dev/null
+++ b/nhrpd/nhrp_route.c
@@ -0,0 +1,524 @@
+/* NHRP routing functions
+ * Copyright (c) 2014-2015 Timo Teräs
+ *
+ * This file is free software: you may copy, redistribute and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "nhrpd.h"
+#include "table.h"
+#include "memory.h"
+#include "stream.h"
+#include "log.h"
+#include "zclient.h"
+
+DEFINE_MTYPE_STATIC(NHRPD, NHRP_ROUTE, "NHRP routing entry");
+
+static struct zclient *zclient;
+static struct route_table *zebra_rib[AFI_MAX];
+
+struct route_info {
+ union sockunion via;
+ struct interface *ifp;
+ struct interface *nhrp_ifp;
+};
+
+static struct route_node *nhrp_route_update_get(const struct prefix *p,
+ int create)
+{
+ struct route_node *rn;
+ afi_t afi = family2afi(PREFIX_FAMILY(p));
+
+ if (!zebra_rib[afi])
+ return NULL;
+
+ if (create) {
+ rn = route_node_get(zebra_rib[afi], p);
+ if (!rn->info) {
+ rn->info = XCALLOC(MTYPE_NHRP_ROUTE,
+ sizeof(struct route_info));
+ route_lock_node(rn);
+ }
+ return rn;
+ } else {
+ return route_node_lookup(zebra_rib[afi], p);
+ }
+}
+
+static void nhrp_route_update_put(struct route_node *rn)
+{
+ struct route_info *ri = rn->info;
+
+ if (!ri->ifp && !ri->nhrp_ifp
+ && sockunion_is_null(&ri->via)) {
+ XFREE(MTYPE_NHRP_ROUTE, rn->info);
+ route_unlock_node(rn);
+ }
+ route_unlock_node(rn);
+}
+
+static void nhrp_route_update_zebra(const struct prefix *p,
+ union sockunion *nexthop,
+ struct interface *ifp)
+{
+ struct route_node *rn;
+ struct route_info *ri;
+
+ rn = nhrp_route_update_get(p, !sockunion_is_null(nexthop) || ifp);
+ if (rn) {
+ ri = rn->info;
+ ri->via = *nexthop;
+ ri->ifp = ifp;
+ nhrp_route_update_put(rn);
+ }
+}
+
+static void nhrp_zebra_register_neigh(vrf_id_t vrf_id, afi_t afi, bool reg)
+{
+ struct stream *s;
+
+ if (!zclient || zclient->sock < 0)
+ return;
+
+ s = zclient->obuf;
+ stream_reset(s);
+
+ zclient_create_header(s, reg ? ZEBRA_NHRP_NEIGH_REGISTER :
+ ZEBRA_NHRP_NEIGH_UNREGISTER,
+ vrf_id);
+ stream_putw(s, afi);
+ stream_putw_at(s, 0, stream_get_endp(s));
+ zclient_send_message(zclient);
+}
+
+void nhrp_route_update_nhrp(const struct prefix *p, struct interface *ifp)
+{
+ struct route_node *rn;
+ struct route_info *ri;
+
+ rn = nhrp_route_update_get(p, ifp != NULL);
+ if (rn) {
+ ri = rn->info;
+ ri->nhrp_ifp = ifp;
+ nhrp_route_update_put(rn);
+ }
+}
+
+void nhrp_route_announce(int add, enum nhrp_cache_type type,
+ const struct prefix *p, struct interface *ifp,
+ const union sockunion *nexthop, uint32_t mtu)
+{
+ struct zapi_route api;
+ struct zapi_nexthop *api_nh;
+ union sockunion *nexthop_ref = (union sockunion *)nexthop;
+
+ if (zclient->sock < 0)
+ return;
+
+ memset(&api, 0, sizeof(api));
+ api.type = ZEBRA_ROUTE_NHRP;
+ api.safi = SAFI_UNICAST;
+ api.vrf_id = VRF_DEFAULT;
+ api.prefix = *p;
+
+ switch (type) {
+ case NHRP_CACHE_NEGATIVE:
+ zapi_route_set_blackhole(&api, BLACKHOLE_REJECT);
+ ifp = NULL;
+ nexthop = NULL;
+ break;
+ case NHRP_CACHE_DYNAMIC:
+ case NHRP_CACHE_NHS:
+ case NHRP_CACHE_STATIC:
+ /* Regular route, so these are announced
+ * to other routing daemons */
+ break;
+ default:
+ SET_FLAG(api.flags, ZEBRA_FLAG_FIB_OVERRIDE);
+ break;
+ }
+ SET_FLAG(api.flags, ZEBRA_FLAG_ALLOW_RECURSION);
+
+ SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP);
+ api.nexthop_num = 1;
+ api_nh = &api.nexthops[0];
+ api_nh->vrf_id = VRF_DEFAULT;
+
+ switch (api.prefix.family) {
+ case AF_INET:
+ if (api.prefix.prefixlen == IPV4_MAX_BITLEN &&
+ nexthop_ref &&
+ memcmp(&nexthop_ref->sin.sin_addr, &api.prefix.u.prefix4,
+ sizeof(struct in_addr)) == 0) {
+ nexthop_ref = NULL;
+ }
+ if (nexthop_ref) {
+ api_nh->gate.ipv4 = nexthop_ref->sin.sin_addr;
+ api_nh->type = NEXTHOP_TYPE_IPV4;
+ }
+ if (ifp) {
+ api_nh->ifindex = ifp->ifindex;
+ if (api_nh->type == NEXTHOP_TYPE_IPV4)
+ api_nh->type = NEXTHOP_TYPE_IPV4_IFINDEX;
+ else
+ api_nh->type = NEXTHOP_TYPE_IFINDEX;
+ }
+ break;
+ case AF_INET6:
+ if (api.prefix.prefixlen == IPV6_MAX_BITLEN &&
+ nexthop_ref &&
+ memcmp(&nexthop_ref->sin6.sin6_addr, &api.prefix.u.prefix6,
+ sizeof(struct in6_addr)) == 0) {
+ nexthop_ref = NULL;
+ }
+ if (nexthop_ref) {
+ api_nh->gate.ipv6 = nexthop_ref->sin6.sin6_addr;
+ api_nh->type = NEXTHOP_TYPE_IPV6;
+ }
+ if (ifp) {
+ api_nh->ifindex = ifp->ifindex;
+ if (api_nh->type == NEXTHOP_TYPE_IPV6)
+ api_nh->type = NEXTHOP_TYPE_IPV6_IFINDEX;
+ else
+ api_nh->type = NEXTHOP_TYPE_IFINDEX;
+ }
+ break;
+ }
+ if (mtu) {
+ SET_FLAG(api.message, ZAPI_MESSAGE_MTU);
+ api.mtu = mtu;
+ }
+
+ if (unlikely(debug_flags & NHRP_DEBUG_ROUTE)) {
+ char buf[PREFIX_STRLEN];
+
+ zlog_debug(
+ "Zebra send: route %s %pFX nexthop %s metric %u count %d dev %s",
+ add ? "add" : "del", &api.prefix,
+ nexthop_ref ? inet_ntop(api.prefix.family,
+ &api_nh->gate,
+ buf, sizeof(buf))
+ : "<onlink>",
+ api.metric, api.nexthop_num, ifp ? ifp->name : "none");
+ }
+
+ zclient_route_send(add ? ZEBRA_ROUTE_ADD : ZEBRA_ROUTE_DELETE, zclient,
+ &api);
+}
+
+int nhrp_route_read(ZAPI_CALLBACK_ARGS)
+{
+ struct zapi_route api;
+ struct zapi_nexthop *api_nh;
+ struct interface *ifp = NULL;
+ union sockunion nexthop_addr;
+ int added;
+
+ if (zapi_route_decode(zclient->ibuf, &api) < 0)
+ return -1;
+
+ /* we completely ignore srcdest routes for now. */
+ if (CHECK_FLAG(api.message, ZAPI_MESSAGE_SRCPFX))
+ return 0;
+
+ /* ignore our routes */
+ if (api.type == ZEBRA_ROUTE_NHRP)
+ return 0;
+
+ sockunion_family(&nexthop_addr) = AF_UNSPEC;
+ if (CHECK_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP)) {
+ api_nh = &api.nexthops[0];
+
+ nexthop_addr.sa.sa_family = api.prefix.family;
+ switch (nexthop_addr.sa.sa_family) {
+ case AF_INET:
+ nexthop_addr.sin.sin_addr = api_nh->gate.ipv4;
+ break;
+ case AF_INET6:
+ nexthop_addr.sin6.sin6_addr = api_nh->gate.ipv6;
+ break;
+ }
+
+ if (api_nh->ifindex != IFINDEX_INTERNAL)
+ ifp = if_lookup_by_index(api_nh->ifindex, VRF_DEFAULT);
+ }
+
+ added = (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD);
+ debugf(NHRP_DEBUG_ROUTE, "if-route-%s: %pFX via %pSU dev %s",
+ added ? "add" : "del", &api.prefix, &nexthop_addr,
+ ifp ? ifp->name : "(none)");
+
+ nhrp_route_update_zebra(&api.prefix, &nexthop_addr, added ? ifp : NULL);
+ nhrp_shortcut_prefix_change(&api.prefix, !added);
+
+ return 0;
+}
+
+int nhrp_route_get_nexthop(const union sockunion *addr, struct prefix *p,
+ union sockunion *via, struct interface **ifp)
+{
+ struct route_node *rn;
+ struct route_info *ri;
+ struct prefix lookup;
+ afi_t afi = family2afi(sockunion_family(addr));
+
+ sockunion2hostprefix(addr, &lookup);
+
+ rn = route_node_match(zebra_rib[afi], &lookup);
+ if (!rn)
+ return 0;
+
+ ri = rn->info;
+ if (ri->nhrp_ifp) {
+ debugf(NHRP_DEBUG_ROUTE, "lookup %pFX: nhrp_if=%s", &lookup,
+ ri->nhrp_ifp->name);
+
+ if (via)
+ sockunion_family(via) = AF_UNSPEC;
+ if (ifp)
+ *ifp = ri->nhrp_ifp;
+ } else {
+ debugf(NHRP_DEBUG_ROUTE, "lookup %pFX: zebra route dev %s",
+ &lookup, ri->ifp ? ri->ifp->name : "(none)");
+
+ if (via)
+ *via = ri->via;
+ if (ifp)
+ *ifp = ri->ifp;
+ }
+ if (p)
+ *p = rn->p;
+ route_unlock_node(rn);
+ return 1;
+}
+
+enum nhrp_route_type nhrp_route_address(struct interface *in_ifp,
+ union sockunion *addr, struct prefix *p,
+ struct nhrp_peer **peer)
+{
+ struct interface *ifp = in_ifp;
+ struct nhrp_interface *nifp;
+ struct nhrp_cache *c;
+ union sockunion via[4];
+ uint32_t network_id = 0;
+ afi_t afi = family2afi(sockunion_family(addr));
+ int i;
+
+ if (ifp) {
+ nifp = ifp->info;
+ network_id = nifp->afi[afi].network_id;
+
+ c = nhrp_cache_get(ifp, addr, 0);
+ if (c && c->cur.type == NHRP_CACHE_LOCAL) {
+ if (p)
+ memset(p, 0, sizeof(*p));
+ return NHRP_ROUTE_LOCAL;
+ }
+ }
+
+ for (i = 0; i < 4; i++) {
+ if (!nhrp_route_get_nexthop(addr, p, &via[i], &ifp))
+ return NHRP_ROUTE_BLACKHOLE;
+ if (ifp) {
+ /* Departing from nbma network? */
+ nifp = ifp->info;
+ if (network_id
+ && network_id != nifp->afi[afi].network_id)
+ return NHRP_ROUTE_OFF_NBMA;
+ }
+ if (sockunion_family(&via[i]) == AF_UNSPEC)
+ break;
+ /* Resolve via node, but return the prefix of first match */
+ addr = &via[i];
+ p = NULL;
+ }
+
+ if (ifp) {
+ c = nhrp_cache_get(ifp, addr, 0);
+ if (c && c->cur.type >= NHRP_CACHE_DYNAMIC) {
+ if (p)
+ memset(p, 0, sizeof(*p));
+ if (c->cur.type == NHRP_CACHE_LOCAL)
+ return NHRP_ROUTE_LOCAL;
+ if (peer)
+ *peer = nhrp_peer_ref(c->cur.peer);
+ return NHRP_ROUTE_NBMA_NEXTHOP;
+ }
+ }
+
+ return NHRP_ROUTE_BLACKHOLE;
+}
+
+static void nhrp_zebra_connected(struct zclient *zclient)
+{
+ zclient_send_reg_requests(zclient, VRF_DEFAULT);
+ zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, AFI_IP,
+ ZEBRA_ROUTE_ALL, 0, VRF_DEFAULT);
+ zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, AFI_IP6,
+ ZEBRA_ROUTE_ALL, 0, VRF_DEFAULT);
+ nhrp_zebra_register_neigh(VRF_DEFAULT, AFI_IP, true);
+ nhrp_zebra_register_neigh(VRF_DEFAULT, AFI_IP6, true);
+}
+
+static zclient_handler *const nhrp_handlers[] = {
+ [ZEBRA_INTERFACE_ADDRESS_ADD] = nhrp_interface_address_add,
+ [ZEBRA_INTERFACE_ADDRESS_DELETE] = nhrp_interface_address_delete,
+ [ZEBRA_REDISTRIBUTE_ROUTE_ADD] = nhrp_route_read,
+ [ZEBRA_REDISTRIBUTE_ROUTE_DEL] = nhrp_route_read,
+ [ZEBRA_NHRP_NEIGH_ADDED] = nhrp_neighbor_operation,
+ [ZEBRA_NHRP_NEIGH_REMOVED] = nhrp_neighbor_operation,
+ [ZEBRA_NHRP_NEIGH_GET] = nhrp_neighbor_operation,
+ [ZEBRA_GRE_UPDATE] = nhrp_gre_update,
+};
+
+void nhrp_zebra_init(void)
+{
+ zebra_rib[AFI_IP] = route_table_init();
+ zebra_rib[AFI_IP6] = route_table_init();
+
+ zclient = zclient_new(master, &zclient_options_default, nhrp_handlers,
+ array_size(nhrp_handlers));
+ zclient->zebra_connected = nhrp_zebra_connected;
+ zclient_init(zclient, ZEBRA_ROUTE_NHRP, 0, &nhrpd_privs);
+}
+
+static void nhrp_table_node_cleanup(struct route_table *table,
+ struct route_node *node)
+{
+ if (!node->info)
+ return;
+
+ XFREE(MTYPE_NHRP_ROUTE, node->info);
+}
+
+void nhrp_send_zebra_configure_arp(struct interface *ifp, int family)
+{
+ struct stream *s;
+
+ if (!zclient || zclient->sock < 0) {
+ debugf(NHRP_DEBUG_COMMON, "%s() : zclient not ready",
+ __func__);
+ return;
+ }
+ s = zclient->obuf;
+ stream_reset(s);
+ zclient_create_header(s, ZEBRA_CONFIGURE_ARP, ifp->vrf->vrf_id);
+ stream_putc(s, family);
+ stream_putl(s, ifp->ifindex);
+ stream_putw_at(s, 0, stream_get_endp(s));
+ zclient_send_message(zclient);
+}
+
+void nhrp_send_zebra_gre_source_set(struct interface *ifp,
+ unsigned int link_idx,
+ vrf_id_t link_vrf_id)
+{
+ struct stream *s;
+
+ if (!zclient || zclient->sock < 0) {
+ zlog_err("%s : zclient not ready", __func__);
+ return;
+ }
+ if (link_idx == IFINDEX_INTERNAL || link_vrf_id == VRF_UNKNOWN) {
+ /* silently ignore */
+ return;
+ }
+ s = zclient->obuf;
+ stream_reset(s);
+ zclient_create_header(s, ZEBRA_GRE_SOURCE_SET, ifp->vrf->vrf_id);
+ stream_putl(s, ifp->ifindex);
+ stream_putl(s, link_idx);
+ stream_putl(s, link_vrf_id);
+ stream_putl(s, 0); /* mtu provisioning */
+ stream_putw_at(s, 0, stream_get_endp(s));
+ zclient_send_message(zclient);
+}
+
+void nhrp_send_zebra_nbr(union sockunion *in,
+ union sockunion *out,
+ struct interface *ifp)
+{
+ struct stream *s;
+
+ if (!zclient || zclient->sock < 0)
+ return;
+ s = zclient->obuf;
+ stream_reset(s);
+ zclient_neigh_ip_encode(s, out ? ZEBRA_NEIGH_IP_ADD :
+ ZEBRA_NEIGH_IP_DEL, in, out,
+ ifp, out ? ZEBRA_NEIGH_STATE_REACHABLE
+ : ZEBRA_NEIGH_STATE_FAILED);
+ stream_putw_at(s, 0, stream_get_endp(s));
+ zclient_send_message(zclient);
+}
+
+int nhrp_send_zebra_gre_request(struct interface *ifp)
+{
+ return zclient_send_zebra_gre_request(zclient, ifp);
+}
+
+void nhrp_zebra_terminate(void)
+{
+ nhrp_zebra_register_neigh(VRF_DEFAULT, AFI_IP, false);
+ nhrp_zebra_register_neigh(VRF_DEFAULT, AFI_IP6, false);
+ zclient_stop(zclient);
+ zclient_free(zclient);
+
+ zebra_rib[AFI_IP]->cleanup = nhrp_table_node_cleanup;
+ zebra_rib[AFI_IP6]->cleanup = nhrp_table_node_cleanup;
+ route_table_finish(zebra_rib[AFI_IP]);
+ route_table_finish(zebra_rib[AFI_IP6]);
+}
+
+int nhrp_gre_update(ZAPI_CALLBACK_ARGS)
+{
+ struct stream *s;
+ struct nhrp_gre_info gre_info, *val;
+ struct interface *ifp;
+
+ /* result */
+ s = zclient->ibuf;
+ if (vrf_id != VRF_DEFAULT)
+ return 0;
+
+ /* read GRE information */
+ STREAM_GETL(s, gre_info.ifindex);
+ STREAM_GETL(s, gre_info.ikey);
+ STREAM_GETL(s, gre_info.okey);
+ STREAM_GETL(s, gre_info.ifindex_link);
+ STREAM_GETL(s, gre_info.vrfid_link);
+ STREAM_GETL(s, gre_info.vtep_ip.s_addr);
+ STREAM_GETL(s, gre_info.vtep_ip_remote.s_addr);
+ if (gre_info.ifindex == IFINDEX_INTERNAL)
+ val = NULL;
+ else
+ val = hash_lookup(nhrp_gre_list, &gre_info);
+ if (val) {
+ if (gre_info.vtep_ip.s_addr != val->vtep_ip.s_addr ||
+ gre_info.vrfid_link != val->vrfid_link ||
+ gre_info.ifindex_link != val->ifindex_link ||
+ gre_info.ikey != val->ikey ||
+ gre_info.okey != val->okey) {
+ /* update */
+ memcpy(val, &gre_info, sizeof(struct nhrp_gre_info));
+ }
+ } else {
+ val = nhrp_gre_info_alloc(&gre_info);
+ }
+ ifp = if_lookup_by_index(gre_info.ifindex, vrf_id);
+ debugf(NHRP_DEBUG_EVENT, "%s: gre interface %d vr %d obtained from system",
+ ifp ? ifp->name : "<none>", gre_info.ifindex, vrf_id);
+ if (ifp)
+ nhrp_interface_update_nbma(ifp, val);
+ return 0;
+
+stream_failure:
+ zlog_err("%s(): error reading response ..", __func__);
+ return -1;
+}
diff --git a/nhrpd/nhrp_shortcut.c b/nhrpd/nhrp_shortcut.c
new file mode 100644
index 0000000..4975aca
--- /dev/null
+++ b/nhrpd/nhrp_shortcut.c
@@ -0,0 +1,543 @@
+/* NHRP shortcut related functions
+ * Copyright (c) 2014-2015 Timo Teräs
+ *
+ * This file is free software: you may copy, redistribute and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "nhrpd.h"
+#include "table.h"
+#include "memory.h"
+#include "thread.h"
+#include "log.h"
+#include "nhrp_protocol.h"
+
+DEFINE_MTYPE_STATIC(NHRPD, NHRP_SHORTCUT, "NHRP shortcut");
+
+static struct route_table *shortcut_rib[AFI_MAX];
+
+static void nhrp_shortcut_do_purge(struct thread *t);
+static void nhrp_shortcut_delete(struct nhrp_shortcut *s);
+static void nhrp_shortcut_send_resolution_req(struct nhrp_shortcut *s);
+
+static void nhrp_shortcut_check_use(struct nhrp_shortcut *s)
+{
+ if (s->expiring && s->cache && s->cache->used) {
+ debugf(NHRP_DEBUG_ROUTE, "Shortcut %pFX used and expiring",
+ s->p);
+ nhrp_shortcut_send_resolution_req(s);
+ }
+}
+
+static void nhrp_shortcut_do_expire(struct thread *t)
+{
+ struct nhrp_shortcut *s = THREAD_ARG(t);
+
+ thread_add_timer(master, nhrp_shortcut_do_purge, s, s->holding_time / 3,
+ &s->t_timer);
+ s->expiring = 1;
+ nhrp_shortcut_check_use(s);
+}
+
+static void nhrp_shortcut_cache_notify(struct notifier_block *n,
+ unsigned long cmd)
+{
+ struct nhrp_shortcut *s =
+ container_of(n, struct nhrp_shortcut, cache_notifier);
+ struct nhrp_cache *c = s->cache;
+
+ switch (cmd) {
+ case NOTIFY_CACHE_UP:
+ if (!s->route_installed) {
+ debugf(NHRP_DEBUG_ROUTE,
+ "Shortcut: route install %pFX nh %pSU dev %s",
+ s->p, &c->remote_addr,
+ c && c->ifp ? c->ifp->name : "<unk>");
+
+ nhrp_route_announce(1, s->type, s->p, c ? c->ifp : NULL,
+ c ? &c->remote_addr : NULL, 0);
+ s->route_installed = 1;
+ }
+ break;
+ case NOTIFY_CACHE_USED:
+ nhrp_shortcut_check_use(s);
+ break;
+ case NOTIFY_CACHE_DOWN:
+ case NOTIFY_CACHE_DELETE:
+ if (s->route_installed) {
+ nhrp_route_announce(0, NHRP_CACHE_INVALID, s->p, NULL,
+ NULL, 0);
+ s->route_installed = 0;
+ }
+ if (cmd == NOTIFY_CACHE_DELETE)
+ nhrp_shortcut_delete(s);
+ break;
+ }
+}
+
+static void nhrp_shortcut_update_binding(struct nhrp_shortcut *s,
+ enum nhrp_cache_type type,
+ struct nhrp_cache *c, int holding_time)
+{
+ s->type = type;
+ if (c != s->cache) {
+ if (s->cache) {
+ nhrp_cache_notify_del(s->cache, &s->cache_notifier);
+ s->cache = NULL;
+ }
+ s->cache = c;
+ if (s->cache) {
+ nhrp_cache_notify_add(s->cache, &s->cache_notifier,
+ nhrp_shortcut_cache_notify);
+ if (s->cache->route_installed) {
+ /* Force renewal of Zebra announce on prefix
+ * change */
+ s->route_installed = 0;
+ debugf(NHRP_DEBUG_ROUTE,
+ "Shortcut: forcing renewal of zebra announce on prefix change peer %pSU ht %u cur nbma %pSU dev %s",
+ &s->cache->remote_addr, holding_time,
+ &s->cache->cur.remote_nbma_natoa,
+ s->cache->ifp->name);
+ nhrp_shortcut_cache_notify(&s->cache_notifier,
+ NOTIFY_CACHE_UP);
+ }
+ }
+ if (!s->cache || !s->cache->route_installed) {
+ debugf(NHRP_DEBUG_ROUTE,
+ "Shortcut: notify cache down because cache?%s or ri?%s",
+ s->cache ? "yes" : "no",
+ s->cache ? (s->cache->route_installed ? "yes"
+ : "no")
+ : "n/a");
+ nhrp_shortcut_cache_notify(&s->cache_notifier,
+ NOTIFY_CACHE_DOWN);
+ }
+ }
+ if (s->type == NHRP_CACHE_NEGATIVE && !s->route_installed) {
+ nhrp_route_announce(1, s->type, s->p, NULL, NULL, 0);
+ s->route_installed = 1;
+ } else if (s->type == NHRP_CACHE_INVALID && s->route_installed) {
+ nhrp_route_announce(0, NHRP_CACHE_INVALID, s->p, NULL, NULL, 0);
+ s->route_installed = 0;
+ }
+
+ THREAD_OFF(s->t_timer);
+ if (holding_time) {
+ s->expiring = 0;
+ s->holding_time = holding_time;
+ thread_add_timer(master, nhrp_shortcut_do_expire, s,
+ 2 * holding_time / 3, &s->t_timer);
+ }
+}
+
+static void nhrp_shortcut_delete(struct nhrp_shortcut *s)
+{
+ struct route_node *rn;
+ afi_t afi = family2afi(PREFIX_FAMILY(s->p));
+
+ THREAD_OFF(s->t_timer);
+ nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid);
+
+ debugf(NHRP_DEBUG_ROUTE, "Shortcut %pFX purged", s->p);
+
+ nhrp_shortcut_update_binding(s, NHRP_CACHE_INVALID, NULL, 0);
+
+ /* Delete node */
+ rn = route_node_lookup(shortcut_rib[afi], s->p);
+ if (rn) {
+ XFREE(MTYPE_NHRP_SHORTCUT, rn->info);
+ rn->info = NULL;
+ route_unlock_node(rn);
+ route_unlock_node(rn);
+ }
+}
+
+static void nhrp_shortcut_do_purge(struct thread *t)
+{
+ struct nhrp_shortcut *s = THREAD_ARG(t);
+ s->t_timer = NULL;
+ nhrp_shortcut_delete(s);
+}
+
+static struct nhrp_shortcut *nhrp_shortcut_get(struct prefix *p)
+{
+ struct nhrp_shortcut *s;
+ struct route_node *rn;
+ afi_t afi = family2afi(PREFIX_FAMILY(p));
+
+ if (!shortcut_rib[afi])
+ return 0;
+
+ rn = route_node_get(shortcut_rib[afi], p);
+ if (!rn->info) {
+ s = rn->info = XCALLOC(MTYPE_NHRP_SHORTCUT,
+ sizeof(struct nhrp_shortcut));
+ s->type = NHRP_CACHE_INVALID;
+ s->p = &rn->p;
+
+ debugf(NHRP_DEBUG_ROUTE, "Shortcut %pFX created", s->p);
+ } else {
+ s = rn->info;
+ route_unlock_node(rn);
+ }
+ return s;
+}
+
+static void nhrp_shortcut_recv_resolution_rep(struct nhrp_reqid *reqid,
+ void *arg)
+{
+ struct nhrp_packet_parser *pp = arg;
+ struct interface *ifp = pp->ifp;
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_shortcut *s =
+ container_of(reqid, struct nhrp_shortcut, reqid);
+ struct nhrp_shortcut *ps;
+ struct nhrp_extension_header *ext;
+ struct nhrp_cie_header *cie;
+ struct nhrp_cache *c = NULL;
+ struct nhrp_cache *c_dst = NULL;
+ union sockunion *proto, cie_proto, *nbma, cie_nbma, nat_nbma;
+ struct prefix prefix, route_prefix;
+ struct zbuf extpl;
+ int holding_time = pp->if_ad->holdtime;
+
+ nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid);
+ THREAD_OFF(s->t_timer);
+ thread_add_timer(master, nhrp_shortcut_do_purge, s, 1, &s->t_timer);
+
+ if (pp->hdr->type != NHRP_PACKET_RESOLUTION_REPLY) {
+ if (pp->hdr->type == NHRP_PACKET_ERROR_INDICATION
+ && pp->hdr->u.error.code
+ == NHRP_ERROR_PROTOCOL_ADDRESS_UNREACHABLE) {
+ debugf(NHRP_DEBUG_COMMON,
+ "Shortcut: Resolution: Protocol address unreachable");
+ nhrp_shortcut_update_binding(s, NHRP_CACHE_NEGATIVE,
+ NULL, holding_time);
+ } else {
+ debugf(NHRP_DEBUG_COMMON,
+ "Shortcut: Resolution failed");
+ }
+ return;
+ }
+
+ /* Minor sanity check */
+ prefix2sockunion(s->p, &cie_proto);
+ if (!sockunion_same(&cie_proto, &pp->dst_proto)) {
+ debugf(NHRP_DEBUG_COMMON,
+ "Shortcut: Warning dst_proto altered from %pSU to %pSU",
+ &cie_proto, &pp->dst_proto);
+ ;
+ }
+
+ /* One or more CIEs should be given as reply, we support only one */
+ cie = nhrp_cie_pull(&pp->payload, pp->hdr, &cie_nbma, &cie_proto);
+ if (!cie || cie->code != NHRP_CODE_SUCCESS) {
+ debugf(NHRP_DEBUG_COMMON, "Shortcut: CIE code %d",
+ cie ? cie->code : -1);
+ return;
+ }
+
+ proto = sockunion_family(&cie_proto) != AF_UNSPEC ? &cie_proto
+ : &pp->dst_proto;
+ if (cie->holding_time)
+ holding_time = htons(cie->holding_time);
+
+ prefix = *s->p;
+ prefix.prefixlen = cie->prefix_length;
+
+ /* Sanity check prefix length */
+ if (prefix.prefixlen >= 8 * prefix_blen(&prefix)
+ || prefix.prefixlen == 0) {
+ prefix.prefixlen = 8 * prefix_blen(&prefix);
+ } else if (nhrp_route_address(NULL, &pp->dst_proto, &route_prefix, NULL)
+ == NHRP_ROUTE_NBMA_NEXTHOP) {
+ if (prefix.prefixlen < route_prefix.prefixlen)
+ prefix.prefixlen = route_prefix.prefixlen;
+ }
+
+ /* Parse extensions */
+ memset(&nat_nbma, 0, sizeof(nat_nbma));
+ while ((ext = nhrp_ext_pull(&pp->extensions, &extpl)) != NULL) {
+ switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) {
+ case NHRP_EXTENSION_NAT_ADDRESS: {
+ struct nhrp_cie_header *cie_nat;
+
+ do {
+ union sockunion cie_nat_proto, cie_nat_nbma;
+
+ sockunion_family(&cie_nat_proto) = AF_UNSPEC;
+ sockunion_family(&cie_nat_nbma) = AF_UNSPEC;
+ cie_nat = nhrp_cie_pull(&extpl, pp->hdr,
+ &cie_nat_nbma,
+ &cie_nat_proto);
+ /* We are interested only in peer CIE */
+ if (cie_nat
+ && sockunion_same(&cie_nat_proto, proto)) {
+ nat_nbma = cie_nat_nbma;
+ }
+ } while (cie_nat);
+ } break;
+ default:
+ break;
+ }
+ }
+
+ /* Update cache entry for the protocol to nbma binding */
+ if (sockunion_family(&nat_nbma) != AF_UNSPEC) {
+ debugf(NHRP_DEBUG_COMMON,
+ "Shortcut: NAT detected (NAT extension) proto %pSU NBMA %pSU claimed-NBMA %pSU",
+ proto, &nat_nbma, &cie_nbma);
+ nbma = &nat_nbma;
+ }
+ /* For NHRP resolution reply the cie_nbma in mandatory part is the
+ * address of the actual address of the sender
+ */
+ else if (!sockunion_same(&cie_nbma, &pp->peer->vc->remote.nbma)
+ && !nhrp_nhs_match_ip(&pp->peer->vc->remote.nbma, nifp)) {
+ debugf(NHRP_DEBUG_COMMON,
+ "Shortcut: NAT detected (no NAT Extension) proto %pSU NBMA %pSU claimed-NBMA %pSU",
+ proto, &pp->peer->vc->remote.nbma, &cie_nbma);
+ nbma = &pp->peer->vc->remote.nbma;
+ nat_nbma = *nbma;
+ } else {
+ nbma = &cie_nbma;
+ }
+
+ debugf(NHRP_DEBUG_COMMON,
+ "Shortcut: %pFX is at proto %pSU dst_proto %pSU NBMA %pSU cie-holdtime %d",
+ &prefix, proto, &pp->dst_proto, nbma,
+ htons(cie->holding_time));
+
+ if (sockunion_family(nbma)) {
+ c = nhrp_cache_get(pp->ifp, proto, 1);
+ if (c) {
+ debugf(NHRP_DEBUG_COMMON,
+ "Shortcut: cache found, update binding");
+ nhrp_cache_update_binding(c, NHRP_CACHE_DYNAMIC,
+ holding_time,
+ nhrp_peer_get(pp->ifp, nbma),
+ htons(cie->mtu),
+ nbma,
+ &cie_nbma);
+ } else {
+ debugf(NHRP_DEBUG_COMMON,
+ "Shortcut: no cache for proto %pSU", proto);
+ }
+
+ /* Update cache binding for dst_proto as well */
+ if (sockunion_cmp(proto, &pp->dst_proto)) {
+ c_dst = nhrp_cache_get(pp->ifp, &pp->dst_proto, 1);
+ if (c_dst) {
+ debugf(NHRP_DEBUG_COMMON,
+ "Shortcut: cache found, update binding");
+ nhrp_cache_update_binding(c_dst,
+ NHRP_CACHE_DYNAMIC,
+ holding_time,
+ nhrp_peer_get(pp->ifp, nbma),
+ htons(cie->mtu),
+ nbma,
+ &cie_nbma);
+ } else {
+ debugf(NHRP_DEBUG_COMMON,
+ "Shortcut: no cache for proto %pSU",
+ &pp->dst_proto);
+ }
+ }
+ }
+
+ /* Update shortcut entry for subnet to protocol gw binding */
+ if (c) {
+ ps = nhrp_shortcut_get(&prefix);
+ if (ps) {
+ ps->addr = s->addr;
+ debugf(NHRP_DEBUG_COMMON,
+ "Shortcut: calling update_binding");
+ nhrp_shortcut_update_binding(ps, NHRP_CACHE_DYNAMIC, c,
+ holding_time);
+ } else {
+ debugf(NHRP_DEBUG_COMMON,
+ "Shortcut: proto diff but no ps");
+ }
+ } else {
+ debugf(NHRP_DEBUG_COMMON,
+ "NO Shortcut because c NULL?%s or same proto?%s",
+ c ? "no" : "yes",
+ proto && pp && sockunion_same(proto, &pp->dst_proto)
+ ? "yes"
+ : "no");
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "Shortcut: Resolution reply handled");
+}
+
+static void nhrp_shortcut_send_resolution_req(struct nhrp_shortcut *s)
+{
+ struct zbuf *zb;
+ struct nhrp_packet_header *hdr;
+ struct interface *ifp;
+ struct nhrp_interface *nifp;
+ struct nhrp_afi_data *if_ad;
+ struct nhrp_peer *peer;
+ struct nhrp_cie_header *cie;
+ struct nhrp_extension_header *ext;
+
+ if (nhrp_route_address(NULL, &s->addr, NULL, &peer)
+ != NHRP_ROUTE_NBMA_NEXTHOP)
+ return;
+
+ if (s->type == NHRP_CACHE_INVALID || s->type == NHRP_CACHE_NEGATIVE)
+ s->type = NHRP_CACHE_INCOMPLETE;
+
+ ifp = peer->ifp;
+ nifp = ifp->info;
+
+ /* Create request */
+ zb = zbuf_alloc(1500);
+ hdr = nhrp_packet_push(
+ zb, NHRP_PACKET_RESOLUTION_REQUEST, &nifp->nbma,
+ &nifp->afi[family2afi(sockunion_family(&s->addr))].addr,
+ &s->addr);
+ hdr->u.request_id =
+ htonl(nhrp_reqid_alloc(&nhrp_packet_reqid, &s->reqid,
+ nhrp_shortcut_recv_resolution_rep));
+ hdr->flags = htons(NHRP_FLAG_RESOLUTION_SOURCE_IS_ROUTER
+ | NHRP_FLAG_RESOLUTION_AUTHORATIVE
+ | NHRP_FLAG_RESOLUTION_SOURCE_STABLE);
+
+ /* RFC2332 - One or zero CIEs, if CIE is present contains:
+ * - Prefix length: widest acceptable prefix we accept (if U set, 0xff)
+ * - MTU: MTU of the source station
+ * - Holding Time: Max time to cache the source information
+ * */
+ /* FIXME: push CIE for each local protocol address */
+ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, NULL, NULL);
+ if_ad = &nifp->afi[family2afi(sockunion_family(&s->addr))];
+ cie->prefix_length = (if_ad->flags & NHRP_IFF_REG_NO_UNIQUE)
+ ? 8 * sockunion_get_addrlen(&s->addr)
+ : 0xff;
+ cie->holding_time = htons(if_ad->holdtime);
+ cie->mtu = htons(if_ad->mtu);
+ debugf(NHRP_DEBUG_COMMON,
+ "Shortcut res_req: set cie ht to %u and mtu to %u. shortcut ht is %u",
+ ntohs(cie->holding_time), ntohs(cie->mtu), s->holding_time);
+
+ nhrp_ext_request(zb, hdr, ifp);
+
+ /* Cisco NAT detection extension */
+ hdr->flags |= htons(NHRP_FLAG_RESOLUTION_NAT);
+ ext = nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS);
+ if (sockunion_family(&nifp->nat_nbma) != AF_UNSPEC) {
+ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nat_nbma,
+ &if_ad->addr);
+ cie->prefix_length = 8 * sockunion_get_addrlen(&if_ad->addr);
+ cie->mtu = htons(if_ad->mtu);
+ nhrp_ext_complete(zb, ext);
+ }
+
+ nhrp_packet_complete(zb, hdr);
+
+ nhrp_peer_send(peer, zb);
+ nhrp_peer_unref(peer);
+ zbuf_free(zb);
+}
+
+void nhrp_shortcut_initiate(union sockunion *addr)
+{
+ struct prefix p;
+ struct nhrp_shortcut *s;
+
+ if (!sockunion2hostprefix(addr, &p))
+ return;
+
+ s = nhrp_shortcut_get(&p);
+ if (s && s->type != NHRP_CACHE_INCOMPLETE) {
+ s->addr = *addr;
+ THREAD_OFF(s->t_timer);
+ thread_add_timer(master, nhrp_shortcut_do_purge, s, 30,
+ &s->t_timer);
+ nhrp_shortcut_send_resolution_req(s);
+ }
+}
+
+void nhrp_shortcut_init(void)
+{
+ shortcut_rib[AFI_IP] = route_table_init();
+ shortcut_rib[AFI_IP6] = route_table_init();
+}
+
+void nhrp_shortcut_terminate(void)
+{
+ route_table_finish(shortcut_rib[AFI_IP]);
+ route_table_finish(shortcut_rib[AFI_IP6]);
+}
+
+void nhrp_shortcut_foreach(afi_t afi,
+ void (*cb)(struct nhrp_shortcut *, void *),
+ void *ctx)
+{
+ struct route_table *rt = shortcut_rib[afi];
+ struct route_node *rn;
+ route_table_iter_t iter;
+
+ if (!rt)
+ return;
+
+ route_table_iter_init(&iter, rt);
+ while ((rn = route_table_iter_next(&iter)) != NULL) {
+ if (rn->info)
+ cb(rn->info, ctx);
+ }
+ route_table_iter_cleanup(&iter);
+}
+
+struct purge_ctx {
+ const struct prefix *p;
+ int deleted;
+};
+
+void nhrp_shortcut_purge(struct nhrp_shortcut *s, int force)
+{
+ THREAD_OFF(s->t_timer);
+ nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid);
+
+ if (force) {
+ /* Immediate purge on route with draw or pending shortcut */
+ thread_add_timer_msec(master, nhrp_shortcut_do_purge, s, 5,
+ &s->t_timer);
+ } else {
+ /* Soft expire - force immediate renewal, but purge
+ * in few seconds to make sure stale route is not
+ * used too long. In practice most purges are caused
+ * by hub bgp change, but target usually stays same.
+ * This allows to keep nhrp route up, and to not
+ * cause temporary rerouting via hubs causing latency
+ * jitter. */
+ thread_add_timer_msec(master, nhrp_shortcut_do_purge, s, 3000,
+ &s->t_timer);
+ s->expiring = 1;
+ nhrp_shortcut_check_use(s);
+ }
+}
+
+static void nhrp_shortcut_purge_prefix(struct nhrp_shortcut *s, void *ctx)
+{
+ struct purge_ctx *pctx = ctx;
+
+ if (prefix_match(pctx->p, s->p))
+ nhrp_shortcut_purge(s, pctx->deleted || !s->cache);
+}
+
+void nhrp_shortcut_prefix_change(const struct prefix *p, int deleted)
+{
+ struct purge_ctx pctx = {
+ .p = p, .deleted = deleted,
+ };
+ nhrp_shortcut_foreach(family2afi(PREFIX_FAMILY(p)),
+ nhrp_shortcut_purge_prefix, &pctx);
+}
diff --git a/nhrpd/nhrp_vc.c b/nhrpd/nhrp_vc.c
new file mode 100644
index 0000000..4b45389
--- /dev/null
+++ b/nhrpd/nhrp_vc.c
@@ -0,0 +1,221 @@
+/* NHRP virtual connection
+ * Copyright (c) 2014-2015 Timo Teräs
+ *
+ * This file is free software: you may copy, redistribute and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include "zebra.h"
+#include "memory.h"
+#include "stream.h"
+#include "hash.h"
+#include "thread.h"
+#include "jhash.h"
+
+#include "nhrpd.h"
+#include "os.h"
+
+DEFINE_MTYPE_STATIC(NHRPD, NHRP_VC, "NHRP virtual connection");
+
+PREDECL_DLIST(childlist);
+
+struct child_sa {
+ uint32_t id;
+ struct nhrp_vc *vc;
+ struct childlist_item childlist_entry;
+};
+
+DECLARE_DLIST(childlist, struct child_sa, childlist_entry);
+
+static struct hash *nhrp_vc_hash;
+static struct childlist_head childlist_head[512];
+
+static unsigned int nhrp_vc_key(const void *peer_data)
+{
+ const struct nhrp_vc *vc = peer_data;
+ return jhash_2words(sockunion_hash(&vc->local.nbma),
+ sockunion_hash(&vc->remote.nbma), 0);
+}
+
+static bool nhrp_vc_cmp(const void *cache_data, const void *key_data)
+{
+ const struct nhrp_vc *a = cache_data;
+ const struct nhrp_vc *b = key_data;
+
+ return sockunion_same(&a->local.nbma, &b->local.nbma)
+ && sockunion_same(&a->remote.nbma, &b->remote.nbma);
+}
+
+static void *nhrp_vc_alloc(void *data)
+{
+ struct nhrp_vc *vc, *key = data;
+
+ vc = XMALLOC(MTYPE_NHRP_VC, sizeof(struct nhrp_vc));
+
+ *vc = (struct nhrp_vc){
+ .local.nbma = key->local.nbma,
+ .remote.nbma = key->remote.nbma,
+ .notifier_list =
+ NOTIFIER_LIST_INITIALIZER(&vc->notifier_list),
+ };
+
+ return vc;
+}
+
+static void nhrp_vc_free(void *data)
+{
+ XFREE(MTYPE_NHRP_VC, data);
+}
+
+struct nhrp_vc *nhrp_vc_get(const union sockunion *src,
+ const union sockunion *dst, int create)
+{
+ struct nhrp_vc key;
+ key.local.nbma = *src;
+ key.remote.nbma = *dst;
+ return hash_get(nhrp_vc_hash, &key, create ? nhrp_vc_alloc : 0);
+}
+
+static void nhrp_vc_check_delete(struct nhrp_vc *vc)
+{
+ if (vc->updating || vc->ipsec || notifier_active(&vc->notifier_list))
+ return;
+ hash_release(nhrp_vc_hash, vc);
+ nhrp_vc_free(vc);
+}
+
+static void nhrp_vc_update(struct nhrp_vc *vc, long cmd)
+{
+ vc->updating = 1;
+ notifier_call(&vc->notifier_list, cmd);
+ vc->updating = 0;
+ nhrp_vc_check_delete(vc);
+}
+
+static void nhrp_vc_ipsec_reset(struct nhrp_vc *vc)
+{
+ vc->local.id[0] = 0;
+ vc->local.certlen = 0;
+ vc->remote.id[0] = 0;
+ vc->remote.certlen = 0;
+}
+
+int nhrp_vc_ipsec_updown(uint32_t child_id, struct nhrp_vc *vc)
+{
+ struct child_sa *sa = NULL, *lsa;
+ uint32_t child_hash = child_id % array_size(childlist_head);
+ int abort_migration = 0;
+
+ frr_each (childlist, &childlist_head[child_hash], lsa) {
+ if (lsa->id == child_id) {
+ sa = lsa;
+ break;
+ }
+ }
+
+ if (!sa) {
+ if (!vc)
+ return 0;
+
+ sa = XMALLOC(MTYPE_NHRP_VC, sizeof(struct child_sa));
+
+ *sa = (struct child_sa){
+ .id = child_id,
+ .vc = NULL,
+ };
+ childlist_add_tail(&childlist_head[child_hash], sa);
+ }
+
+ if (sa->vc == vc)
+ return 0;
+
+ if (vc) {
+ /* Attach first to new VC */
+ vc->ipsec++;
+ nhrp_vc_update(vc, NOTIFY_VC_IPSEC_CHANGED);
+ }
+ if (sa->vc && vc) {
+ /* Notify old VC of migration */
+ sa->vc->abort_migration = 0;
+ debugf(NHRP_DEBUG_COMMON, "IPsec NBMA change of %pSU to %pSU",
+ &sa->vc->remote.nbma, &vc->remote.nbma);
+ nhrp_vc_update(sa->vc, NOTIFY_VC_IPSEC_UPDATE_NBMA);
+ abort_migration = sa->vc->abort_migration;
+ }
+ if (sa->vc) {
+ /* Deattach old VC */
+ sa->vc->ipsec--;
+ if (!sa->vc->ipsec)
+ nhrp_vc_ipsec_reset(sa->vc);
+ nhrp_vc_update(sa->vc, NOTIFY_VC_IPSEC_CHANGED);
+ }
+
+ /* Update */
+ sa->vc = vc;
+ if (!vc) {
+ childlist_del(&childlist_head[child_hash], sa);
+ XFREE(MTYPE_NHRP_VC, sa);
+ }
+
+ return abort_migration;
+}
+
+void nhrp_vc_notify_add(struct nhrp_vc *vc, struct notifier_block *n,
+ notifier_fn_t action)
+{
+ notifier_add(n, &vc->notifier_list, action);
+}
+
+void nhrp_vc_notify_del(struct nhrp_vc *vc, struct notifier_block *n)
+{
+ notifier_del(n, &vc->notifier_list);
+ nhrp_vc_check_delete(vc);
+}
+
+
+struct nhrp_vc_iterator_ctx {
+ void (*cb)(struct nhrp_vc *, void *);
+ void *ctx;
+};
+
+static void nhrp_vc_iterator(struct hash_bucket *b, void *ctx)
+{
+ struct nhrp_vc_iterator_ctx *ic = ctx;
+ ic->cb(b->data, ic->ctx);
+}
+
+void nhrp_vc_foreach(void (*cb)(struct nhrp_vc *, void *), void *ctx)
+{
+ struct nhrp_vc_iterator_ctx ic = {
+ .cb = cb, .ctx = ctx,
+ };
+ hash_iterate(nhrp_vc_hash, nhrp_vc_iterator, &ic);
+}
+
+void nhrp_vc_init(void)
+{
+ size_t i;
+
+ nhrp_vc_hash = hash_create(nhrp_vc_key, nhrp_vc_cmp, "NHRP VC hash");
+ for (i = 0; i < array_size(childlist_head); i++)
+ childlist_init(&childlist_head[i]);
+}
+
+void nhrp_vc_reset(void)
+{
+ struct child_sa *sa;
+ size_t i;
+
+ for (i = 0; i < array_size(childlist_head); i++) {
+ frr_each_safe (childlist, &childlist_head[i], sa)
+ nhrp_vc_ipsec_updown(sa->id, 0);
+ }
+}
+
+void nhrp_vc_terminate(void)
+{
+ nhrp_vc_reset();
+ hash_clean(nhrp_vc_hash, nhrp_vc_free);
+}
diff --git a/nhrpd/nhrp_vty.c b/nhrpd/nhrp_vty.c
new file mode 100644
index 0000000..3a8baa2
--- /dev/null
+++ b/nhrpd/nhrp_vty.c
@@ -0,0 +1,1273 @@
+/* NHRP vty handling
+ * Copyright (c) 2014-2015 Timo Teräs
+ *
+ * This file is free software: you may copy, redistribute and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include "zebra.h"
+#include "command.h"
+#include "zclient.h"
+#include "stream.h"
+#include "filter.h"
+#include "json.h"
+
+#include "nhrpd.h"
+#include "netlink.h"
+
+static int nhrp_config_write(struct vty *vty);
+static struct cmd_node zebra_node = {
+ .name = "zebra",
+ .node = ZEBRA_NODE,
+ .parent_node = CONFIG_NODE,
+ .prompt = "%s(config-router)# ",
+ .config_write = nhrp_config_write,
+};
+
+#define NHRP_DEBUG_FLAGS_CMD "<all|common|event|interface|kernel|route|vici>"
+
+#define NHRP_DEBUG_FLAGS_STR \
+ "All messages\n" \
+ "Common messages (default)\n" \
+ "Event manager messages\n" \
+ "Interface messages\n" \
+ "Kernel messages\n" \
+ "Route messages\n" \
+ "VICI messages\n"
+
+static const struct message debug_flags_desc[] = {
+ {NHRP_DEBUG_ALL, "all"}, {NHRP_DEBUG_COMMON, "common"},
+ {NHRP_DEBUG_IF, "interface"}, {NHRP_DEBUG_KERNEL, "kernel"},
+ {NHRP_DEBUG_ROUTE, "route"}, {NHRP_DEBUG_VICI, "vici"},
+ {NHRP_DEBUG_EVENT, "event"}, {0}};
+
+static const struct message interface_flags_desc[] = {
+ {NHRP_IFF_SHORTCUT, "shortcut"},
+ {NHRP_IFF_REDIRECT, "redirect"},
+ {NHRP_IFF_REG_NO_UNIQUE, "registration no-unique"},
+ {0}};
+
+static int nhrp_vty_return(struct vty *vty, int ret)
+{
+ static const char *const errmsgs[] = {
+ [NHRP_ERR_FAIL] = "Command failed",
+ [NHRP_ERR_NO_MEMORY] = "Out of memory",
+ [NHRP_ERR_UNSUPPORTED_INTERFACE] =
+ "NHRP not supported on this interface",
+ [NHRP_ERR_NHRP_NOT_ENABLED] =
+ "NHRP not enabled (set 'nhrp network-id' first)",
+ [NHRP_ERR_ENTRY_EXISTS] = "Entry exists already",
+ [NHRP_ERR_ENTRY_NOT_FOUND] = "Entry not found",
+ [NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH] =
+ "Protocol address family does not match command (ip/ipv6 mismatch)",
+ };
+ const char *str = NULL;
+ char buf[256];
+
+ if (ret == NHRP_OK)
+ return CMD_SUCCESS;
+
+ if (ret > 0 && ret <= NHRP_ERR_MAX)
+ if (errmsgs[ret])
+ str = errmsgs[ret];
+
+ if (!str) {
+ str = buf;
+ snprintf(buf, sizeof(buf), "Unknown error %d", ret);
+ }
+
+ vty_out(vty, "%% %s\n", str);
+
+ return CMD_WARNING_CONFIG_FAILED;
+ ;
+}
+
+static int toggle_flag(struct vty *vty, const struct message *flag_desc,
+ const char *name, int on_off, unsigned *flags)
+{
+ int i;
+
+ for (i = 0; flag_desc[i].str != NULL; i++) {
+ if (strcmp(flag_desc[i].str, name) != 0)
+ continue;
+ if (on_off)
+ *flags |= flag_desc[i].key;
+ else
+ *flags &= ~flag_desc[i].key;
+ return CMD_SUCCESS;
+ }
+
+ vty_out(vty, "%% Invalid value %s\n", name);
+ return CMD_WARNING_CONFIG_FAILED;
+ ;
+}
+
+#ifndef NO_DEBUG
+
+DEFUN_NOSH(show_debugging_nhrp, show_debugging_nhrp_cmd,
+ "show debugging [nhrp]",
+ SHOW_STR
+ "Debugging information\n"
+ "NHRP configuration\n")
+{
+ int i;
+
+ vty_out(vty, "NHRP debugging status:\n");
+
+ for (i = 0; debug_flags_desc[i].str != NULL; i++) {
+ if (debug_flags_desc[i].key == NHRP_DEBUG_ALL)
+ continue;
+ if (!(debug_flags_desc[i].key & debug_flags))
+ continue;
+
+ vty_out(vty, " NHRP %s debugging is on\n",
+ debug_flags_desc[i].str);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(debug_nhrp, debug_nhrp_cmd,
+ "debug nhrp " NHRP_DEBUG_FLAGS_CMD,
+ "Enable debug messages for specific or all parts.\n"
+ "NHRP information\n"
+ NHRP_DEBUG_FLAGS_STR)
+{
+ return toggle_flag(vty, debug_flags_desc, argv[2]->text, 1,
+ &debug_flags);
+}
+
+DEFUN(no_debug_nhrp, no_debug_nhrp_cmd,
+ "no debug nhrp " NHRP_DEBUG_FLAGS_CMD,
+ NO_STR
+ "Disable debug messages for specific or all parts.\n"
+ "NHRP information\n"
+ NHRP_DEBUG_FLAGS_STR)
+{
+ return toggle_flag(vty, debug_flags_desc, argv[3]->text, 0,
+ &debug_flags);
+}
+
+#endif /* NO_DEBUG */
+
+static int nhrp_config_write(struct vty *vty)
+{
+#ifndef NO_DEBUG
+ if (debug_flags == NHRP_DEBUG_ALL) {
+ vty_out(vty, "debug nhrp all\n");
+ } else {
+ int i;
+
+ for (i = 0; debug_flags_desc[i].str != NULL; i++) {
+ if (debug_flags_desc[i].key == NHRP_DEBUG_ALL)
+ continue;
+ if (!(debug_flags & debug_flags_desc[i].key))
+ continue;
+ vty_out(vty, "debug nhrp %s\n",
+ debug_flags_desc[i].str);
+ }
+ }
+ vty_out(vty, "!\n");
+#endif /* NO_DEBUG */
+
+ if (nhrp_event_socket_path) {
+ vty_out(vty, "nhrp event socket %s\n", nhrp_event_socket_path);
+ }
+ if (netlink_nflog_group) {
+ vty_out(vty, "nhrp nflog-group %d\n", netlink_nflog_group);
+ }
+ if (netlink_mcast_nflog_group)
+ vty_out(vty, "nhrp multicast-nflog-group %d\n",
+ netlink_mcast_nflog_group);
+
+ return 0;
+}
+
+#define IP_STR "IP information\n"
+#define IPV6_STR "IPv6 information\n"
+#define AFI_CMD "<ip|ipv6>"
+#define AFI_STR IP_STR IPV6_STR
+#define NHRP_STR "Next Hop Resolution Protocol functions\n"
+
+static afi_t cmd_to_afi(const struct cmd_token *tok)
+{
+ return strcmp(tok->text, "ipv6") == 0 ? AFI_IP6 : AFI_IP;
+}
+
+static const char *afi_to_cmd(afi_t afi)
+{
+ if (afi == AFI_IP6)
+ return "ipv6";
+ return "ip";
+}
+
+DEFUN(nhrp_event_socket, nhrp_event_socket_cmd,
+ "nhrp event socket SOCKET",
+ NHRP_STR
+ "Event Manager commands\n"
+ "Event Manager unix socket path\n"
+ "Unix path for the socket\n")
+{
+ evmgr_set_socket(argv[3]->arg);
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_nhrp_event_socket, no_nhrp_event_socket_cmd,
+ "no nhrp event socket [SOCKET]",
+ NO_STR
+ NHRP_STR
+ "Event Manager commands\n"
+ "Event Manager unix socket path\n"
+ "Unix path for the socket\n")
+{
+ evmgr_set_socket(NULL);
+ return CMD_SUCCESS;
+}
+
+DEFUN(nhrp_nflog_group, nhrp_nflog_group_cmd,
+ "nhrp nflog-group (1-65535)",
+ NHRP_STR
+ "Specify NFLOG group number\n"
+ "NFLOG group number\n")
+{
+ uint32_t nfgroup;
+
+ nfgroup = strtoul(argv[2]->arg, NULL, 10);
+ netlink_set_nflog_group(nfgroup);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_nhrp_nflog_group, no_nhrp_nflog_group_cmd,
+ "no nhrp nflog-group [(1-65535)]",
+ NO_STR
+ NHRP_STR
+ "Specify NFLOG group number\n"
+ "NFLOG group number\n")
+{
+ netlink_set_nflog_group(0);
+ return CMD_SUCCESS;
+}
+
+DEFUN(nhrp_multicast_nflog_group, nhrp_multicast_nflog_group_cmd,
+ "nhrp multicast-nflog-group (1-65535)",
+ NHRP_STR
+ "Specify NFLOG group number for Multicast Packets\n"
+ "NFLOG group number\n")
+{
+ uint32_t nfgroup;
+
+ nfgroup = strtoul(argv[2]->arg, NULL, 10);
+ netlink_mcast_set_nflog_group(nfgroup);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_nhrp_multicast_nflog_group, no_nhrp_multicast_nflog_group_cmd,
+ "no nhrp multicast-nflog-group [(1-65535)]",
+ NO_STR
+ NHRP_STR
+ "Specify NFLOG group number\n"
+ "NFLOG group number\n")
+{
+ netlink_mcast_set_nflog_group(0);
+ return CMD_SUCCESS;
+}
+
+DEFUN(tunnel_protection, tunnel_protection_cmd,
+ "tunnel protection vici profile PROFILE [fallback-profile FALLBACK]",
+ "NHRP/GRE integration\n"
+ "IPsec protection\n"
+ "VICI (StrongSwan)\n"
+ "IPsec profile\n"
+ "IPsec profile name\n"
+ "Fallback IPsec profile\n"
+ "Fallback IPsec profile name\n")
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+
+ nhrp_interface_set_protection(ifp, argv[4]->arg,
+ argc > 6 ? argv[6]->arg : NULL);
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_tunnel_protection, no_tunnel_protection_cmd,
+ "no tunnel protection",
+ NO_STR
+ "NHRP/GRE integration\n"
+ "IPsec protection\n")
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+
+ nhrp_interface_set_protection(ifp, NULL, NULL);
+ return CMD_SUCCESS;
+}
+
+DEFUN(tunnel_source, tunnel_source_cmd,
+ "tunnel source INTERFACE",
+ "NHRP/GRE integration\n"
+ "Tunnel device binding tracking\n"
+ "Interface name\n")
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+ nhrp_interface_set_source(ifp, argv[2]->arg);
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_tunnel_source, no_tunnel_source_cmd,
+ "no tunnel source",
+ "NHRP/GRE integration\n"
+ "Tunnel device binding tracking\n"
+ "Interface name\n")
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+ nhrp_interface_set_source(ifp, NULL);
+ return CMD_SUCCESS;
+}
+
+DEFUN(if_nhrp_network_id, if_nhrp_network_id_cmd,
+ AFI_CMD " nhrp network-id (1-4294967295)",
+ AFI_STR
+ NHRP_STR
+ "Enable NHRP and specify network-id\n"
+ "System local ID to specify interface group\n")
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ nifp->afi[afi].network_id = strtoul(argv[3]->arg, NULL, 10);
+ nhrp_interface_update(ifp);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(if_no_nhrp_network_id, if_no_nhrp_network_id_cmd,
+ "no " AFI_CMD " nhrp network-id [(1-4294967295)]",
+ NO_STR
+ AFI_STR
+ NHRP_STR
+ "Enable NHRP and specify network-id\n"
+ "System local ID to specify interface group\n")
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[1]);
+
+ nifp->afi[afi].network_id = 0;
+ nhrp_interface_update(ifp);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(if_nhrp_flags, if_nhrp_flags_cmd,
+ AFI_CMD " nhrp <shortcut|redirect>",
+ AFI_STR
+ NHRP_STR
+ "Allow shortcut establishment\n"
+ "Send redirect notifications\n")
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ return toggle_flag(vty, interface_flags_desc, argv[2]->text, 1,
+ &nifp->afi[afi].flags);
+}
+
+DEFUN(if_no_nhrp_flags, if_no_nhrp_flags_cmd,
+ "no " AFI_CMD " nhrp <shortcut|redirect>",
+ NO_STR
+ AFI_STR
+ NHRP_STR
+ "Allow shortcut establishment\n"
+ "Send redirect notifications\n")
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[1]);
+
+ return toggle_flag(vty, interface_flags_desc, argv[3]->text, 0,
+ &nifp->afi[afi].flags);
+}
+
+DEFUN(if_nhrp_reg_flags, if_nhrp_reg_flags_cmd,
+ AFI_CMD " nhrp registration no-unique",
+ AFI_STR
+ NHRP_STR
+ "Registration configuration\n"
+ "Don't set unique flag\n")
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+ char name[256];
+ snprintf(name, sizeof(name), "registration %s", argv[3]->text);
+ return toggle_flag(vty, interface_flags_desc, name, 1,
+ &nifp->afi[afi].flags);
+}
+
+DEFUN(if_no_nhrp_reg_flags, if_no_nhrp_reg_flags_cmd,
+ "no " AFI_CMD " nhrp registration no-unique",
+ NO_STR
+ AFI_STR
+ NHRP_STR
+ "Registration configuration\n"
+ "Don't set unique flag\n")
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[1]);
+ char name[256];
+ snprintf(name, sizeof(name), "registration %s", argv[4]->text);
+ return toggle_flag(vty, interface_flags_desc, name, 0,
+ &nifp->afi[afi].flags);
+}
+
+DEFUN(if_nhrp_holdtime, if_nhrp_holdtime_cmd,
+ AFI_CMD " nhrp holdtime (1-65000)",
+ AFI_STR
+ NHRP_STR
+ "Specify NBMA address validity time\n"
+ "Time in seconds that NBMA addresses are advertised valid\n")
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ nifp->afi[afi].holdtime = strtoul(argv[3]->arg, NULL, 10);
+ nhrp_interface_update(ifp);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(if_no_nhrp_holdtime, if_no_nhrp_holdtime_cmd,
+ "no " AFI_CMD " nhrp holdtime [(1-65000)]",
+ NO_STR
+ AFI_STR
+ NHRP_STR
+ "Specify NBMA address validity time\n"
+ "Time in seconds that NBMA addresses are advertised valid\n")
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[1]);
+
+ nifp->afi[afi].holdtime = NHRPD_DEFAULT_HOLDTIME;
+ nhrp_interface_update(ifp);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(if_nhrp_mtu, if_nhrp_mtu_cmd,
+ "ip nhrp mtu <(576-1500)|opennhrp>",
+ IP_STR
+ NHRP_STR
+ "Configure NHRP advertised MTU\n"
+ "MTU value\n"
+ "Advertise bound interface MTU similar to OpenNHRP\n")
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+ struct nhrp_interface *nifp = ifp->info;
+
+ if (argv[3]->arg[0] == 'o') {
+ nifp->afi[AFI_IP].configured_mtu = -1;
+ } else {
+ nifp->afi[AFI_IP].configured_mtu =
+ strtoul(argv[3]->arg, NULL, 10);
+ }
+ nhrp_interface_update_mtu(ifp, AFI_IP);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(if_no_nhrp_mtu, if_no_nhrp_mtu_cmd,
+ "no ip nhrp mtu [(576-1500)|opennhrp]",
+ NO_STR
+ IP_STR
+ NHRP_STR
+ "Configure NHRP advertised MTU\n"
+ "MTU value\n"
+ "Advertise bound interface MTU similar to OpenNHRP\n")
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+ struct nhrp_interface *nifp = ifp->info;
+
+ nifp->afi[AFI_IP].configured_mtu = 0;
+ nhrp_interface_update_mtu(ifp, AFI_IP);
+ return CMD_SUCCESS;
+}
+
+DEFUN(if_nhrp_map, if_nhrp_map_cmd,
+ AFI_CMD " nhrp map <A.B.C.D|X:X::X:X> <A.B.C.D|local>",
+ AFI_STR
+ NHRP_STR
+ "Nexthop Server configuration\n"
+ "IPv4 protocol address\n"
+ "IPv6 protocol address\n"
+ "IPv4 NBMA address\n"
+ "Handle protocol address locally\n")
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+ afi_t afi = cmd_to_afi(argv[0]);
+ union sockunion proto_addr, nbma_addr;
+ struct nhrp_cache_config *cc;
+ struct nhrp_cache *c;
+ enum nhrp_cache_type type;
+
+ if (str2sockunion(argv[3]->arg, &proto_addr) < 0
+ || afi2family(afi) != sockunion_family(&proto_addr))
+ return nhrp_vty_return(vty, NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH);
+
+ if (strmatch(argv[4]->text, "local"))
+ type = NHRP_CACHE_LOCAL;
+ else {
+ if (str2sockunion(argv[4]->arg, &nbma_addr) < 0)
+ return nhrp_vty_return(vty, NHRP_ERR_FAIL);
+ type = NHRP_CACHE_STATIC;
+ }
+ cc = nhrp_cache_config_get(ifp, &proto_addr, 1);
+ if (!cc)
+ return nhrp_vty_return(vty, NHRP_ERR_FAIL);
+ cc->nbma = nbma_addr;
+ cc->type = type;
+ /* gre layer not ready */
+ if (ifp->ifindex == IFINDEX_INTERNAL)
+ return CMD_SUCCESS;
+
+ c = nhrp_cache_get(ifp, &proto_addr, 1);
+ if (!c)
+ return nhrp_vty_return(vty, NHRP_ERR_FAIL);
+
+ c->map = 1;
+ if (type == NHRP_CACHE_LOCAL)
+ nhrp_cache_update_binding(c, NHRP_CACHE_LOCAL, 0, NULL, 0,
+ NULL, NULL);
+ else
+ nhrp_cache_update_binding(c, NHRP_CACHE_STATIC, 0,
+ nhrp_peer_get(ifp, &nbma_addr), 0,
+ NULL, NULL);
+ return CMD_SUCCESS;
+}
+
+DEFUN(if_no_nhrp_map, if_no_nhrp_map_cmd,
+ "no " AFI_CMD " nhrp map <A.B.C.D|X:X::X:X> [<A.B.C.D|local>]",
+ NO_STR
+ AFI_STR
+ NHRP_STR
+ "Nexthop Server configuration\n"
+ "IPv4 protocol address\n"
+ "IPv6 protocol address\n"
+ "IPv4 NBMA address\n"
+ "Handle protocol address locally\n")
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+ afi_t afi = cmd_to_afi(argv[1]);
+ union sockunion proto_addr, nbma_addr;
+ struct nhrp_cache_config *cc;
+ struct nhrp_cache *c;
+
+ if (str2sockunion(argv[4]->arg, &proto_addr) < 0
+ || afi2family(afi) != sockunion_family(&proto_addr))
+ return nhrp_vty_return(vty, NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH);
+
+ cc = nhrp_cache_config_get(ifp, &proto_addr, 0);
+ if (!cc)
+ return nhrp_vty_return(vty, NHRP_ERR_FAIL);
+ nhrp_cache_config_free(cc);
+
+ c = nhrp_cache_get(ifp, &proto_addr, 0);
+ /* silently return */
+ if (!c || !c->map)
+ return CMD_SUCCESS;
+
+ nhrp_cache_update_binding(c, c->cur.type, -1,
+ nhrp_peer_get(ifp, &nbma_addr), 0, NULL,
+ NULL);
+ return CMD_SUCCESS;
+}
+
+DEFUN(if_nhrp_map_multicast, if_nhrp_map_multicast_cmd,
+ AFI_CMD " nhrp map multicast <A.B.C.D|X:X::X:X|dynamic>",
+ AFI_STR
+ NHRP_STR
+ "Multicast NBMA Configuration\n"
+ "Use this NBMA mapping for multicasts\n"
+ "IPv4 NBMA address\n"
+ "IPv6 NBMA address\n"
+ "Dynamically learn destinations from client registrations on hub\n")
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+ afi_t afi = cmd_to_afi(argv[0]);
+ union sockunion nbma_addr;
+ int ret;
+
+ if (str2sockunion(argv[4]->arg, &nbma_addr) < 0)
+ sockunion_family(&nbma_addr) = AF_UNSPEC;
+
+ ret = nhrp_multicast_add(ifp, afi, &nbma_addr);
+
+ return nhrp_vty_return(vty, ret);
+}
+
+DEFUN(if_no_nhrp_map_multicast, if_no_nhrp_map_multicast_cmd,
+ "no " AFI_CMD " nhrp map multicast <A.B.C.D|X:X::X:X|dynamic>",
+ NO_STR
+ AFI_STR
+ NHRP_STR
+ "Multicast NBMA Configuration\n"
+ "Use this NBMA mapping for multicasts\n"
+ "IPv4 NBMA address\n"
+ "IPv6 NBMA address\n"
+ "Dynamically learn destinations from client registrations on hub\n")
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+ afi_t afi = cmd_to_afi(argv[1]);
+ union sockunion nbma_addr;
+ int ret;
+
+ if (str2sockunion(argv[5]->arg, &nbma_addr) < 0)
+ sockunion_family(&nbma_addr) = AF_UNSPEC;
+
+ ret = nhrp_multicast_del(ifp, afi, &nbma_addr);
+
+ return nhrp_vty_return(vty, ret);
+}
+
+DEFUN(if_nhrp_nhs, if_nhrp_nhs_cmd,
+ AFI_CMD " nhrp nhs <A.B.C.D|X:X::X:X|dynamic> nbma <A.B.C.D|FQDN>",
+ AFI_STR
+ NHRP_STR
+ "Nexthop Server configuration\n"
+ "IPv4 protocol address\n"
+ "IPv6 protocol address\n"
+ "Automatic detection of protocol address\n"
+ "NBMA address\n"
+ "IPv4 NBMA address\n"
+ "Fully qualified domain name for NBMA address(es)\n")
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+ afi_t afi = cmd_to_afi(argv[0]);
+ union sockunion proto_addr;
+ int ret;
+
+ if (str2sockunion(argv[3]->arg, &proto_addr) < 0)
+ sockunion_family(&proto_addr) = AF_UNSPEC;
+
+ ret = nhrp_nhs_add(ifp, afi, &proto_addr, argv[5]->arg);
+ return nhrp_vty_return(vty, ret);
+}
+
+DEFUN(if_no_nhrp_nhs, if_no_nhrp_nhs_cmd,
+ "no " AFI_CMD " nhrp nhs <A.B.C.D|X:X::X:X|dynamic> nbma <A.B.C.D|FQDN>",
+ NO_STR
+ AFI_STR
+ NHRP_STR
+ "Nexthop Server configuration\n"
+ "IPv4 protocol address\n"
+ "IPv6 protocol address\n"
+ "Automatic detection of protocol address\n"
+ "NBMA address\n"
+ "IPv4 NBMA address\n"
+ "Fully qualified domain name for NBMA address(es)\n")
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+ afi_t afi = cmd_to_afi(argv[1]);
+ union sockunion proto_addr;
+ int ret;
+
+ if (str2sockunion(argv[4]->arg, &proto_addr) < 0)
+ sockunion_family(&proto_addr) = AF_UNSPEC;
+
+ ret = nhrp_nhs_del(ifp, afi, &proto_addr, argv[6]->arg);
+ return nhrp_vty_return(vty, ret);
+}
+
+struct info_ctx {
+ struct vty *vty;
+ afi_t afi;
+ int count;
+ struct json_object *json;
+};
+
+static void show_ip_nhrp_cache(struct nhrp_cache *c, void *pctx)
+{
+ struct info_ctx *ctx = pctx;
+ struct vty *vty = ctx->vty;
+ char buf[3][SU_ADDRSTRLEN];
+ struct json_object *json = NULL;
+
+ if (ctx->afi != family2afi(sockunion_family(&c->remote_addr)))
+ return;
+
+
+ if (!ctx->count && !ctx->json) {
+ vty_out(vty, "%-8s %-8s %-24s %-24s %-24s %-6s %s\n", "Iface",
+ "Type", "Protocol", "NBMA", "Claimed NBMA", "Flags",
+ "Identity");
+ }
+ ctx->count++;
+
+ sockunion2str(&c->remote_addr, buf[0], sizeof(buf[0]));
+ if (c->cur.type == NHRP_CACHE_LOCAL) {
+ struct nhrp_interface *nifp = c->ifp->info;
+
+ if (sockunion_family(&nifp->nbma) != AF_UNSPEC) {
+ sockunion2str(&nifp->nbma, buf[1], sizeof(buf[1]));
+ sockunion2str(&nifp->nbma, buf[2], sizeof(buf[2]));
+ } else {
+ snprintf(buf[1], sizeof(buf[1]), "-");
+ snprintf(buf[2], sizeof(buf[2]), "-");
+ }
+
+ /* if we are behind NAT then update NBMA field */
+ if (sockunion_family(&nifp->nat_nbma) != AF_UNSPEC)
+ sockunion2str(&nifp->nat_nbma, buf[1], sizeof(buf[1]));
+ } else {
+ if (c->cur.peer)
+ sockunion2str(&c->cur.peer->vc->remote.nbma,
+ buf[1], sizeof(buf[1]));
+ else
+ snprintf(buf[1], sizeof(buf[1]), "-");
+
+ if (c->cur.peer
+ && sockunion_family(&c->cur.remote_nbma_claimed)
+ != AF_UNSPEC)
+ sockunion2str(&c->cur.remote_nbma_claimed,
+ buf[2], sizeof(buf[2]));
+ else
+ snprintf(buf[2], sizeof(buf[2]), "-");
+ }
+
+ if (ctx->json) {
+ json = json_object_new_object();
+ json_object_string_add(json, "interface", c->ifp->name);
+ json_object_string_add(json, "type",
+ nhrp_cache_type_str[c->cur.type]);
+ json_object_string_add(json, "protocol", buf[0]);
+ json_object_string_add(json, "nbma", buf[1]);
+ json_object_string_add(json, "claimed_nbma", buf[2]);
+
+ if (c->used)
+ json_object_boolean_true_add(json, "used");
+ else
+ json_object_boolean_false_add(json, "used");
+
+ if (c->t_timeout)
+ json_object_boolean_true_add(json, "timeout");
+ else
+ json_object_boolean_false_add(json, "timeout");
+
+ if (c->t_auth)
+ json_object_boolean_true_add(json, "auth");
+ else
+ json_object_boolean_false_add(json, "auth");
+
+ if (c->cur.peer)
+ json_object_string_add(json, "identity",
+ c->cur.peer->vc->remote.id);
+ else
+ json_object_string_add(json, "identity", "-");
+
+ json_object_array_add(ctx->json, json);
+ return;
+ }
+ vty_out(ctx->vty, "%-8s %-8s %-24s %-24s %-24s %c%c%c %s\n",
+ c->ifp->name,
+ nhrp_cache_type_str[c->cur.type],
+ buf[0], buf[1], buf[2],
+ c->used ? 'U' : ' ', c->t_timeout ? 'T' : ' ',
+ c->t_auth ? 'A' : ' ',
+ c->cur.peer ? c->cur.peer->vc->remote.id : "-");
+}
+
+static void show_ip_nhrp_nhs(struct nhrp_nhs *n, struct nhrp_registration *reg,
+ void *pctx)
+{
+ struct info_ctx *ctx = pctx;
+ struct vty *vty = ctx->vty;
+ char buf[2][SU_ADDRSTRLEN];
+ struct json_object *json = NULL;
+
+ if (!ctx->count && !ctx->json) {
+ vty_out(vty, "%-8s %-24s %-16s %-16s\n", "Iface", "FQDN",
+ "NBMA", "Protocol");
+ }
+ ctx->count++;
+
+ if (reg && reg->peer)
+ sockunion2str(&reg->peer->vc->remote.nbma, buf[0],
+ sizeof(buf[0]));
+ else
+ snprintf(buf[0], sizeof(buf[0]), "-");
+ sockunion2str(reg ? &reg->proto_addr : &n->proto_addr, buf[1],
+ sizeof(buf[1]));
+
+ if (ctx->json) {
+ json = json_object_new_object();
+ json_object_string_add(json, "interface", n->ifp->name);
+ json_object_string_add(json, "fqdn", n->nbma_fqdn);
+ json_object_string_add(json, "nbma", buf[0]);
+ json_object_string_add(json, "protocol", buf[1]);
+
+ json_object_array_add(ctx->json, json);
+ return;
+ }
+
+ vty_out(vty, "%-8s %-24s %-16s %-16s\n", n->ifp->name, n->nbma_fqdn,
+ buf[0], buf[1]);
+}
+
+static void show_ip_nhrp_shortcut(struct nhrp_shortcut *s, void *pctx)
+{
+ struct info_ctx *ctx = pctx;
+ struct nhrp_cache *c;
+ struct vty *vty = ctx->vty;
+ char buf1[PREFIX_STRLEN], buf2[SU_ADDRSTRLEN];
+ struct json_object *json = NULL;
+
+ if (!ctx->count) {
+ vty_out(vty, "%-8s %-24s %-24s %s\n", "Type", "Prefix", "Via",
+ "Identity");
+ }
+ ctx->count++;
+
+ c = s->cache;
+ buf2[0] = '\0';
+ if (c)
+ sockunion2str(&c->remote_addr, buf2, sizeof(buf2));
+ prefix2str(s->p, buf1, sizeof(buf1));
+
+ if (ctx->json) {
+ json = json_object_new_object();
+ json_object_string_add(json, "type",
+ nhrp_cache_type_str[s->type]);
+ json_object_string_add(json, "prefix", buf1);
+
+ if (c)
+ json_object_string_add(json, "via", buf2);
+
+ if (c && c->cur.peer)
+ json_object_string_add(json, "identity",
+ c->cur.peer->vc->remote.id);
+ else
+ json_object_string_add(json, "identity", "");
+
+ json_object_array_add(ctx->json, json);
+ return;
+ }
+
+ vty_out(ctx->vty, "%-8s %-24s %-24s %s\n",
+ nhrp_cache_type_str[s->type],
+ buf1, buf2,
+ (c && c->cur.peer) ? c->cur.peer->vc->remote.id : "");
+}
+
+static void show_ip_opennhrp_cache(struct nhrp_cache *c, void *pctx)
+{
+ struct info_ctx *ctx = pctx;
+ char buf[3][SU_ADDRSTRLEN];
+ struct json_object *json = NULL;
+
+
+ if (ctx->afi != family2afi(sockunion_family(&c->remote_addr)))
+ return;
+
+ sockunion2str(&c->remote_addr, buf[0], sizeof(buf[0]));
+ if (c->cur.peer)
+ sockunion2str(&c->cur.peer->vc->remote.nbma, buf[1],
+ sizeof(buf[1]));
+ if (sockunion_family(&c->cur.remote_nbma_natoa) != AF_UNSPEC)
+ sockunion2str(&c->cur.remote_nbma_natoa, buf[2],
+ sizeof(buf[2]));
+ if (ctx->json) {
+ json = json_object_new_object();
+ json_object_string_add(json, "type",
+ nhrp_cache_type_str[c->cur.type]);
+
+ if (c->cur.peer && c->cur.peer->online)
+ json_object_boolean_true_add(json, "up");
+ else
+ json_object_boolean_false_add(json, "up");
+
+ if (c->used)
+ json_object_boolean_true_add(json, "used");
+ else
+ json_object_boolean_false_add(json, "used");
+
+ json_object_string_add(json, "protocolAddress", buf[0]);
+ json_object_int_add(json, "protocolAddressSize",
+ 8 * family2addrsize(sockunion_family
+ (&c->remote_addr)));
+
+ if (c->cur.peer)
+ json_object_string_add(json, "nbmaAddress", buf[1]);
+
+ if (sockunion_family(&c->cur.remote_nbma_natoa) != AF_UNSPEC)
+ json_object_string_add(json, "nbmaNatOaAddress",
+ buf[2]);
+
+ json_object_array_add(ctx->json, json);
+ return;
+ }
+ vty_out(ctx->vty,
+ "Type: %s\n"
+ "Flags:%s%s\n"
+ "Protocol-Address: %s/%zu\n",
+ nhrp_cache_type_str[c->cur.type],
+ (c->cur.peer && c->cur.peer->online) ? " up" : "",
+ c->used ? " used" : "",
+ buf[0],
+ 8 * family2addrsize(sockunion_family(&c->remote_addr)));
+
+ if (c->cur.peer)
+ vty_out(ctx->vty, "NBMA-Address: %s\n", buf[1]);
+
+ if (sockunion_family(&c->cur.remote_nbma_natoa) != AF_UNSPEC)
+ vty_out(ctx->vty, "NBMA-NAT-OA-Address: %s\n", buf[2]);
+
+ vty_out(ctx->vty, "\n\n");
+}
+
+DEFUN(show_ip_nhrp, show_ip_nhrp_cmd,
+ "show " AFI_CMD " nhrp [cache|nhs|shortcut|opennhrp] [json]",
+ SHOW_STR
+ AFI_STR
+ "NHRP information\n"
+ "Forwarding cache information\n"
+ "Next hop server information\n"
+ "Shortcut information\n"
+ "opennhrpctl style cache dump\n"
+ JSON_STR)
+{
+ struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT);
+ struct interface *ifp;
+ struct info_ctx ctx = {
+ .vty = vty, .afi = cmd_to_afi(argv[1]), .json = NULL
+ };
+ bool uj = use_json(argc, argv);
+ struct json_object *json_path = NULL;
+ struct json_object *json_vrf = NULL, *json_vrf_path = NULL;
+ int ret = CMD_SUCCESS;
+
+ if (uj) {
+ json_vrf = json_object_new_object();
+ json_vrf_path = json_object_new_object();
+ json_path = json_object_new_array();
+ ctx.json = json_path;
+ }
+ if (argc <= 3 || argv[3]->text[0] == 'c') {
+ FOR_ALL_INTERFACES (vrf, ifp)
+ nhrp_cache_foreach(ifp, show_ip_nhrp_cache, &ctx);
+ } else if (argv[3]->text[0] == 'n') {
+ FOR_ALL_INTERFACES (vrf, ifp)
+ nhrp_nhs_foreach(ifp, ctx.afi, show_ip_nhrp_nhs, &ctx);
+ } else if (argv[3]->text[0] == 's') {
+ nhrp_shortcut_foreach(ctx.afi, show_ip_nhrp_shortcut, &ctx);
+ } else {
+ if (!ctx.json)
+ vty_out(vty, "Status: ok\n\n");
+ else
+ json_object_string_add(json_vrf, "status", "ok");
+
+ ctx.count++;
+ FOR_ALL_INTERFACES (vrf, ifp)
+ nhrp_cache_foreach(ifp, show_ip_opennhrp_cache, &ctx);
+ }
+
+ if (uj)
+ json_object_int_add(json_vrf, "entriesCount", ctx.count);
+ if (!ctx.count) {
+ if (!ctx.json)
+ vty_out(vty, "%% No entries\n");
+ ret = CMD_WARNING;
+ }
+ if (uj) {
+ json_object_object_add(json_vrf_path, "attr", json_vrf);
+ json_object_object_add(json_vrf_path, "table", ctx.json);
+ vty_json(vty, json_vrf_path);
+ }
+ return ret;
+}
+
+struct dmvpn_cfg {
+ struct vty *vty;
+ struct json_object *json;
+};
+
+static void show_dmvpn_entry(struct nhrp_vc *vc, void *ctx)
+{
+ struct dmvpn_cfg *ctxt = ctx;
+ struct vty *vty;
+ struct json_object *json = NULL;
+
+ if (!ctxt || !ctxt->vty)
+ return;
+ vty = ctxt->vty;
+ if (ctxt->json) {
+ json = json_object_new_object();
+ json_object_string_addf(json, "src", "%pSU", &vc->local.nbma);
+ json_object_string_addf(json, "dst", "%pSU", &vc->remote.nbma);
+
+ if (notifier_active(&vc->notifier_list))
+ json_object_boolean_true_add(json, "notifierActive");
+ else
+ json_object_boolean_false_add(json, "notifierActive");
+
+ json_object_int_add(json, "sas", vc->ipsec);
+ json_object_string_add(json, "identity", vc->remote.id);
+ json_object_array_add(ctxt->json, json);
+ } else {
+ vty_out(vty, "%-24pSU %-24pSU %c %-4d %-24s\n",
+ &vc->local.nbma, &vc->remote.nbma,
+ notifier_active(&vc->notifier_list) ? 'n' : ' ',
+ vc->ipsec, vc->remote.id);
+ }
+}
+
+DEFUN(show_dmvpn, show_dmvpn_cmd,
+ "show dmvpn [json]",
+ SHOW_STR
+ "DMVPN information\n"
+ JSON_STR)
+{
+ bool uj = use_json(argc, argv);
+ struct dmvpn_cfg ctxt;
+ struct json_object *json_path = NULL;
+
+ ctxt.vty = vty;
+ if (!uj) {
+ ctxt.json = NULL;
+ vty_out(vty, "%-24s %-24s %-6s %-4s %-24s\n",
+ "Src", "Dst", "Flags", "SAs", "Identity");
+ } else {
+ json_path = json_object_new_array();
+ ctxt.json = json_path;
+ }
+ nhrp_vc_foreach(show_dmvpn_entry, &ctxt);
+ if (uj)
+ vty_json(vty, json_path);
+ return CMD_SUCCESS;
+}
+
+static void clear_nhrp_cache(struct nhrp_cache *c, void *data)
+{
+ struct info_ctx *ctx = data;
+ if (c->cur.type <= NHRP_CACHE_DYNAMIC) {
+ nhrp_cache_update_binding(c, c->cur.type, -1, NULL, 0, NULL,
+ NULL);
+ if (ctx)
+ ctx->count++;
+ }
+}
+
+static void clear_nhrp_shortcut(struct nhrp_shortcut *s, void *data)
+{
+ struct info_ctx *ctx = data;
+ nhrp_shortcut_purge(s, 1);
+ ctx->count++;
+}
+
+DEFUN(clear_nhrp, clear_nhrp_cmd,
+ "clear " AFI_CMD " nhrp <cache|shortcut>",
+ CLEAR_STR
+ AFI_STR
+ NHRP_STR
+ "Dynamic cache entries\n"
+ "Shortcut entries\n")
+{
+ struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT);
+ struct interface *ifp;
+ struct info_ctx ctx = {
+ .vty = vty, .afi = cmd_to_afi(argv[1]), .count = 0,
+ };
+
+ if (argc <= 3 || argv[3]->text[0] == 'c') {
+ FOR_ALL_INTERFACES (vrf, ifp)
+ nhrp_cache_foreach(ifp, clear_nhrp_cache, &ctx);
+ } else {
+ nhrp_shortcut_foreach(ctx.afi, clear_nhrp_shortcut, &ctx);
+ /* Clear cache also because when a shortcut is cleared then its
+ * cache entry should be cleared as well (otherwise traffic
+ * continues via the shortcut path)
+ */
+ FOR_ALL_INTERFACES (vrf, ifp)
+ nhrp_cache_foreach(ifp, clear_nhrp_cache, NULL);
+ }
+
+ if (!ctx.count) {
+ vty_out(vty, "%% No entries\n");
+ return CMD_WARNING;
+ }
+
+ vty_out(vty, "%% %d entries cleared\n", ctx.count);
+ return CMD_SUCCESS;
+}
+
+struct write_map_ctx {
+ struct vty *vty;
+ int family;
+ const char *aficmd;
+};
+
+static void interface_config_write_nhrp_map(struct nhrp_cache_config *c,
+ void *data)
+{
+ struct write_map_ctx *ctx = data;
+ struct vty *vty = ctx->vty;
+
+ if (sockunion_family(&c->remote_addr) != ctx->family)
+ return;
+
+ vty_out(vty, " %s nhrp map %pSU ", ctx->aficmd, &c->remote_addr);
+ if (c->type == NHRP_CACHE_LOCAL)
+ vty_out(vty, "local\n");
+ else
+ vty_out(vty, "%pSU\n", &c->nbma);
+}
+
+static int interface_config_write(struct vty *vty)
+{
+ struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT);
+ struct write_map_ctx mapctx;
+ struct interface *ifp;
+ struct nhrp_interface *nifp;
+ struct nhrp_nhs *nhs;
+ struct nhrp_multicast *mcast;
+ const char *aficmd;
+ afi_t afi;
+ int i;
+
+ FOR_ALL_INTERFACES (vrf, ifp) {
+ if_vty_config_start(vty, ifp);
+ if (ifp->desc)
+ vty_out(vty, " description %s\n", ifp->desc);
+
+ nifp = ifp->info;
+ if (nifp->ipsec_profile) {
+ vty_out(vty, " tunnel protection vici profile %s",
+ nifp->ipsec_profile);
+ if (nifp->ipsec_fallback_profile)
+ vty_out(vty, " fallback-profile %s",
+ nifp->ipsec_fallback_profile);
+ vty_out(vty, "\n");
+ }
+ if (nifp->source)
+ vty_out(vty, " tunnel source %s\n", nifp->source);
+
+ for (afi = 0; afi < AFI_MAX; afi++) {
+ struct nhrp_afi_data *ad = &nifp->afi[afi];
+
+ aficmd = afi_to_cmd(afi);
+
+ if (ad->network_id)
+ vty_out(vty, " %s nhrp network-id %u\n", aficmd,
+ ad->network_id);
+
+ if (ad->holdtime != NHRPD_DEFAULT_HOLDTIME)
+ vty_out(vty, " %s nhrp holdtime %u\n", aficmd,
+ ad->holdtime);
+
+ if (ad->configured_mtu < 0)
+ vty_out(vty, " %s nhrp mtu opennhrp\n", aficmd);
+ else if (ad->configured_mtu)
+ vty_out(vty, " %s nhrp mtu %u\n", aficmd,
+ ad->configured_mtu);
+
+ for (i = 0; interface_flags_desc[i].str != NULL; i++) {
+ if (!(ad->flags & interface_flags_desc[i].key))
+ continue;
+ vty_out(vty, " %s nhrp %s\n", aficmd,
+ interface_flags_desc[i].str);
+ }
+
+ mapctx = (struct write_map_ctx){
+ .vty = vty,
+ .family = afi2family(afi),
+ .aficmd = aficmd,
+ };
+ nhrp_cache_config_foreach(
+ ifp, interface_config_write_nhrp_map, &mapctx);
+
+ frr_each (nhrp_nhslist, &ad->nhslist_head, nhs) {
+ vty_out(vty, " %s nhrp nhs ", aficmd);
+ if (sockunion_family(&nhs->proto_addr)
+ == AF_UNSPEC)
+ vty_out(vty, "dynamic");
+ else
+ vty_out(vty, "%pSU", &nhs->proto_addr);
+ vty_out(vty, " nbma %s\n", nhs->nbma_fqdn);
+ }
+
+ frr_each (nhrp_mcastlist, &ad->mcastlist_head, mcast) {
+ vty_out(vty, " %s nhrp map multicast ", aficmd);
+ if (sockunion_family(&mcast->nbma_addr)
+ == AF_UNSPEC)
+ vty_out(vty, "dynamic\n");
+ else
+ vty_out(vty, "%pSU\n",
+ &mcast->nbma_addr);
+ }
+ }
+
+ if_vty_config_end(vty);
+ }
+
+ return 0;
+}
+
+void nhrp_config_init(void)
+{
+ install_node(&zebra_node);
+ install_default(ZEBRA_NODE);
+
+ /* access-list commands */
+ access_list_init();
+
+ /* global commands */
+ install_element(VIEW_NODE, &show_ip_nhrp_cmd);
+ install_element(VIEW_NODE, &show_dmvpn_cmd);
+ install_element(ENABLE_NODE, &clear_nhrp_cmd);
+
+ install_element(ENABLE_NODE, &show_debugging_nhrp_cmd);
+
+ install_element(ENABLE_NODE, &debug_nhrp_cmd);
+ install_element(ENABLE_NODE, &no_debug_nhrp_cmd);
+
+ install_element(CONFIG_NODE, &debug_nhrp_cmd);
+ install_element(CONFIG_NODE, &no_debug_nhrp_cmd);
+
+ install_element(CONFIG_NODE, &nhrp_event_socket_cmd);
+ install_element(CONFIG_NODE, &no_nhrp_event_socket_cmd);
+ install_element(CONFIG_NODE, &nhrp_nflog_group_cmd);
+ install_element(CONFIG_NODE, &no_nhrp_nflog_group_cmd);
+ install_element(CONFIG_NODE, &nhrp_multicast_nflog_group_cmd);
+ install_element(CONFIG_NODE, &no_nhrp_multicast_nflog_group_cmd);
+
+ vrf_cmd_init(NULL);
+
+ /* interface specific commands */
+ if_cmd_init(interface_config_write);
+ install_element(INTERFACE_NODE, &tunnel_protection_cmd);
+ install_element(INTERFACE_NODE, &no_tunnel_protection_cmd);
+ install_element(INTERFACE_NODE, &tunnel_source_cmd);
+ install_element(INTERFACE_NODE, &no_tunnel_source_cmd);
+ install_element(INTERFACE_NODE, &if_nhrp_network_id_cmd);
+ install_element(INTERFACE_NODE, &if_no_nhrp_network_id_cmd);
+ install_element(INTERFACE_NODE, &if_nhrp_holdtime_cmd);
+ install_element(INTERFACE_NODE, &if_no_nhrp_holdtime_cmd);
+ install_element(INTERFACE_NODE, &if_nhrp_mtu_cmd);
+ install_element(INTERFACE_NODE, &if_no_nhrp_mtu_cmd);
+ install_element(INTERFACE_NODE, &if_nhrp_flags_cmd);
+ install_element(INTERFACE_NODE, &if_no_nhrp_flags_cmd);
+ install_element(INTERFACE_NODE, &if_nhrp_reg_flags_cmd);
+ install_element(INTERFACE_NODE, &if_no_nhrp_reg_flags_cmd);
+ install_element(INTERFACE_NODE, &if_nhrp_map_cmd);
+ install_element(INTERFACE_NODE, &if_no_nhrp_map_cmd);
+ install_element(INTERFACE_NODE, &if_nhrp_map_multicast_cmd);
+ install_element(INTERFACE_NODE, &if_no_nhrp_map_multicast_cmd);
+ install_element(INTERFACE_NODE, &if_nhrp_nhs_cmd);
+ install_element(INTERFACE_NODE, &if_no_nhrp_nhs_cmd);
+}
diff --git a/nhrpd/nhrpd.h b/nhrpd/nhrpd.h
new file mode 100644
index 0000000..753c6e9
--- /dev/null
+++ b/nhrpd/nhrpd.h
@@ -0,0 +1,536 @@
+/* NHRP daemon internal structures and function prototypes
+ * Copyright (c) 2014-2015 Timo Teräs
+ *
+ * This file is free software: you may copy, redistribute and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef NHRPD_H
+#define NHRPD_H
+
+#include "zbuf.h"
+#include "zclient.h"
+#include "debug.h"
+#include "memory.h"
+#include "resolver.h"
+
+DECLARE_MGROUP(NHRPD);
+
+#define NHRPD_DEFAULT_HOLDTIME 7200
+
+#define NHRP_VTY_PORT 2610
+#define NHRP_DEFAULT_CONFIG "nhrpd.conf"
+
+extern struct thread_master *master;
+
+enum { NHRP_OK = 0,
+ NHRP_ERR_FAIL,
+ NHRP_ERR_NO_MEMORY,
+ NHRP_ERR_UNSUPPORTED_INTERFACE,
+ NHRP_ERR_NHRP_NOT_ENABLED,
+ NHRP_ERR_ENTRY_EXISTS,
+ NHRP_ERR_ENTRY_NOT_FOUND,
+ NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH,
+ __NHRP_ERR_MAX };
+#define NHRP_ERR_MAX (__NHRP_ERR_MAX - 1)
+
+struct notifier_block;
+
+typedef void (*notifier_fn_t)(struct notifier_block *, unsigned long);
+
+PREDECL_DLIST(notifier_list);
+
+struct notifier_block {
+ struct notifier_list_item notifier_entry;
+ notifier_fn_t action;
+};
+
+DECLARE_DLIST(notifier_list, struct notifier_block, notifier_entry);
+
+struct notifier_list {
+ struct notifier_list_head head;
+};
+
+#define NOTIFIER_LIST_INITIALIZER(l) \
+ { \
+ .head = INIT_DLIST((l)->head) \
+ }
+
+static inline void notifier_init(struct notifier_list *l)
+{
+ notifier_list_init(&l->head);
+}
+
+static inline void notifier_add(struct notifier_block *n,
+ struct notifier_list *l, notifier_fn_t action)
+{
+ n->action = action;
+ notifier_list_add_tail(&l->head, n);
+}
+
+static inline void notifier_del(struct notifier_block *n,
+ struct notifier_list *l)
+{
+ notifier_list_del(&l->head, n);
+}
+
+static inline void notifier_call(struct notifier_list *l, int cmd)
+{
+ struct notifier_block *n;
+
+ frr_each_safe (notifier_list, &l->head, n)
+ n->action(n, cmd);
+}
+
+static inline int notifier_active(struct notifier_list *l)
+{
+ return notifier_list_count(&l->head) > 0;
+}
+
+extern struct hash *nhrp_gre_list;
+
+void nhrp_zebra_init(void);
+void nhrp_zebra_terminate(void);
+void nhrp_send_zebra_configure_arp(struct interface *ifp, int family);
+void nhrp_send_zebra_nbr(union sockunion *in,
+ union sockunion *out,
+ struct interface *ifp);
+
+void nhrp_send_zebra_gre_source_set(struct interface *ifp,
+ unsigned int link_idx,
+ vrf_id_t link_vrf_id);
+
+extern int nhrp_send_zebra_gre_request(struct interface *ifp);
+extern struct nhrp_gre_info *nhrp_gre_info_alloc(struct nhrp_gre_info *p);
+
+struct zbuf;
+struct nhrp_vc;
+struct nhrp_cache;
+struct nhrp_nhs;
+struct nhrp_interface;
+
+#define MAX_ID_LENGTH 64
+#define MAX_CERT_LENGTH 2048
+
+enum nhrp_notify_type {
+ NOTIFY_INTERFACE_UP,
+ NOTIFY_INTERFACE_DOWN,
+ NOTIFY_INTERFACE_CHANGED,
+ NOTIFY_INTERFACE_ADDRESS_CHANGED,
+ NOTIFY_INTERFACE_NBMA_CHANGED,
+ NOTIFY_INTERFACE_MTU_CHANGED,
+ NOTIFY_INTERFACE_IPSEC_CHANGED,
+
+ NOTIFY_VC_IPSEC_CHANGED,
+ NOTIFY_VC_IPSEC_UPDATE_NBMA,
+
+ NOTIFY_PEER_UP,
+ NOTIFY_PEER_DOWN,
+ NOTIFY_PEER_IFCONFIG_CHANGED,
+ NOTIFY_PEER_MTU_CHANGED,
+ NOTIFY_PEER_NBMA_CHANGING,
+
+ NOTIFY_CACHE_UP,
+ NOTIFY_CACHE_DOWN,
+ NOTIFY_CACHE_DELETE,
+ NOTIFY_CACHE_USED,
+ NOTIFY_CACHE_BINDING_CHANGE,
+};
+
+struct nhrp_vc {
+ struct notifier_list notifier_list;
+ uint32_t ipsec;
+ uint32_t ike_uniqueid;
+ uint8_t updating;
+ uint8_t abort_migration;
+
+ struct nhrp_vc_peer {
+ union sockunion nbma;
+ char id[MAX_ID_LENGTH];
+ uint16_t certlen;
+ uint8_t cert[MAX_CERT_LENGTH];
+ } local, remote;
+};
+
+enum nhrp_route_type {
+ NHRP_ROUTE_BLACKHOLE,
+ NHRP_ROUTE_LOCAL,
+ NHRP_ROUTE_NBMA_NEXTHOP,
+ NHRP_ROUTE_OFF_NBMA,
+};
+
+struct nhrp_peer {
+ unsigned int ref;
+ unsigned online : 1;
+ unsigned requested : 1;
+ unsigned fallback_requested : 1;
+ unsigned prio : 1;
+ struct notifier_list notifier_list;
+ struct interface *ifp;
+ struct nhrp_vc *vc;
+ struct thread *t_fallback;
+ struct notifier_block vc_notifier, ifp_notifier;
+ struct thread *t_timer;
+};
+
+struct nhrp_packet_parser {
+ struct interface *ifp;
+ struct nhrp_afi_data *if_ad;
+ struct nhrp_peer *peer;
+ struct zbuf *pkt;
+ struct zbuf payload;
+ struct zbuf extensions;
+ struct nhrp_packet_header *hdr;
+ enum nhrp_route_type route_type;
+ struct prefix route_prefix;
+ union sockunion src_nbma, src_proto, dst_proto;
+};
+
+struct nhrp_reqid_pool {
+ struct hash *reqid_hash;
+ uint32_t next_request_id;
+};
+
+struct nhrp_reqid {
+ uint32_t request_id;
+ void (*cb)(struct nhrp_reqid *, void *);
+};
+
+extern struct nhrp_reqid_pool nhrp_packet_reqid;
+extern struct nhrp_reqid_pool nhrp_event_reqid;
+
+enum nhrp_cache_type {
+ NHRP_CACHE_INVALID = 0,
+ NHRP_CACHE_INCOMPLETE,
+ NHRP_CACHE_NEGATIVE,
+ NHRP_CACHE_CACHED,
+ NHRP_CACHE_DYNAMIC,
+ NHRP_CACHE_NHS,
+ NHRP_CACHE_STATIC,
+ NHRP_CACHE_LOCAL,
+ NHRP_CACHE_NUM_TYPES
+};
+
+extern const char *const nhrp_cache_type_str[];
+extern unsigned long nhrp_cache_counts[NHRP_CACHE_NUM_TYPES];
+
+struct nhrp_cache_config {
+ struct interface *ifp;
+ union sockunion remote_addr;
+ enum nhrp_cache_type type;
+ union sockunion nbma;
+};
+
+struct nhrp_cache {
+ struct interface *ifp;
+ union sockunion remote_addr;
+
+ unsigned map : 1;
+ unsigned used : 1;
+ unsigned route_installed : 1;
+ unsigned nhrp_route_installed : 1;
+
+ struct notifier_block peer_notifier;
+ struct notifier_block newpeer_notifier;
+ struct notifier_list notifier_list;
+ struct nhrp_reqid eventid;
+ struct thread *t_timeout;
+ struct thread *t_auth;
+
+ struct {
+ enum nhrp_cache_type type;
+ union sockunion remote_nbma_natoa;
+ union sockunion remote_nbma_claimed;
+ struct nhrp_peer *peer;
+ time_t expires;
+ uint32_t mtu;
+ int holding_time;
+ } cur, new;
+};
+
+struct nhrp_shortcut {
+ struct prefix *p;
+ union sockunion addr;
+
+ struct nhrp_reqid reqid;
+ struct thread *t_timer;
+
+ enum nhrp_cache_type type;
+ unsigned int holding_time;
+ unsigned route_installed : 1;
+ unsigned expiring : 1;
+
+ struct nhrp_cache *cache;
+ struct notifier_block cache_notifier;
+};
+
+PREDECL_DLIST(nhrp_nhslist);
+PREDECL_DLIST(nhrp_mcastlist);
+PREDECL_DLIST(nhrp_reglist);
+
+struct nhrp_nhs {
+ struct interface *ifp;
+ struct nhrp_nhslist_item nhslist_entry;
+
+ unsigned hub : 1;
+ afi_t afi;
+ union sockunion proto_addr;
+ const char *nbma_fqdn; /* IP-address or FQDN */
+
+ struct thread *t_resolve;
+ struct resolver_query dns_resolve;
+ struct nhrp_reglist_head reglist_head;
+};
+
+DECLARE_DLIST(nhrp_nhslist, struct nhrp_nhs, nhslist_entry);
+
+struct nhrp_multicast {
+ struct interface *ifp;
+ struct nhrp_mcastlist_item mcastlist_entry;
+ afi_t afi;
+ union sockunion nbma_addr; /* IP-address */
+};
+
+DECLARE_DLIST(nhrp_mcastlist, struct nhrp_multicast, mcastlist_entry);
+
+struct nhrp_registration {
+ struct nhrp_reglist_item reglist_entry;
+ struct thread *t_register;
+ struct nhrp_nhs *nhs;
+ struct nhrp_reqid reqid;
+ unsigned int timeout;
+ unsigned mark : 1;
+ union sockunion proto_addr;
+ struct nhrp_peer *peer;
+ struct notifier_block peer_notifier;
+};
+
+DECLARE_DLIST(nhrp_reglist, struct nhrp_registration, reglist_entry);
+
+#define NHRP_IFF_SHORTCUT 0x0001
+#define NHRP_IFF_REDIRECT 0x0002
+#define NHRP_IFF_REG_NO_UNIQUE 0x0100
+
+struct nhrp_interface {
+ struct interface *ifp;
+
+ unsigned enabled : 1;
+
+ char *ipsec_profile, *ipsec_fallback_profile, *source;
+ union sockunion nbma;
+ union sockunion nat_nbma;
+ unsigned int link_idx;
+ unsigned int link_vrf_id;
+ uint32_t i_grekey;
+ uint32_t o_grekey;
+
+ struct hash *peer_hash;
+ struct hash *cache_config_hash;
+ struct hash *cache_hash;
+
+ struct notifier_list notifier_list;
+
+ struct interface *nbmaifp;
+ struct notifier_block nbmanifp_notifier;
+
+ struct nhrp_afi_data {
+ unsigned flags;
+ unsigned short configured : 1;
+ union sockunion addr;
+ uint32_t network_id;
+ short configured_mtu;
+ unsigned short mtu;
+ unsigned int holdtime;
+ struct nhrp_nhslist_head nhslist_head;
+ struct nhrp_mcastlist_head mcastlist_head;
+ } afi[AFI_MAX];
+};
+
+struct nhrp_gre_info {
+ ifindex_t ifindex;
+ struct in_addr vtep_ip; /* IFLA_GRE_LOCAL */
+ struct in_addr vtep_ip_remote; /* IFLA_GRE_REMOTE */
+ uint32_t ikey;
+ uint32_t okey;
+ ifindex_t ifindex_link; /* Interface index of interface
+ * linked with GRE
+ */
+ vrf_id_t vrfid_link;
+};
+
+extern struct zebra_privs_t nhrpd_privs;
+
+int sock_open_unix(const char *path);
+
+void nhrp_interface_init(void);
+void nhrp_interface_update(struct interface *ifp);
+void nhrp_interface_update_mtu(struct interface *ifp, afi_t afi);
+void nhrp_interface_update_nbma(struct interface *ifp,
+ struct nhrp_gre_info *gre_info);
+
+int nhrp_interface_add(ZAPI_CALLBACK_ARGS);
+int nhrp_interface_delete(ZAPI_CALLBACK_ARGS);
+int nhrp_interface_up(ZAPI_CALLBACK_ARGS);
+int nhrp_interface_down(ZAPI_CALLBACK_ARGS);
+int nhrp_interface_address_add(ZAPI_CALLBACK_ARGS);
+int nhrp_interface_address_delete(ZAPI_CALLBACK_ARGS);
+int nhrp_neighbor_operation(ZAPI_CALLBACK_ARGS);
+int nhrp_gre_update(ZAPI_CALLBACK_ARGS);
+
+void nhrp_interface_notify_add(struct interface *ifp, struct notifier_block *n,
+ notifier_fn_t fn);
+void nhrp_interface_notify_del(struct interface *ifp, struct notifier_block *n);
+void nhrp_interface_set_protection(struct interface *ifp, const char *profile,
+ const char *fallback_profile);
+void nhrp_interface_set_source(struct interface *ifp, const char *ifname);
+extern int nhrp_ifp_create(struct interface *ifp);
+extern int nhrp_ifp_up(struct interface *ifp);
+extern int nhrp_ifp_down(struct interface *ifp);
+extern int nhrp_ifp_destroy(struct interface *ifp);
+
+int nhrp_nhs_add(struct interface *ifp, afi_t afi, union sockunion *proto_addr,
+ const char *nbma_fqdn);
+int nhrp_nhs_del(struct interface *ifp, afi_t afi, union sockunion *proto_addr,
+ const char *nbma_fqdn);
+int nhrp_nhs_free(struct nhrp_interface *nifp, afi_t afi, struct nhrp_nhs *nhs);
+void nhrp_nhs_terminate(void);
+void nhrp_nhs_foreach(struct interface *ifp, afi_t afi,
+ void (*cb)(struct nhrp_nhs *, struct nhrp_registration *,
+ void *),
+ void *ctx);
+void nhrp_nhs_interface_del(struct interface *ifp);
+
+int nhrp_multicast_add(struct interface *ifp, afi_t afi,
+ union sockunion *nbma_addr);
+int nhrp_multicast_del(struct interface *ifp, afi_t afi,
+ union sockunion *nbma_addr);
+void nhrp_multicast_interface_del(struct interface *ifp);
+void nhrp_multicast_foreach(struct interface *ifp, afi_t afi,
+ void (*cb)(struct nhrp_multicast *, void *),
+ void *ctx);
+void netlink_mcast_set_nflog_group(int nlgroup);
+
+void nhrp_route_update_nhrp(const struct prefix *p, struct interface *ifp);
+void nhrp_route_announce(int add, enum nhrp_cache_type type,
+ const struct prefix *p, struct interface *ifp,
+ const union sockunion *nexthop, uint32_t mtu);
+int nhrp_route_read(ZAPI_CALLBACK_ARGS);
+int nhrp_route_get_nexthop(const union sockunion *addr, struct prefix *p,
+ union sockunion *via, struct interface **ifp);
+enum nhrp_route_type nhrp_route_address(struct interface *in_ifp,
+ union sockunion *addr, struct prefix *p,
+ struct nhrp_peer **peer);
+
+void nhrp_config_init(void);
+
+void nhrp_shortcut_init(void);
+void nhrp_shortcut_terminate(void);
+void nhrp_shortcut_initiate(union sockunion *addr);
+void nhrp_shortcut_foreach(afi_t afi,
+ void (*cb)(struct nhrp_shortcut *, void *),
+ void *ctx);
+void nhrp_shortcut_purge(struct nhrp_shortcut *s, int force);
+void nhrp_shortcut_prefix_change(const struct prefix *p, int deleted);
+
+void nhrp_cache_interface_del(struct interface *ifp);
+void nhrp_cache_config_free(struct nhrp_cache_config *c);
+struct nhrp_cache_config *nhrp_cache_config_get(struct interface *ifp,
+ union sockunion *remote_addr,
+ int create);
+struct nhrp_cache *nhrp_cache_get(struct interface *ifp,
+ union sockunion *remote_addr, int create);
+void nhrp_cache_foreach(struct interface *ifp,
+ void (*cb)(struct nhrp_cache *, void *), void *ctx);
+void nhrp_cache_config_foreach(struct interface *ifp,
+ void (*cb)(struct nhrp_cache_config *, void *), void *ctx);
+void nhrp_cache_set_used(struct nhrp_cache *, int);
+int nhrp_cache_update_binding(struct nhrp_cache *, enum nhrp_cache_type type,
+ int holding_time, struct nhrp_peer *p,
+ uint32_t mtu, union sockunion *nbma_natoa,
+ union sockunion *claimed_nbma);
+void nhrp_cache_notify_add(struct nhrp_cache *c, struct notifier_block *,
+ notifier_fn_t);
+void nhrp_cache_notify_del(struct nhrp_cache *c, struct notifier_block *);
+
+void nhrp_vc_init(void);
+void nhrp_vc_terminate(void);
+struct nhrp_vc *nhrp_vc_get(const union sockunion *src,
+ const union sockunion *dst, int create);
+int nhrp_vc_ipsec_updown(uint32_t child_id, struct nhrp_vc *vc);
+void nhrp_vc_notify_add(struct nhrp_vc *, struct notifier_block *,
+ notifier_fn_t);
+void nhrp_vc_notify_del(struct nhrp_vc *, struct notifier_block *);
+void nhrp_vc_foreach(void (*cb)(struct nhrp_vc *, void *), void *ctx);
+void nhrp_vc_reset(void);
+
+void vici_init(void);
+void vici_terminate(void);
+void vici_terminate_vc_by_profile_name(char *profile_name);
+void vici_terminate_vc_by_ike_id(unsigned int ike_id);
+void vici_request_vc(const char *profile, union sockunion *src,
+ union sockunion *dst, int prio);
+
+extern const char *nhrp_event_socket_path;
+
+void evmgr_init(void);
+void evmgr_terminate(void);
+void evmgr_set_socket(const char *socket);
+void evmgr_notify(const char *name, struct nhrp_cache *c,
+ void (*cb)(struct nhrp_reqid *, void *));
+
+struct nhrp_packet_header *nhrp_packet_push(struct zbuf *zb, uint8_t type,
+ const union sockunion *src_nbma,
+ const union sockunion *src_proto,
+ const union sockunion *dst_proto);
+void nhrp_packet_complete(struct zbuf *zb, struct nhrp_packet_header *hdr);
+uint16_t nhrp_packet_calculate_checksum(const uint8_t *pdu, uint16_t len);
+
+struct nhrp_packet_header *nhrp_packet_pull(struct zbuf *zb,
+ union sockunion *src_nbma,
+ union sockunion *src_proto,
+ union sockunion *dst_proto);
+
+struct nhrp_cie_header *nhrp_cie_push(struct zbuf *zb, uint8_t code,
+ const union sockunion *nbma,
+ const union sockunion *proto);
+struct nhrp_cie_header *nhrp_cie_pull(struct zbuf *zb,
+ struct nhrp_packet_header *hdr,
+ union sockunion *nbma,
+ union sockunion *proto);
+
+struct nhrp_extension_header *
+nhrp_ext_push(struct zbuf *zb, struct nhrp_packet_header *hdr, uint16_t type);
+void nhrp_ext_complete(struct zbuf *zb, struct nhrp_extension_header *ext);
+struct nhrp_extension_header *nhrp_ext_pull(struct zbuf *zb,
+ struct zbuf *payload);
+void nhrp_ext_request(struct zbuf *zb, struct nhrp_packet_header *hdr,
+ struct interface *);
+int nhrp_ext_reply(struct zbuf *zb, struct nhrp_packet_header *hdr,
+ struct interface *ifp, struct nhrp_extension_header *ext,
+ struct zbuf *extpayload);
+
+uint32_t nhrp_reqid_alloc(struct nhrp_reqid_pool *, struct nhrp_reqid *r,
+ void (*cb)(struct nhrp_reqid *, void *));
+void nhrp_reqid_free(struct nhrp_reqid_pool *, struct nhrp_reqid *r);
+struct nhrp_reqid *nhrp_reqid_lookup(struct nhrp_reqid_pool *, uint32_t reqid);
+
+int nhrp_packet_init(void);
+
+void nhrp_peer_interface_del(struct interface *ifp);
+struct nhrp_peer *nhrp_peer_get(struct interface *ifp,
+ const union sockunion *remote_nbma);
+struct nhrp_peer *nhrp_peer_ref(struct nhrp_peer *p);
+void nhrp_peer_unref(struct nhrp_peer *p);
+int nhrp_peer_check(struct nhrp_peer *p, int establish);
+void nhrp_peer_notify_add(struct nhrp_peer *p, struct notifier_block *,
+ notifier_fn_t);
+void nhrp_peer_notify_del(struct nhrp_peer *p, struct notifier_block *);
+void nhrp_peer_recv(struct nhrp_peer *p, struct zbuf *zb);
+void nhrp_peer_send(struct nhrp_peer *p, struct zbuf *zb);
+void nhrp_peer_send_indication(struct interface *ifp, uint16_t, struct zbuf *);
+
+int nhrp_nhs_match_ip(union sockunion *in_ip, struct nhrp_interface *nifp);
+
+#endif
diff --git a/nhrpd/os.h b/nhrpd/os.h
new file mode 100644
index 0000000..2b9e07f
--- /dev/null
+++ b/nhrpd/os.h
@@ -0,0 +1,7 @@
+
+int os_socket(void);
+int os_sendmsg(const uint8_t *buf, size_t len, int ifindex, const uint8_t *addr,
+ size_t addrlen, uint16_t protocol);
+int os_recvmsg(uint8_t *buf, size_t *len, int *ifindex, uint8_t *addr,
+ size_t *addrlen);
+int os_configure_dmvpn(unsigned int ifindex, const char *ifname, int af);
diff --git a/nhrpd/reqid.c b/nhrpd/reqid.c
new file mode 100644
index 0000000..738e935
--- /dev/null
+++ b/nhrpd/reqid.c
@@ -0,0 +1,52 @@
+#include "zebra.h"
+#include "hash.h"
+#include "nhrpd.h"
+
+static unsigned int nhrp_reqid_key(const void *data)
+{
+ const struct nhrp_reqid *r = data;
+ return r->request_id;
+}
+
+static bool nhrp_reqid_cmp(const void *data, const void *key)
+{
+ const struct nhrp_reqid *a = data, *b = key;
+
+ return a->request_id == b->request_id;
+}
+
+uint32_t nhrp_reqid_alloc(struct nhrp_reqid_pool *p, struct nhrp_reqid *r,
+ void (*cb)(struct nhrp_reqid *, void *))
+{
+ if (!p->reqid_hash) {
+ p->reqid_hash = hash_create(nhrp_reqid_key, nhrp_reqid_cmp,
+ "NHRP reqid Hash");
+ p->next_request_id = 1;
+ }
+
+ if (r->cb != cb) {
+ r->request_id = p->next_request_id;
+ if (++p->next_request_id == 0)
+ p->next_request_id = 1;
+ r->cb = cb;
+ (void)hash_get(p->reqid_hash, r, hash_alloc_intern);
+ }
+ return r->request_id;
+}
+
+void nhrp_reqid_free(struct nhrp_reqid_pool *p, struct nhrp_reqid *r)
+{
+ if (r->cb) {
+ hash_release(p->reqid_hash, r);
+ r->cb = NULL;
+ }
+}
+
+struct nhrp_reqid *nhrp_reqid_lookup(struct nhrp_reqid_pool *p, uint32_t reqid)
+{
+ struct nhrp_reqid key;
+ if (!p->reqid_hash)
+ return 0;
+ key.request_id = reqid;
+ return hash_lookup(p->reqid_hash, &key);
+}
diff --git a/nhrpd/subdir.am b/nhrpd/subdir.am
new file mode 100644
index 0000000..dc0c162
--- /dev/null
+++ b/nhrpd/subdir.am
@@ -0,0 +1,45 @@
+#
+# nhrpd
+#
+
+if NHRPD
+sbin_PROGRAMS += nhrpd/nhrpd
+vtysh_scan += nhrpd/nhrp_vty.c
+vtysh_daemons += nhrpd
+man8 += $(MANBUILD)/frr-nhrpd.8
+endif
+
+nhrpd_nhrpd_LDADD = lib/libfrr.la lib/libfrrcares.la $(LIBCAP)
+nhrpd_nhrpd_SOURCES = \
+ nhrpd/linux.c \
+ nhrpd/netlink_arp.c \
+ nhrpd/nhrp_cache.c \
+ nhrpd/nhrp_errors.c \
+ nhrpd/nhrp_event.c \
+ nhrpd/nhrp_interface.c \
+ nhrpd/nhrp_main.c \
+ nhrpd/nhrp_nhs.c \
+ nhrpd/nhrp_packet.c \
+ nhrpd/nhrp_peer.c \
+ nhrpd/nhrp_multicast.c \
+ nhrpd/nhrp_route.c \
+ nhrpd/nhrp_shortcut.c \
+ nhrpd/nhrp_vc.c \
+ nhrpd/nhrp_vty.c \
+ nhrpd/reqid.c \
+ nhrpd/vici.c \
+ nhrpd/zbuf.c \
+ nhrpd/znl.c \
+ # end
+
+noinst_HEADERS += \
+ nhrpd/debug.h \
+ nhrpd/netlink.h \
+ nhrpd/nhrp_errors.h \
+ nhrpd/nhrp_protocol.h \
+ nhrpd/nhrpd.h \
+ nhrpd/os.h \
+ nhrpd/vici.h \
+ nhrpd/zbuf.h \
+ nhrpd/znl.h \
+ # end
diff --git a/nhrpd/vici.c b/nhrpd/vici.c
new file mode 100644
index 0000000..6ba2399
--- /dev/null
+++ b/nhrpd/vici.c
@@ -0,0 +1,621 @@
+/* strongSwan VICI protocol implementation for NHRP
+ * Copyright (c) 2014-2015 Timo Teräs
+ *
+ * This file is free software: you may copy, redistribute and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "thread.h"
+#include "zbuf.h"
+#include "log.h"
+#include "lib_errors.h"
+
+#include "nhrpd.h"
+#include "vici.h"
+#include "nhrp_errors.h"
+
+#define ERRNO_IO_RETRY(EN) (((EN) == EAGAIN) || ((EN) == EWOULDBLOCK) || ((EN) == EINTR))
+
+struct blob {
+ char *ptr;
+ int len;
+};
+
+static int blob_equal(const struct blob *b, const char *str)
+{
+ if (!b || b->len != (int)strlen(str))
+ return 0;
+ return memcmp(b->ptr, str, b->len) == 0;
+}
+
+static int blob2buf(const struct blob *b, char *buf, size_t n)
+{
+ if (!b || b->len >= (int)n)
+ return 0;
+ memcpy(buf, b->ptr, b->len);
+ buf[b->len] = 0;
+ return 1;
+}
+
+struct vici_conn {
+ struct thread *t_reconnect, *t_read, *t_write;
+ struct zbuf ibuf;
+ struct zbuf_queue obuf;
+ int fd;
+ uint8_t ibuf_data[VICI_MAX_MSGLEN];
+};
+
+struct vici_message_ctx {
+ const char *sections[8];
+ int nsections;
+};
+
+static void vici_reconnect(struct thread *t);
+static void vici_submit_request(struct vici_conn *vici, const char *name, ...);
+
+static void vici_zbuf_puts(struct zbuf *obuf, const char *str)
+{
+ size_t len = strlen(str);
+ zbuf_put8(obuf, len);
+ zbuf_put(obuf, str, len);
+}
+
+static void vici_connection_error(struct vici_conn *vici)
+{
+ nhrp_vc_reset();
+
+ THREAD_OFF(vici->t_read);
+ THREAD_OFF(vici->t_write);
+ zbuf_reset(&vici->ibuf);
+ zbufq_reset(&vici->obuf);
+
+ close(vici->fd);
+ vici->fd = -1;
+ thread_add_timer(master, vici_reconnect, vici, 2, &vici->t_reconnect);
+}
+
+static void vici_parse_message(struct vici_conn *vici, struct zbuf *msg,
+ void (*parser)(struct vici_message_ctx *ctx,
+ enum vici_type_t msgtype,
+ const struct blob *key,
+ const struct blob *val),
+ struct vici_message_ctx *ctx)
+{
+ uint8_t *type;
+ struct blob key = {0};
+ struct blob val = {0};
+
+ while ((type = zbuf_may_pull(msg, uint8_t)) != NULL) {
+ switch (*type) {
+ case VICI_SECTION_START:
+ key.len = zbuf_get8(msg);
+ key.ptr = zbuf_pulln(msg, key.len);
+ debugf(NHRP_DEBUG_VICI, "VICI: Section start '%.*s'",
+ key.len, key.ptr);
+ parser(ctx, *type, &key, NULL);
+ ctx->nsections++;
+ break;
+ case VICI_SECTION_END:
+ debugf(NHRP_DEBUG_VICI, "VICI: Section end");
+ parser(ctx, *type, NULL, NULL);
+ ctx->nsections--;
+ break;
+ case VICI_KEY_VALUE:
+ key.len = zbuf_get8(msg);
+ key.ptr = zbuf_pulln(msg, key.len);
+ val.len = zbuf_get_be16(msg);
+ val.ptr = zbuf_pulln(msg, val.len);
+ debugf(NHRP_DEBUG_VICI, "VICI: Key '%.*s'='%.*s'",
+ key.len, key.ptr, val.len, val.ptr);
+ parser(ctx, *type, &key, &val);
+ break;
+ case VICI_LIST_START:
+ key.len = zbuf_get8(msg);
+ key.ptr = zbuf_pulln(msg, key.len);
+ debugf(NHRP_DEBUG_VICI, "VICI: List start '%.*s'",
+ key.len, key.ptr);
+ break;
+ case VICI_LIST_ITEM:
+ val.len = zbuf_get_be16(msg);
+ val.ptr = zbuf_pulln(msg, val.len);
+ debugf(NHRP_DEBUG_VICI, "VICI: List item: '%.*s'",
+ val.len, val.ptr);
+ parser(ctx, *type, &key, &val);
+ break;
+ case VICI_LIST_END:
+ debugf(NHRP_DEBUG_VICI, "VICI: List end");
+ break;
+ default:
+ debugf(NHRP_DEBUG_VICI,
+ "VICI: Unsupported message component type %d",
+ *type);
+ return;
+ }
+ }
+}
+
+struct handle_sa_ctx {
+ struct vici_message_ctx msgctx;
+ int event;
+ int child_ok;
+ int kill_ikesa;
+ uint32_t child_uniqueid, ike_uniqueid;
+ struct {
+ union sockunion host;
+ struct blob id, cert;
+ } local, remote;
+};
+
+static void parse_sa_message(struct vici_message_ctx *ctx,
+ enum vici_type_t msgtype, const struct blob *key,
+ const struct blob *val)
+{
+ struct handle_sa_ctx *sactx =
+ container_of(ctx, struct handle_sa_ctx, msgctx);
+ struct nhrp_vc *vc;
+ char buf[512];
+
+ switch (msgtype) {
+ case VICI_SECTION_START:
+ if (ctx->nsections == 3) {
+ /* Begin of child-sa section, reset child vars */
+ sactx->child_uniqueid = 0;
+ sactx->child_ok = 0;
+ }
+ break;
+ case VICI_SECTION_END:
+ if (ctx->nsections == 3) {
+ /* End of child-sa section, update nhrp_vc */
+ int up = sactx->child_ok || sactx->event == 1;
+ if (up) {
+ vc = nhrp_vc_get(&sactx->local.host,
+ &sactx->remote.host, up);
+ if (vc) {
+ blob2buf(&sactx->local.id, vc->local.id,
+ sizeof(vc->local.id));
+ if (blob2buf(&sactx->local.cert,
+ (char *)vc->local.cert,
+ sizeof(vc->local.cert)))
+ vc->local.certlen =
+ sactx->local.cert.len;
+ blob2buf(&sactx->remote.id,
+ vc->remote.id,
+ sizeof(vc->remote.id));
+ if (blob2buf(&sactx->remote.cert,
+ (char *)vc->remote.cert,
+ sizeof(vc->remote.cert)))
+ vc->remote.certlen =
+ sactx->remote.cert.len;
+ sactx->kill_ikesa |=
+ nhrp_vc_ipsec_updown(
+ sactx->child_uniqueid,
+ vc);
+ vc->ike_uniqueid = sactx->ike_uniqueid;
+ }
+ } else {
+ nhrp_vc_ipsec_updown(sactx->child_uniqueid, 0);
+ }
+ }
+ break;
+ default:
+ if (!key || !key->ptr)
+ break;
+
+ switch (key->ptr[0]) {
+ case 'l':
+ if (blob_equal(key, "local-host")
+ && ctx->nsections == 1) {
+ if (blob2buf(val, buf, sizeof(buf)))
+ if (str2sockunion(buf,
+ &sactx->local.host)
+ < 0)
+ flog_err(
+ EC_NHRP_SWAN,
+ "VICI: bad strongSwan local-host: %s",
+ buf);
+ } else if (blob_equal(key, "local-id")
+ && ctx->nsections == 1) {
+ sactx->local.id = *val;
+ } else if (blob_equal(key, "local-cert-data")
+ && ctx->nsections == 1) {
+ sactx->local.cert = *val;
+ }
+ break;
+ case 'r':
+ if (blob_equal(key, "remote-host")
+ && ctx->nsections == 1) {
+ if (blob2buf(val, buf, sizeof(buf)))
+ if (str2sockunion(buf,
+ &sactx->remote.host)
+ < 0)
+ flog_err(
+ EC_NHRP_SWAN,
+ "VICI: bad strongSwan remote-host: %s",
+ buf);
+ } else if (blob_equal(key, "remote-id")
+ && ctx->nsections == 1) {
+ sactx->remote.id = *val;
+ } else if (blob_equal(key, "remote-cert-data")
+ && ctx->nsections == 1) {
+ sactx->remote.cert = *val;
+ }
+ break;
+ case 'u':
+ if (blob_equal(key, "uniqueid")
+ && blob2buf(val, buf, sizeof(buf))) {
+ if (ctx->nsections == 3)
+ sactx->child_uniqueid =
+ strtoul(buf, NULL, 0);
+ else if (ctx->nsections == 1)
+ sactx->ike_uniqueid =
+ strtoul(buf, NULL, 0);
+ }
+ break;
+ case 's':
+ if (blob_equal(key, "state") && ctx->nsections == 3) {
+ sactx->child_ok =
+ (sactx->event == 0
+ && (blob_equal(val, "INSTALLED")
+ || blob_equal(val, "REKEYED")));
+ }
+ break;
+ }
+ break;
+ }
+}
+
+static void parse_cmd_response(struct vici_message_ctx *ctx,
+ enum vici_type_t msgtype, const struct blob *key,
+ const struct blob *val)
+{
+ char buf[512];
+
+ switch (msgtype) {
+ case VICI_KEY_VALUE:
+ if (blob_equal(key, "errmsg")
+ && blob2buf(val, buf, sizeof(buf)))
+ flog_err(EC_NHRP_SWAN, "VICI: strongSwan: %s", buf);
+ break;
+ default:
+ break;
+ }
+}
+
+static void vici_recv_sa(struct vici_conn *vici, struct zbuf *msg, int event)
+{
+ char buf[32];
+ struct handle_sa_ctx ctx = {
+ .event = event,
+ .msgctx.nsections = 0
+ };
+
+ vici_parse_message(vici, msg, parse_sa_message, &ctx.msgctx);
+
+ if (ctx.kill_ikesa && ctx.ike_uniqueid) {
+ debugf(NHRP_DEBUG_COMMON, "VICI: Deleting IKE_SA %u",
+ ctx.ike_uniqueid);
+ snprintf(buf, sizeof(buf), "%u", ctx.ike_uniqueid);
+ vici_submit_request(vici, "terminate", VICI_KEY_VALUE, "ike-id",
+ strlen(buf), buf, VICI_END);
+ }
+}
+
+static void vici_recv_message(struct vici_conn *vici, struct zbuf *msg)
+{
+ uint32_t msglen;
+ uint8_t msgtype;
+ struct blob name;
+ struct vici_message_ctx ctx = { .nsections = 0 };
+
+ msglen = zbuf_get_be32(msg);
+ msgtype = zbuf_get8(msg);
+ debugf(NHRP_DEBUG_VICI, "VICI: Message %d, %d bytes", msgtype, msglen);
+
+ switch (msgtype) {
+ case VICI_EVENT:
+ name.len = zbuf_get8(msg);
+ name.ptr = zbuf_pulln(msg, name.len);
+
+ debugf(NHRP_DEBUG_VICI, "VICI: Event '%.*s'", name.len,
+ name.ptr);
+ if (blob_equal(&name, "list-sa")
+ || blob_equal(&name, "child-updown")
+ || blob_equal(&name, "child-rekey"))
+ vici_recv_sa(vici, msg, 0);
+ else if (blob_equal(&name, "child-state-installed")
+ || blob_equal(&name, "child-state-rekeyed"))
+ vici_recv_sa(vici, msg, 1);
+ else if (blob_equal(&name, "child-state-destroying"))
+ vici_recv_sa(vici, msg, 2);
+ break;
+ case VICI_CMD_RESPONSE:
+ vici_parse_message(vici, msg, parse_cmd_response, &ctx);
+ break;
+ case VICI_EVENT_UNKNOWN:
+ case VICI_CMD_UNKNOWN:
+ flog_err(
+ EC_NHRP_SWAN,
+ "VICI: StrongSwan does not support mandatory events (unpatched?)");
+ break;
+ case VICI_EVENT_CONFIRM:
+ break;
+ default:
+ zlog_notice("VICI: Unrecognized message type %d", msgtype);
+ break;
+ }
+}
+
+static void vici_read(struct thread *t)
+{
+ struct vici_conn *vici = THREAD_ARG(t);
+ struct zbuf *ibuf = &vici->ibuf;
+ struct zbuf pktbuf;
+
+ if (zbuf_read(ibuf, vici->fd, (size_t)-1) < 0) {
+ vici_connection_error(vici);
+ return;
+ }
+
+ /* Process all messages in buffer */
+ do {
+ uint32_t *hdrlen = zbuf_may_pull(ibuf, uint32_t);
+ if (!hdrlen)
+ break;
+ if (!zbuf_may_pulln(ibuf, ntohl(*hdrlen))) {
+ zbuf_reset_head(ibuf, hdrlen);
+ break;
+ }
+
+ /* Handle packet */
+ zbuf_init(&pktbuf, hdrlen, htonl(*hdrlen) + 4,
+ htonl(*hdrlen) + 4);
+ vici_recv_message(vici, &pktbuf);
+ } while (1);
+
+ thread_add_read(master, vici_read, vici, vici->fd, &vici->t_read);
+}
+
+static void vici_write(struct thread *t)
+{
+ struct vici_conn *vici = THREAD_ARG(t);
+ int r;
+
+ r = zbufq_write(&vici->obuf, vici->fd);
+ if (r > 0) {
+ thread_add_write(master, vici_write, vici, vici->fd,
+ &vici->t_write);
+ } else if (r < 0) {
+ vici_connection_error(vici);
+ }
+}
+
+static void vici_submit(struct vici_conn *vici, struct zbuf *obuf)
+{
+ if (vici->fd < 0) {
+ zbuf_free(obuf);
+ return;
+ }
+
+ zbufq_queue(&vici->obuf, obuf);
+ thread_add_write(master, vici_write, vici, vici->fd, &vici->t_write);
+}
+
+static void vici_submit_request(struct vici_conn *vici, const char *name, ...)
+{
+ struct zbuf *obuf;
+ uint32_t *hdrlen;
+ va_list va;
+ size_t len;
+ int type;
+
+ obuf = zbuf_alloc(256);
+ if (!obuf)
+ return;
+
+ hdrlen = zbuf_push(obuf, uint32_t);
+ zbuf_put8(obuf, VICI_CMD_REQUEST);
+ vici_zbuf_puts(obuf, name);
+
+ va_start(va, name);
+ for (type = va_arg(va, int); type != VICI_END; type = va_arg(va, int)) {
+ zbuf_put8(obuf, type);
+ switch (type) {
+ case VICI_KEY_VALUE:
+ vici_zbuf_puts(obuf, va_arg(va, const char *));
+ len = va_arg(va, size_t);
+ zbuf_put_be16(obuf, len);
+ zbuf_put(obuf, va_arg(va, void *), len);
+ break;
+ default:
+ break;
+ }
+ }
+ va_end(va);
+ *hdrlen = htonl(zbuf_used(obuf) - 4);
+ vici_submit(vici, obuf);
+}
+
+static void vici_register_event(struct vici_conn *vici, const char *name)
+{
+ struct zbuf *obuf;
+ uint32_t *hdrlen;
+ uint8_t namelen;
+
+ namelen = strlen(name);
+ obuf = zbuf_alloc(4 + 1 + 1 + namelen);
+ if (!obuf)
+ return;
+
+ hdrlen = zbuf_push(obuf, uint32_t);
+ zbuf_put8(obuf, VICI_EVENT_REGISTER);
+ zbuf_put8(obuf, namelen);
+ zbuf_put(obuf, name, namelen);
+ *hdrlen = htonl(zbuf_used(obuf) - 4);
+
+ vici_submit(vici, obuf);
+}
+
+static bool vici_charon_filepath_done;
+static bool vici_charon_not_found;
+
+static char *vici_get_charon_filepath(void)
+{
+ static char buff[1200];
+ FILE *fp;
+ char *ptr;
+ char line[1024];
+
+ if (vici_charon_filepath_done)
+ return (char *)buff;
+ fp = popen("ipsec --piddir", "r");
+ if (!fp) {
+ if (!vici_charon_not_found) {
+ flog_err(EC_NHRP_SWAN,
+ "VICI: Failed to retrieve charon file path");
+ vici_charon_not_found = true;
+ }
+ return NULL;
+ }
+ /* last line of output is used to get vici path */
+ while (fgets(line, sizeof(line), fp) != NULL) {
+ ptr = strchr(line, '\n');
+ if (ptr)
+ *ptr = '\0';
+ snprintf(buff, sizeof(buff), "%s/charon.vici", line);
+ }
+ pclose(fp);
+ vici_charon_filepath_done = true;
+ return buff;
+}
+
+static void vici_reconnect(struct thread *t)
+{
+ struct vici_conn *vici = THREAD_ARG(t);
+ int fd;
+ char *file_path;
+
+ if (vici->fd >= 0)
+ return;
+
+ fd = sock_open_unix(VICI_SOCKET);
+ if (fd < 0) {
+ file_path = vici_get_charon_filepath();
+ if (file_path)
+ fd = sock_open_unix(file_path);
+ }
+ if (fd < 0) {
+ debugf(NHRP_DEBUG_VICI,
+ "%s: failure connecting VICI socket: %s", __func__,
+ strerror(errno));
+ thread_add_timer(master, vici_reconnect, vici, 2,
+ &vici->t_reconnect);
+ return;
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "VICI: Connected");
+ vici->fd = fd;
+ thread_add_read(master, vici_read, vici, vici->fd, &vici->t_read);
+
+ /* Send event subscribtions */
+ // vici_register_event(vici, "child-updown");
+ // vici_register_event(vici, "child-rekey");
+ vici_register_event(vici, "child-state-installed");
+ vici_register_event(vici, "child-state-rekeyed");
+ vici_register_event(vici, "child-state-destroying");
+ vici_register_event(vici, "list-sa");
+ vici_submit_request(vici, "list-sas", VICI_END);
+}
+
+static struct vici_conn vici_connection;
+
+void vici_init(void)
+{
+ struct vici_conn *vici = &vici_connection;
+
+ vici->fd = -1;
+ zbuf_init(&vici->ibuf, vici->ibuf_data, sizeof(vici->ibuf_data), 0);
+ zbufq_init(&vici->obuf);
+ thread_add_timer_msec(master, vici_reconnect, vici, 10,
+ &vici->t_reconnect);
+}
+
+void vici_terminate(void)
+{
+}
+
+void vici_terminate_vc_by_profile_name(char *profile_name)
+{
+ struct vici_conn *vici = &vici_connection;
+
+ debugf(NHRP_DEBUG_VICI, "Terminate profile = %s", profile_name);
+ vici_submit_request(vici, "terminate", VICI_KEY_VALUE, "ike",
+ strlen(profile_name), profile_name, VICI_END);
+}
+
+void vici_terminate_vc_by_ike_id(unsigned int ike_id)
+{
+ struct vici_conn *vici = &vici_connection;
+ char ike_id_str[10];
+
+ snprintf(ike_id_str, sizeof(ike_id_str), "%d", ike_id);
+ debugf(NHRP_DEBUG_VICI, "Terminate ike_id_str = %s", ike_id_str);
+ vici_submit_request(vici, "terminate", VICI_KEY_VALUE, "ike-id",
+ strlen(ike_id_str), ike_id_str, VICI_END);
+}
+
+void vici_request_vc(const char *profile, union sockunion *src,
+ union sockunion *dst, int prio)
+{
+ struct vici_conn *vici = &vici_connection;
+ char buf[2][SU_ADDRSTRLEN];
+
+ sockunion2str(src, buf[0], sizeof(buf[0]));
+ sockunion2str(dst, buf[1], sizeof(buf[1]));
+
+ vici_submit_request(vici, "initiate", VICI_KEY_VALUE, "child",
+ strlen(profile), profile, VICI_KEY_VALUE, "timeout",
+ (size_t)2, "-1", VICI_KEY_VALUE, "async", (size_t)1,
+ "1", VICI_KEY_VALUE, "init-limits", (size_t)1,
+ prio ? "0" : "1", VICI_KEY_VALUE, "my-host",
+ strlen(buf[0]), buf[0], VICI_KEY_VALUE,
+ "other-host", strlen(buf[1]), buf[1], VICI_END);
+}
+
+int sock_open_unix(const char *path)
+{
+ int ret, fd;
+ struct sockaddr_un addr;
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
+ return -1;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strlcpy(addr.sun_path, path, sizeof(addr.sun_path));
+
+ ret = connect(fd, (struct sockaddr *)&addr,
+ sizeof(addr.sun_family) + strlen(addr.sun_path));
+ if (ret < 0) {
+ close(fd);
+ return -1;
+ }
+
+ ret = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
+ if (ret < 0) {
+ close(fd);
+ return -1;
+ }
+
+ return fd;
+}
diff --git a/nhrpd/vici.h b/nhrpd/vici.h
new file mode 100644
index 0000000..f2ad3a9
--- /dev/null
+++ b/nhrpd/vici.h
@@ -0,0 +1,24 @@
+
+enum vici_type_t {
+ VICI_START = 0,
+ VICI_SECTION_START = 1,
+ VICI_SECTION_END = 2,
+ VICI_KEY_VALUE = 3,
+ VICI_LIST_START = 4,
+ VICI_LIST_ITEM = 5,
+ VICI_LIST_END = 6,
+ VICI_END = 7
+};
+
+enum vici_operation_t {
+ VICI_CMD_REQUEST = 0,
+ VICI_CMD_RESPONSE,
+ VICI_CMD_UNKNOWN,
+ VICI_EVENT_REGISTER,
+ VICI_EVENT_UNREGISTER,
+ VICI_EVENT_CONFIRM,
+ VICI_EVENT_UNKNOWN,
+ VICI_EVENT,
+};
+
+#define VICI_MAX_MSGLEN (512*1024)
diff --git a/nhrpd/zbuf.c b/nhrpd/zbuf.c
new file mode 100644
index 0000000..3d54f4e
--- /dev/null
+++ b/nhrpd/zbuf.c
@@ -0,0 +1,241 @@
+/* Stream/packet buffer API implementation
+ * Copyright (c) 2014-2015 Timo Teräs
+ *
+ * This file is free software: you may copy, redistribute and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+#include "zbuf.h"
+#include "memory.h"
+#include "nhrpd.h"
+
+#define ERRNO_IO_RETRY(EN) (((EN) == EAGAIN) || ((EN) == EWOULDBLOCK) || ((EN) == EINTR))
+
+DEFINE_MTYPE_STATIC(NHRPD, ZBUF_DATA, "NHRPD zbuf data");
+
+struct zbuf *zbuf_alloc(size_t size)
+{
+ struct zbuf *zb;
+
+ zb = XCALLOC(MTYPE_ZBUF_DATA, sizeof(*zb) + size);
+
+ zbuf_init(zb, zb + 1, size, 0);
+ zb->allocated = 1;
+
+ return zb;
+}
+
+void zbuf_init(struct zbuf *zb, void *buf, size_t len, size_t datalen)
+{
+ *zb = (struct zbuf){
+ .buf = buf,
+ .end = (uint8_t *)buf + len,
+ .head = buf,
+ .tail = (uint8_t *)buf + datalen,
+ };
+}
+
+void zbuf_free(struct zbuf *zb)
+{
+ if (zb->allocated)
+ XFREE(MTYPE_ZBUF_DATA, zb);
+}
+
+void zbuf_reset(struct zbuf *zb)
+{
+ zb->head = zb->tail = zb->buf;
+ zb->error = 0;
+}
+
+void zbuf_reset_head(struct zbuf *zb, void *ptr)
+{
+ assert((void *)zb->buf <= ptr && ptr <= (void *)zb->tail);
+ zb->head = ptr;
+}
+
+static void zbuf_remove_headroom(struct zbuf *zb)
+{
+ ssize_t headroom = zbuf_headroom(zb);
+ if (!headroom)
+ return;
+ memmove(zb->buf, zb->head, zbuf_used(zb));
+ zb->head -= headroom;
+ zb->tail -= headroom;
+}
+
+ssize_t zbuf_read(struct zbuf *zb, int fd, size_t maxlen)
+{
+ ssize_t r;
+
+ if (zb->error)
+ return -3;
+
+ zbuf_remove_headroom(zb);
+ if (maxlen > zbuf_tailroom(zb))
+ maxlen = zbuf_tailroom(zb);
+
+ r = read(fd, zb->tail, maxlen);
+ if (r > 0)
+ zb->tail += r;
+ else if (r == 0)
+ r = -2;
+ else if (ERRNO_IO_RETRY(errno))
+ r = 0;
+
+ return r;
+}
+
+ssize_t zbuf_write(struct zbuf *zb, int fd)
+{
+ ssize_t r;
+
+ if (zb->error)
+ return -3;
+
+ r = write(fd, zb->head, zbuf_used(zb));
+ if (r > 0) {
+ zb->head += r;
+ if (zb->head == zb->tail)
+ zbuf_reset(zb);
+ } else if (r == 0)
+ r = -2;
+ else if (ERRNO_IO_RETRY(errno))
+ r = 0;
+
+ return r;
+}
+
+ssize_t zbuf_recv(struct zbuf *zb, int fd)
+{
+ ssize_t r;
+
+ if (zb->error)
+ return -3;
+
+ zbuf_remove_headroom(zb);
+ r = recv(fd, zb->tail, zbuf_tailroom(zb), 0);
+ if (r > 0)
+ zb->tail += r;
+ else if (r == 0)
+ r = -2;
+ else if (ERRNO_IO_RETRY(errno))
+ r = 0;
+ return r;
+}
+
+ssize_t zbuf_send(struct zbuf *zb, int fd)
+{
+ ssize_t r;
+
+ if (zb->error)
+ return -3;
+
+ r = send(fd, zb->head, zbuf_used(zb), 0);
+ if (r >= 0)
+ zbuf_reset(zb);
+
+ return r;
+}
+
+void *zbuf_may_pull_until(struct zbuf *zb, const char *sep, struct zbuf *msg)
+{
+ size_t seplen = strlen(sep), len;
+ uint8_t *ptr;
+
+ ptr = memmem(zb->head, zbuf_used(zb), sep, seplen);
+ if (!ptr)
+ return NULL;
+
+ len = ptr - zb->head + seplen;
+ zbuf_init(msg, zbuf_pulln(zb, len), len, len);
+ return msg->head;
+}
+
+void zbufq_init(struct zbuf_queue *zbq)
+{
+ *zbq = (struct zbuf_queue){
+ .queue_head = INIT_DLIST(zbq->queue_head),
+ };
+}
+
+void zbufq_reset(struct zbuf_queue *zbq)
+{
+ struct zbuf *buf;
+
+ frr_each_safe (zbuf_queue, &zbq->queue_head, buf) {
+ zbuf_queue_del(&zbq->queue_head, buf);
+ zbuf_free(buf);
+ }
+}
+
+void zbufq_queue(struct zbuf_queue *zbq, struct zbuf *zb)
+{
+ zbuf_queue_add_tail(&zbq->queue_head, zb);
+}
+
+int zbufq_write(struct zbuf_queue *zbq, int fd)
+{
+ struct iovec iov[16];
+ struct zbuf *zb;
+ ssize_t r;
+ size_t iovcnt = 0;
+
+ frr_each_safe (zbuf_queue, &zbq->queue_head, zb) {
+ iov[iovcnt++] = (struct iovec){
+ .iov_base = zb->head, .iov_len = zbuf_used(zb),
+ };
+ if (iovcnt >= array_size(iov))
+ break;
+ }
+
+ r = writev(fd, iov, iovcnt);
+ if (r < 0)
+ return r;
+
+ frr_each_safe (zbuf_queue, &zbq->queue_head, zb) {
+ if (r < (ssize_t)zbuf_used(zb)) {
+ zb->head += r;
+ return 1;
+ }
+
+ r -= zbuf_used(zb);
+ zbuf_queue_del(&zbq->queue_head, zb);
+ zbuf_free(zb);
+ }
+
+ return 0;
+}
+
+void zbuf_copy(struct zbuf *zdst, struct zbuf *zsrc, size_t len)
+{
+ const void *src;
+ void *dst;
+
+ dst = zbuf_pushn(zdst, len);
+ src = zbuf_pulln(zsrc, len);
+ if (!dst || !src)
+ return;
+ memcpy(dst, src, len);
+}
+
+void zbuf_copy_peek(struct zbuf *zdst, struct zbuf *zsrc, size_t len)
+{
+ const void *src;
+ void *dst;
+
+ dst = zbuf_pushn(zdst, len);
+ src = zbuf_pulln(zsrc, 0);
+ if (!dst || !src)
+ return;
+ memcpy(dst, src, len);
+}
diff --git a/nhrpd/zbuf.h b/nhrpd/zbuf.h
new file mode 100644
index 0000000..d036b10
--- /dev/null
+++ b/nhrpd/zbuf.h
@@ -0,0 +1,203 @@
+/* Stream/packet buffer API
+ * Copyright (c) 2014-2015 Timo Teräs
+ *
+ * This file is free software: you may copy, redistribute and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef ZBUF_H
+#define ZBUF_H
+
+#include <stdint.h>
+#include <string.h>
+#include <endian.h>
+#include <sys/types.h>
+
+#include "typesafe.h"
+
+PREDECL_DLIST(zbuf_queue);
+
+struct zbuf {
+ struct zbuf_queue_item queue_entry;
+ unsigned allocated : 1;
+ unsigned error : 1;
+ uint8_t *buf, *end;
+ uint8_t *head, *tail;
+};
+
+DECLARE_DLIST(zbuf_queue, struct zbuf, queue_entry);
+
+struct zbuf_queue {
+ struct zbuf_queue_head queue_head;
+};
+
+struct zbuf *zbuf_alloc(size_t size);
+void zbuf_init(struct zbuf *zb, void *buf, size_t len, size_t datalen);
+void zbuf_free(struct zbuf *zb);
+
+static inline size_t zbuf_size(struct zbuf *zb)
+{
+ return zb->end - zb->buf;
+}
+
+static inline size_t zbuf_used(struct zbuf *zb)
+{
+ return zb->tail - zb->head;
+}
+
+static inline size_t zbuf_tailroom(struct zbuf *zb)
+{
+ return zb->end - zb->tail;
+}
+
+static inline size_t zbuf_headroom(struct zbuf *zb)
+{
+ return zb->head - zb->buf;
+}
+
+void zbuf_reset(struct zbuf *zb);
+void zbuf_reset_head(struct zbuf *zb, void *ptr);
+ssize_t zbuf_read(struct zbuf *zb, int fd, size_t maxlen);
+ssize_t zbuf_write(struct zbuf *zb, int fd);
+ssize_t zbuf_recv(struct zbuf *zb, int fd);
+ssize_t zbuf_send(struct zbuf *zb, int fd);
+
+static inline void zbuf_set_rerror(struct zbuf *zb)
+{
+ zb->error = 1;
+ zb->head = zb->tail;
+}
+
+static inline void zbuf_set_werror(struct zbuf *zb)
+{
+ zb->error = 1;
+ zb->tail = zb->end;
+}
+
+static inline void *__zbuf_pull(struct zbuf *zb, size_t size, int error)
+{
+ void *head = zb->head;
+ if (size > zbuf_used(zb)) {
+ if (error)
+ zbuf_set_rerror(zb);
+ return NULL;
+ }
+ zb->head += size;
+ return head;
+}
+
+#define zbuf_pull(zb, type) ((type *)__zbuf_pull(zb, sizeof(type), 1))
+#define zbuf_pulln(zb, sz) (__zbuf_pull(zb, sz, 1))
+#define zbuf_may_pull(zb, type) ((type *)__zbuf_pull(zb, sizeof(type), 0))
+#define zbuf_may_pulln(zb, sz) (__zbuf_pull(zb, sz, 0))
+
+void *zbuf_may_pull_until(struct zbuf *zb, const char *sep, struct zbuf *msg);
+
+static inline void zbuf_get(struct zbuf *zb, void *dst, size_t len)
+{
+ void *src = zbuf_pulln(zb, len);
+ if (src)
+ memcpy(dst, src, len);
+}
+
+static inline uint8_t zbuf_get8(struct zbuf *zb)
+{
+ uint8_t *src = zbuf_pull(zb, uint8_t);
+ if (src)
+ return *src;
+ return 0;
+}
+
+static inline uint32_t zbuf_get32(struct zbuf *zb)
+{
+ struct unaligned32 {
+ uint32_t value;
+ } __attribute__((packed));
+
+ struct unaligned32 *v = zbuf_pull(zb, struct unaligned32);
+ if (v)
+ return v->value;
+ return 0;
+}
+
+static inline uint16_t zbuf_get_be16(struct zbuf *zb)
+{
+ struct unaligned16 {
+ uint16_t value;
+ } __attribute__((packed));
+
+ struct unaligned16 *v = zbuf_pull(zb, struct unaligned16);
+ if (v)
+ return be16toh(v->value);
+ return 0;
+}
+
+static inline uint32_t zbuf_get_be32(struct zbuf *zb)
+{
+ return be32toh(zbuf_get32(zb));
+}
+
+static inline void *__zbuf_push(struct zbuf *zb, size_t size, int error)
+{
+ void *tail = zb->tail;
+ if (size > zbuf_tailroom(zb)) {
+ if (error)
+ zbuf_set_werror(zb);
+ return NULL;
+ }
+ zb->tail += size;
+ return tail;
+}
+
+#define zbuf_push(zb, type) ((type *)__zbuf_push(zb, sizeof(type), 1))
+#define zbuf_pushn(zb, sz) (__zbuf_push(zb, sz, 1))
+#define zbuf_may_push(zb, type) ((type *)__zbuf_may_push(zb, sizeof(type), 0))
+#define zbuf_may_pushn(zb, sz) (__zbuf_push(zb, sz, 0))
+
+static inline void zbuf_put(struct zbuf *zb, const void *src, size_t len)
+{
+ void *dst = zbuf_pushn(zb, len);
+ if (dst)
+ memcpy(dst, src, len);
+}
+
+static inline void zbuf_put8(struct zbuf *zb, uint8_t val)
+{
+ uint8_t *dst = zbuf_push(zb, uint8_t);
+ if (dst)
+ *dst = val;
+}
+
+static inline void zbuf_put_be16(struct zbuf *zb, uint16_t val)
+{
+ struct unaligned16 {
+ uint16_t value;
+ } __attribute__((packed));
+
+ struct unaligned16 *v = zbuf_push(zb, struct unaligned16);
+ if (v)
+ v->value = htobe16(val);
+}
+
+static inline void zbuf_put_be32(struct zbuf *zb, uint32_t val)
+{
+ struct unaligned32 {
+ uint32_t value;
+ } __attribute__((packed));
+
+ struct unaligned32 *v = zbuf_push(zb, struct unaligned32);
+ if (v)
+ v->value = htobe32(val);
+}
+
+void zbuf_copy(struct zbuf *zb, struct zbuf *src, size_t len);
+void zbuf_copy_peek(struct zbuf *zdst, struct zbuf *zsrc, size_t len);
+
+void zbufq_init(struct zbuf_queue *);
+void zbufq_reset(struct zbuf_queue *);
+void zbufq_queue(struct zbuf_queue *, struct zbuf *);
+int zbufq_write(struct zbuf_queue *, int);
+
+#endif
diff --git a/nhrpd/znl.c b/nhrpd/znl.c
new file mode 100644
index 0000000..6030987
--- /dev/null
+++ b/nhrpd/znl.c
@@ -0,0 +1,170 @@
+/* Netlink helpers for zbuf
+ * Copyright (c) 2014-2015 Timo Teräs
+ *
+ * This file is free software: you may copy, redistribute and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#include "znl.h"
+
+#define ZNL_ALIGN(len) (((len)+3) & ~3)
+
+void *znl_push(struct zbuf *zb, size_t n)
+{
+ return zbuf_pushn(zb, ZNL_ALIGN(n));
+}
+
+void *znl_pull(struct zbuf *zb, size_t n)
+{
+ return zbuf_pulln(zb, ZNL_ALIGN(n));
+}
+
+struct nlmsghdr *znl_nlmsg_push(struct zbuf *zb, uint16_t type, uint16_t flags)
+{
+ struct nlmsghdr *n;
+
+ n = znl_push(zb, sizeof(*n));
+ if (!n)
+ return NULL;
+
+ *n = (struct nlmsghdr){
+ .nlmsg_type = type, .nlmsg_flags = flags,
+ };
+ return n;
+}
+
+void znl_nlmsg_complete(struct zbuf *zb, struct nlmsghdr *n)
+{
+ n->nlmsg_len = zb->tail - (uint8_t *)n;
+}
+
+struct nlmsghdr *znl_nlmsg_pull(struct zbuf *zb, struct zbuf *payload)
+{
+ struct nlmsghdr *n;
+ size_t plen;
+
+ n = znl_pull(zb, sizeof(*n));
+ if (!n)
+ return NULL;
+
+ plen = n->nlmsg_len - sizeof(*n);
+ zbuf_init(payload, znl_pull(zb, plen), plen, plen);
+ zbuf_may_pulln(zb, ZNL_ALIGN(plen) - plen);
+
+ return n;
+}
+
+struct rtattr *znl_rta_push(struct zbuf *zb, uint16_t type, const void *val,
+ size_t len)
+{
+ struct rtattr *rta;
+ uint8_t *dst;
+
+ rta = znl_push(zb, ZNL_ALIGN(sizeof(*rta)) + ZNL_ALIGN(len));
+ if (!rta)
+ return NULL;
+
+ *rta = (struct rtattr){
+ .rta_type = type, .rta_len = ZNL_ALIGN(sizeof(*rta)) + len,
+ };
+
+ dst = (uint8_t *)(rta + 1);
+ memcpy(dst, val, len);
+ memset(dst + len, 0, ZNL_ALIGN(len) - len);
+
+ return rta;
+}
+
+struct rtattr *znl_rta_push_u32(struct zbuf *zb, uint16_t type, uint32_t val)
+{
+ return znl_rta_push(zb, type, &val, sizeof(val));
+}
+
+struct rtattr *znl_rta_nested_push(struct zbuf *zb, uint16_t type)
+{
+ struct rtattr *rta;
+
+ rta = znl_push(zb, sizeof(*rta));
+ if (!rta)
+ return NULL;
+
+ *rta = (struct rtattr){
+ .rta_type = type,
+ };
+ return rta;
+}
+
+void znl_rta_nested_complete(struct zbuf *zb, struct rtattr *rta)
+{
+ size_t len = zb->tail - (uint8_t *)rta;
+ size_t align = ZNL_ALIGN(len) - len;
+
+ if (align) {
+ void *dst = zbuf_pushn(zb, align);
+ if (dst)
+ memset(dst, 0, align);
+ }
+ rta->rta_len = len;
+}
+
+struct rtattr *znl_rta_pull(struct zbuf *zb, struct zbuf *payload)
+{
+ struct rtattr *rta;
+ size_t plen;
+
+ rta = znl_pull(zb, sizeof(*rta));
+ if (!rta)
+ return NULL;
+
+ if (rta->rta_len > sizeof(*rta)) {
+ plen = rta->rta_len - sizeof(*rta);
+ zbuf_init(payload, znl_pull(zb, plen), plen, plen);
+ } else {
+ zbuf_init(payload, NULL, 0, 0);
+ }
+
+ return rta;
+}
+
+int znl_open(int protocol, int groups)
+{
+ struct sockaddr_nl addr;
+ int fd, buf = 128 * 1024;
+
+ fd = socket(AF_NETLINK, SOCK_RAW, protocol);
+ if (fd < 0)
+ return -1;
+
+ if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) < 0)
+ goto error;
+ if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0)
+ goto error;
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &buf, sizeof(buf)) < 0)
+ goto error;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.nl_family = AF_NETLINK;
+ addr.nl_groups = groups;
+ if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
+ goto error;
+
+ return fd;
+error:
+ close(fd);
+ return -1;
+}
diff --git a/nhrpd/znl.h b/nhrpd/znl.h
new file mode 100644
index 0000000..fd31daa
--- /dev/null
+++ b/nhrpd/znl.h
@@ -0,0 +1,29 @@
+/* Netlink helpers for zbuf
+ * Copyright (c) 2014-2015 Timo Teräs
+ *
+ * This file is free software: you may copy, redistribute and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include "zbuf.h"
+
+#define ZNL_BUFFER_SIZE 8192
+
+void *znl_push(struct zbuf *zb, size_t n);
+void *znl_pull(struct zbuf *zb, size_t n);
+
+struct nlmsghdr *znl_nlmsg_push(struct zbuf *zb, uint16_t type, uint16_t flags);
+void znl_nlmsg_complete(struct zbuf *zb, struct nlmsghdr *n);
+struct nlmsghdr *znl_nlmsg_pull(struct zbuf *zb, struct zbuf *payload);
+
+struct rtattr *znl_rta_push(struct zbuf *zb, uint16_t type, const void *val,
+ size_t len);
+struct rtattr *znl_rta_push_u32(struct zbuf *zb, uint16_t type, uint32_t val);
+struct rtattr *znl_rta_nested_push(struct zbuf *zb, uint16_t type);
+void znl_rta_nested_complete(struct zbuf *zb, struct rtattr *rta);
+
+struct rtattr *znl_rta_pull(struct zbuf *zb, struct zbuf *payload);
+
+int znl_open(int protocol, int groups);