Browse Source

nhrpd: implement next hop resolution protocol

This provides DMVPN support and integrates to strongSwan. Please read
README.nhrpd and README.kernel for more details.
Timo Teräs 2 years ago
parent
commit
dafa05e65f
46 changed files with 7803 additions and 11 deletions
  1. 2 0
      .gitignore
  2. 2 2
      Makefile.am
  3. 1 0
      SERVICES
  4. 26 1
      configure.ac
  5. 5 0
      lib/log.c
  6. 3 1
      lib/log.h
  7. 14 0
      lib/memtypes.c
  8. 2 0
      lib/route_types.txt
  9. 35 0
      nhrpd/Makefile.am
  10. 145 0
      nhrpd/README.kernel
  11. 137 0
      nhrpd/README.nhrpd
  12. 42 0
      nhrpd/debug.h
  13. 153 0
      nhrpd/linux.c
  14. 191 0
      nhrpd/list.h
  15. 24 0
      nhrpd/netlink.h
  16. 275 0
      nhrpd/netlink_arp.c
  17. 141 0
      nhrpd/netlink_gre.c
  18. 341 0
      nhrpd/nhrp_cache.c
  19. 280 0
      nhrpd/nhrp_event.c
  20. 404 0
      nhrpd/nhrp_interface.c
  21. 246 0
      nhrpd/nhrp_main.c
  22. 369 0
      nhrpd/nhrp_nhs.c
  23. 312 0
      nhrpd/nhrp_packet.c
  24. 860 0
      nhrpd/nhrp_peer.c
  25. 128 0
      nhrpd/nhrp_protocol.h
  26. 345 0
      nhrpd/nhrp_route.c
  27. 402 0
      nhrpd/nhrp_shortcut.c
  28. 217 0
      nhrpd/nhrp_vc.c
  29. 928 0
      nhrpd/nhrp_vty.c
  30. 400 0
      nhrpd/nhrpd.h
  31. 5 0
      nhrpd/os.h
  32. 49 0
      nhrpd/reqid.c
  33. 190 0
      nhrpd/resolver.c
  34. 482 0
      nhrpd/vici.c
  35. 24 0
      nhrpd/vici.h
  36. 219 0
      nhrpd/zbuf.c
  37. 189 0
      nhrpd/zbuf.h
  38. 160 0
      nhrpd/znl.c
  39. 29 0
      nhrpd/znl.h
  40. 1 0
      vtysh/Makefile.am
  41. 1 0
      vtysh/vtysh.c
  42. 3 2
      vtysh/vtysh.h
  43. 1 0
      zebra/client_main.c
  44. 2 0
      zebra/zebra_rib.c
  45. 16 5
      zebra/zebra_rnh.c
  46. 2 0
      zebra/zebra_vty.c

+ 2 - 0
.gitignore

@@ -27,6 +27,7 @@ quagga-[0-9.][0-9.][0-9.]*.tar.gz
 quagga-[0-9.][0-9.][0-9.]*.tar.gz.asc
 .nfs*
 libtool
+.libs
 .arch-inventory
 .arch-ids
 {arch}
@@ -34,6 +35,7 @@ build
 .msg
 .rebase-*
 *~
+*.o
 *.loT
 m4/*.m4
 !m4/ax_sys_weak_alias.m4

+ 2 - 2
Makefile.am

@@ -1,10 +1,10 @@
 ## Process this file with automake to produce Makefile.in.
 
-SUBDIRS = lib qpb fpm @ZEBRA@ @BGPD@ @RIPD@ @RIPNGD@ @OSPFD@ @OSPF6D@ \
+SUBDIRS = lib qpb fpm @ZEBRA@ @BGPD@ @RIPD@ @RIPNGD@ @OSPFD@ @OSPF6D@ @NHRPD@ \
          @ISISD@ @PIMD@ @WATCHQUAGGA@ @VTYSH@ @OSPFCLIENT@ @DOC@ m4 @pkgsrcdir@ \
          redhat @SOLARIS@ tests
 
-DIST_SUBDIRS = lib qpb fpm zebra bgpd ripd ripngd ospfd ospf6d \
+DIST_SUBDIRS = lib qpb fpm zebra bgpd ripd ripngd ospfd ospf6d nhrpd \
 	  isisd watchquagga vtysh ospfclient doc m4 pkgsrc redhat tests \
 	  solaris pimd
 

+ 1 - 0
SERVICES

@@ -18,3 +18,4 @@ ospf6d		2606/tcp
 ospfapi		2607/tcp
 isisd		2608/tcp
 pimd		2611/tcp
+nhrpd		2612/tcp

+ 26 - 1
configure.ac

@@ -76,6 +76,7 @@ AC_PROG_CPP
 AM_PROG_CC_C_O
 AC_PROG_RANLIB
 AC_PROG_EGREP
+PKG_PROG_PKG_CONFIG
 
 dnl autoconf 2.59 appears not to support AC_PROG_SED
 dnl AC_PROG_SED
@@ -245,6 +246,8 @@ AC_ARG_ENABLE(ospfd,
   AS_HELP_STRING([--disable-ospfd], [do not build ospfd]))
 AC_ARG_ENABLE(ospf6d,
   AS_HELP_STRING([--disable-ospf6d], [do not build ospf6d]))
+AC_ARG_ENABLE(nhrpd,
+  AS_HELP_STRING([--disable-nhrpd], [do not build nhrpd]))
 AC_ARG_ENABLE(watchquagga,
   AS_HELP_STRING([--disable-watchquagga], [do not build watchquagga]))
 AC_ARG_ENABLE(isisd,
@@ -1190,6 +1193,17 @@ else
 fi
 AM_CONDITIONAL(OSPFD, test "x$OSPFD" = "xospfd")
 
+if test x"$opsys" != x"gnu-linux"; then
+	dnl NHRPd works currently with Linux only.
+	enable_nhrpd="no"
+fi
+if test "${enable_nhrpd}" = "no";then
+  NHRPD=""
+else
+  NHRPD="nhrpd"
+fi
+AM_CONDITIONAL(NHRPD, test "x$NHRPD" = "xnhrpd")
+
 if test "${enable_watchquagga}" = "no";then
   WATCHQUAGGA=""
 else
@@ -1245,6 +1259,7 @@ AC_SUBST(RIPD)
 AC_SUBST(RIPNGD)
 AC_SUBST(OSPFD)
 AC_SUBST(OSPF6D)
+AC_SUBST(NHRPD)
 AC_SUBST(WATCHQUAGGA)
 AC_SUBST(ISISD)
 AC_SUBST(PIMD)
@@ -1280,6 +1295,14 @@ AC_SUBST(HAVE_LIBPCREPOSIX)
 AC_SUBST(LIB_REGEX)
 
 dnl ------------------
+dnl check C-Ares library
+dnl ------------------
+if test "${enable_nhrpd}" != "no";then
+   PKG_CHECK_MODULES([CARES], [libcares])
+fi
+
+
+dnl ------------------
 dnl check Net-SNMP library
 dnl ------------------
 if test "${enable_snmp}" != ""; then
@@ -1555,6 +1578,7 @@ AC_DEFINE_UNQUOTED(PATH_RIPNGD_PID, "$quagga_statedir/ripngd.pid",ripngd PID)
 AC_DEFINE_UNQUOTED(PATH_BGPD_PID, "$quagga_statedir/bgpd.pid",bgpd PID)
 AC_DEFINE_UNQUOTED(PATH_OSPFD_PID, "$quagga_statedir/ospfd.pid",ospfd PID)
 AC_DEFINE_UNQUOTED(PATH_OSPF6D_PID, "$quagga_statedir/ospf6d.pid",ospf6d PID)
+AC_DEFINE_UNQUOTED(PATH_NHRPD_PID, "$quagga_statedir/nhrpd.pid",nhrpd PID)
 AC_DEFINE_UNQUOTED(PATH_ISISD_PID, "$quagga_statedir/isisd.pid",isisd PID)
 AC_DEFINE_UNQUOTED(PATH_PIMD_PID, "$quagga_statedir/pimd.pid",pimd PID)
 AC_DEFINE_UNQUOTED(PATH_WATCHQUAGGA_PID, "$quagga_statedir/watchquagga.pid",watchquagga PID)
@@ -1565,6 +1589,7 @@ AC_DEFINE_UNQUOTED(RIPNG_VTYSH_PATH, "$quagga_statedir/ripngd.vty",ripng vty soc
 AC_DEFINE_UNQUOTED(BGP_VTYSH_PATH, "$quagga_statedir/bgpd.vty",bgpd vty socket)
 AC_DEFINE_UNQUOTED(OSPF_VTYSH_PATH, "$quagga_statedir/ospfd.vty",ospfd vty socket)
 AC_DEFINE_UNQUOTED(OSPF6_VTYSH_PATH, "$quagga_statedir/ospf6d.vty",ospf6d vty socket)
+AC_DEFINE_UNQUOTED(NHRP_VTYSH_PATH, "$quagga_statedir/nhrpd.vty",nhrpd vty socket)
 AC_DEFINE_UNQUOTED(ISIS_VTYSH_PATH, "$quagga_statedir/isisd.vty",isisd vty socket)
 AC_DEFINE_UNQUOTED(PIM_VTYSH_PATH, "$quagga_statedir/pimd.vty",pimd vty socket)
 AC_DEFINE_UNQUOTED(DAEMON_VTY_DIR, "$quagga_statedir",daemon vty directory)
@@ -1592,7 +1617,7 @@ AC_CONFIG_FILES([Makefile lib/Makefile qpb/Makefile zebra/Makefile ripd/Makefile
 	  ripngd/Makefile bgpd/Makefile ospfd/Makefile watchquagga/Makefile
 	  ospf6d/Makefile isisd/Makefile vtysh/Makefile
 	  doc/Makefile ospfclient/Makefile tests/Makefile m4/Makefile
-	  pimd/Makefile
+	  pimd/Makefile nhrpd/Makefile
 	  tests/bgpd.tests/Makefile
 	  tests/libzebra.tests/Makefile
 	  redhat/Makefile

+ 5 - 0
lib/log.c

@@ -53,6 +53,7 @@ const char *zlog_proto_names[] =
   "ISIS",
   "PIM",
   "MASC",
+  "NHRP",
   NULL,
 };
 
@@ -986,6 +987,8 @@ proto_redistnum(int afi, const char *s)
 	return ZEBRA_ROUTE_BGP;
       else if (strncmp (s, "ba", 2) == 0)
 	return ZEBRA_ROUTE_BABEL;
+      else if (strncmp (s, "n", 1) == 0)
+	return ZEBRA_ROUTE_NHRP;
     }
   if (afi == AFI_IP6)
     {
@@ -1005,6 +1008,8 @@ proto_redistnum(int afi, const char *s)
 	return ZEBRA_ROUTE_BGP;
       else if (strncmp (s, "ba", 2) == 0)
 	return ZEBRA_ROUTE_BABEL;
+      else if (strncmp (s, "n", 1) == 0)
+	return ZEBRA_ROUTE_NHRP;
     }
   return -1;
 }

+ 3 - 1
lib/log.h

@@ -24,6 +24,7 @@
 #define _ZEBRA_LOG_H
 
 #include <syslog.h>
+#include <stdio.h>
 
 /* Here is some guidance on logging levels to use:
  *
@@ -54,7 +55,8 @@ typedef enum
   ZLOG_OSPF6,
   ZLOG_ISIS,
   ZLOG_PIM,
-  ZLOG_MASC
+  ZLOG_MASC,
+  ZLOG_NHRP,
 } zlog_proto_t;
 
 /* If maxlvl is set to ZLOG_DISABLED, then no messages will be sent

+ 14 - 0
lib/memtypes.c

@@ -280,6 +280,19 @@ struct memory_list memory_list_pim[] =
   { -1, NULL },
 };
 
+struct memory_list memory_list_nhrp[] =
+{
+  { MTYPE_NHRP_IF,		"NHRP interface"		},
+  { MTYPE_NHRP_VC,		"NHRP virtual connection"	},
+  { MTYPE_NHRP_PEER,		"NHRP peer entry"		},
+  { MTYPE_NHRP_CACHE,		"NHRP cache entry"		},
+  { MTYPE_NHRP_NHS,		"NHRP next hop server"		},
+  { MTYPE_NHRP_REGISTRATION,	"NHRP registration entries"	},
+  { MTYPE_NHRP_SHORTCUT,	"NHRP shortcut"			},
+  { MTYPE_NHRP_ROUTE,		"NHRP routing entry"		},
+  { -1, NULL }
+};
+
 struct memory_list memory_list_vtysh[] =
 {
   { MTYPE_VTYSH_CONFIG,		"Vtysh configuration",		},
@@ -297,5 +310,6 @@ struct mlist mlists[] __attribute__ ((unused)) = {
   { memory_list_isis,	"ISIS"	},
   { memory_list_bgp,	"BGP"	},
   { memory_list_pim,	"PIM"	},
+  { memory_list_nhrp,	"NHRP"	},
   { NULL, NULL},
 };

+ 2 - 0
lib/route_types.txt

@@ -60,6 +60,7 @@ ZEBRA_ROUTE_PIM,	pim,	   pimd,   'P', 1, 0, "PIM"
 ZEBRA_ROUTE_HSLS,       hsls,      hslsd,  'H', 0, 0, "HSLS"
 ZEBRA_ROUTE_OLSR,       olsr,      olsrd,  'o', 0, 0, "OLSR"
 ZEBRA_ROUTE_BABEL,      babel,     babeld, 'A', 1, 1, "Babel"
+ZEBRA_ROUTE_NHRP,       nhrp,      nhrpd,  'N', 1, 1, "NHRP"
 
 ## help strings
 ZEBRA_ROUTE_SYSTEM, "Reserved route type, for internal use only"
@@ -76,3 +77,4 @@ ZEBRA_ROUTE_PIM,    "Protocol Independent Multicast (PIM)"
 ZEBRA_ROUTE_HSLS,   "Hazy-Sighted Link State Protocol (HSLS)"
 ZEBRA_ROUTE_OLSR,   "Optimised Link State Routing (OLSR)"
 ZEBRA_ROUTE_BABEL,  "Babel routing protocol (Babel)"
+ZEBRA_ROUTE_NHRP,   "Next Hop Resolution Protocol (NHRP)"

+ 35 - 0
nhrpd/Makefile.am

@@ -0,0 +1,35 @@
+## Process this file with automake to produce Makefile.in.
+
+AM_CPPFLAGS = -I.. -I$(top_srcdir) -I$(top_srcdir)/lib -I$(top_builddir)/lib -DQUAGGA_NO_DEPRECATED_INTERFACES
+DEFS = @DEFS@ @CARES_CFLAGS@ -DSYSCONFDIR=\"$(sysconfdir)/\"
+INSTALL_SDATA=@INSTALL@ -m 600
+
+AM_CFLAGS = $(PICFLAGS) #$(WERROR)
+AM_LDFLAGS = $(PICLDFLAGS)
+
+sbin_PROGRAMS = nhrpd
+
+nhrpd_SOURCES = \
+	zbuf.c \
+	znl.c \
+	resolver.c \
+	linux.c \
+	netlink_arp.c \
+	netlink_gre.c \
+	vici.c \
+	reqid.c \
+	nhrp_event.c \
+	nhrp_packet.c \
+	nhrp_interface.c \
+	nhrp_vc.c \
+	nhrp_peer.c \
+	nhrp_cache.c \
+	nhrp_nhs.c \
+	nhrp_route.c \
+	nhrp_shortcut.c \
+	nhrp_vty.c \
+	nhrp_main.c
+
+nhrpd_LDADD = ../lib/libzebra.la @LIBCAP@ @CARES_LIBS@
+
+#dist_examples_DATA = nhrpd.conf.sample

+ 145 - 0
nhrpd/README.kernel

@@ -0,0 +1,145 @@
+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
+    (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.
+

+ 137 - 0
nhrpd/README.nhrpd

@@ -0,0 +1,137 @@
+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 (hardcoded now as /var/run/charon.vici).
+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
+

+ 42 - 0
nhrpd/debug.h

@@ -0,0 +1,42 @@
+#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;
+
+#if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L
+
+#define debugf(level, ...) \
+	do {						\
+		if (unlikely(debug_flags & level))	\
+			zlog_debug(__VA_ARGS__);	\
+	} while(0)
+
+#elif defined __GNUC__
+
+#define debugf(level, _args...)				\
+	do {						\
+		if (unlikely(debug_flags & level))	\
+			zlog_debug(_args);		\
+	} while(0)
+
+#else
+
+static inline void debugf(int level, const char *format, ...) { }
+
+#endif
+

+ 153 - 0
nhrpd/linux.c

@@ -0,0 +1,153 @@
+/* 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 <fcntl.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <asm/types.h>
+#include <arpa/inet.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/ip.h>
+#include <linux/if_arp.h>
+#include <linux/if_tunnel.h>
+
+#include "nhrp_protocol.h"
+#include "os.h"
+#include "netlink.h"
+
+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)
+{
+	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;
+
+	if (addrlen > sizeof(lladdr.sll_addr))
+		return -1;
+
+	memset(&lladdr, 0, sizeof(lladdr));
+	lladdr.sll_family = AF_PACKET;
+	lladdr.sll_protocol = htons(ETH_P_NHRP);
+	lladdr.sll_ifindex = ifindex;
+	lladdr.sll_halen = addrlen;
+	memcpy(lladdr.sll_addr, addr, addrlen);
+
+	status = sendmsg(nhrp_socket_fd, &msg, 0);
+	if (status < 0)
+		return -1;
+
+	return 0;
+}
+
+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;
+
+	strncpy(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[256];
+	int fd, ret = -1;
+
+	sprintf(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 = -1;
+
+	switch (af) {
+	case AF_INET:
+		ret  = linux_icmp_redirect_off("all");
+		ret |= linux_icmp_redirect_off(ifname);
+		ret |= netlink_configure_arp(ifindex, AF_INET);
+		ret |= linux_configure_arp(ifname, 1);
+		break;
+	}
+
+	return ret;
+}

+ 191 - 0
nhrpd/list.h

@@ -0,0 +1,191 @@
+/* Linux kernel style list handling function
+ *
+ * Written from scratch by Timo Teräs <timo.teras@iki.fi>, but modeled
+ * after the linux kernel code.
+ *
+ * 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 LIST_H
+#define LIST_H
+
+#ifndef NULL
+#define NULL 0L
+#endif
+
+#ifndef offsetof
+#ifdef __compiler_offsetof
+#define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER)
+#else
+#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
+#endif
+#endif
+
+#ifndef container_of
+#define container_of(ptr, type, member) ({                      \
+        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
+        (type *)( (char *)__mptr - offsetof(type,member) );})
+#endif
+
+struct hlist_head {
+	struct hlist_node *first;
+};
+
+struct hlist_node {
+	struct hlist_node *next;
+	struct hlist_node **pprev;
+};
+
+static inline int hlist_empty(const struct hlist_head *h)
+{
+	return !h->first;
+}
+
+static inline int hlist_hashed(const struct hlist_node *n)
+{
+	return n->pprev != NULL;
+}
+
+static inline void hlist_del(struct hlist_node *n)
+{
+	struct hlist_node *next = n->next;
+	struct hlist_node **pprev = n->pprev;
+
+	*pprev = next;
+	if (next)
+		next->pprev = pprev;
+
+	n->next = NULL;
+	n->pprev = NULL;
+}
+
+static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
+{
+	struct hlist_node *first = h->first;
+
+	n->next = first;
+	if (first)
+		first->pprev = &n->next;
+	n->pprev = &h->first;
+	h->first = n;
+}
+
+static inline void hlist_add_after(struct hlist_node *n, struct hlist_node *prev)
+{
+	n->next = prev->next;
+	n->pprev = &prev->next;
+	prev->next = n;
+}
+
+static inline struct hlist_node **hlist_tail_ptr(struct hlist_head *h)
+{
+	struct hlist_node *n = h->first;
+	if (n == NULL)
+		return &h->first;
+	while (n->next != NULL)
+		n = n->next;
+	return &n->next;
+}
+
+#define hlist_entry(ptr, type, member) container_of(ptr,type,member)
+
+#define hlist_for_each(pos, head) \
+	for (pos = (head)->first; pos; pos = pos->next)
+
+#define hlist_for_each_safe(pos, n, head) \
+	for (pos = (head)->first; pos && ({ n = pos->next; 1; }); pos = n)
+
+#define hlist_for_each_entry(tpos, pos, head, member)			 \
+	for (pos = (head)->first; pos && 				 \
+		({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+	     pos = pos->next)
+
+#define hlist_for_each_entry_safe(tpos, pos, n, head, member) 		 \
+	for (pos = (head)->first;					 \
+	     pos && ({ n = pos->next; 1; }) &&				 \
+		({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+	     pos = n)
+
+
+struct list_head {
+	struct list_head *next, *prev;
+};
+
+#define LIST_INITIALIZER(l) { .next = &l, .prev = &l }
+
+static inline void list_init(struct list_head *list)
+{
+	list->next = list;
+	list->prev = list;
+}
+
+static inline void __list_add(struct list_head *new,
+			      struct list_head *prev,
+			      struct list_head *next)
+{
+	next->prev = new;
+	new->next = next;
+	new->prev = prev;
+	prev->next = new;
+}
+
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+	__list_add(new, head, head->next);
+}
+
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+	__list_add(new, head->prev, head);
+}
+
+static inline void __list_del(struct list_head * prev, struct list_head * next)
+{
+	next->prev = prev;
+	prev->next = next;
+}
+
+static inline void list_del(struct list_head *entry)
+{
+	__list_del(entry->prev, entry->next);
+	entry->next = NULL;
+	entry->prev = NULL;
+}
+
+static inline int list_hashed(const struct list_head *n)
+{
+	return n->next != n && n->next != NULL;
+}
+
+static inline int list_empty(const struct list_head *n)
+{
+	return !list_hashed(n);
+}
+
+#define list_next(ptr, type, member) \
+	(list_hashed(ptr) ? container_of((ptr)->next,type,member) : NULL)
+
+#define list_entry(ptr, type, member) container_of(ptr,type,member)
+
+#define list_for_each(pos, head) \
+	for (pos = (head)->next; pos != (head); pos = pos->next)
+
+#define list_for_each_safe(pos, n, head) \
+	for (pos = (head)->next, n = pos->next; pos != (head); \
+		pos = n, n = pos->next)
+
+#define list_for_each_entry(pos, head, member)				\
+	for (pos = list_entry((head)->next, typeof(*pos), member);	\
+	     &pos->member != (head); 					\
+	     pos = list_entry(pos->member.next, typeof(*pos), member))
+
+#define list_for_each_entry_safe(pos, n, head, member)			\
+	for (pos = list_entry((head)->next, typeof(*pos), member),	\
+		n = list_entry(pos->member.next, typeof(*pos), member);	\
+	     &pos->member != (head); 					\
+	     pos = n, n = list_entry(n->member.next, typeof(*n), member))
+
+#endif

+ 24 - 0
nhrpd/netlink.h

@@ -0,0 +1,24 @@
+/* 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 <stdint.h>
+
+union sockunion;
+struct interface;
+
+extern int netlink_nflog_group;
+extern int netlink_req_fd;
+
+int netlink_init(void);
+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);
+
+void netlink_gre_get_info(unsigned int ifindex, uint32_t *gre_key, unsigned int *link_index, struct in_addr *saddr);
+void netlink_gre_set_link(unsigned int ifindex, unsigned int link_index);

+ 275 - 0
nhrpd/netlink_arp.c

@@ -0,0 +1,275 @@
+/* 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.
+ */
+
+#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 "nhrpd.h"
+#include "netlink.h"
+#include "znl.h"
+
+int netlink_req_fd = -1;
+int netlink_nflog_group;
+static int netlink_log_fd = -1;
+static struct thread *netlink_log_thread;
+static int netlink_listen_fd = -1;
+
+typedef void (*netlink_dispatch_f)(struct nlmsghdr *msg, struct zbuf *zb);
+
+void netlink_update_binding(struct interface *ifp, union sockunion *proto, union sockunion *nbma)
+{
+	struct nlmsghdr *n;
+	struct ndmsg *ndm;
+	struct zbuf *zb = zbuf_alloc(512);
+
+	n = znl_nlmsg_push(zb, nbma ? RTM_NEWNEIGH : RTM_DELNEIGH, NLM_F_REQUEST | NLM_F_REPLACE | NLM_F_CREATE);
+	ndm = znl_push(zb, sizeof(*ndm));
+	*ndm = (struct ndmsg) {
+		.ndm_family = sockunion_family(proto),
+		.ndm_ifindex = ifp->ifindex,
+		.ndm_type = RTN_UNICAST,
+		.ndm_state = nbma ? NUD_REACHABLE : NUD_FAILED,
+	};
+	znl_rta_push(zb, NDA_DST, sockunion_get_addr(proto), family2addrsize(sockunion_family(proto)));
+	if (nbma)
+		znl_rta_push(zb, NDA_LLADDR, sockunion_get_addr(nbma), family2addrsize(sockunion_family(nbma)));
+	znl_nlmsg_complete(zb, n);
+	zbuf_send(zb, netlink_req_fd);
+	zbuf_recv(zb, netlink_req_fd);
+	zbuf_free(zb);
+}
+
+static void netlink_neigh_msg(struct nlmsghdr *msg, struct zbuf *zb)
+{
+	struct ndmsg *ndm;
+	struct rtattr *rta;
+	struct nhrp_cache *c;
+	struct interface *ifp;
+	struct zbuf payload;
+	union sockunion addr;
+	size_t len;
+	char buf[SU_ADDRSTRLEN];
+	int state;
+
+	ndm = znl_pull(zb, sizeof(*ndm));
+	if (!ndm) return;
+
+	sockunion_family(&addr) = AF_UNSPEC;
+	while ((rta = znl_rta_pull(zb, &payload)) != NULL) {
+		len = zbuf_used(&payload);
+		switch (rta->rta_type) {
+		case NDA_DST:
+			sockunion_set(&addr, ndm->ndm_family, zbuf_pulln(&payload, len), len);
+			break;
+		}
+	}
+
+	ifp = if_lookup_by_index(ndm->ndm_ifindex);
+	if (!ifp || sockunion_family(&addr) == AF_UNSPEC)
+		return;
+
+	c = nhrp_cache_get(ifp, &addr, 0);
+	if (!c)
+		return;
+
+	if (msg->nlmsg_type == RTM_GETNEIGH) {
+		debugf(NHRP_DEBUG_KERNEL, "Netlink: who-has %s dev %s",
+			sockunion2str(&addr, buf, sizeof buf),
+			ifp->name);
+
+		if (c->cur.type >= NHRP_CACHE_CACHED) {
+			nhrp_cache_set_used(c, 1);
+			netlink_update_binding(ifp, &addr, &c->cur.peer->vc->remote.nbma);
+		}
+	} else {
+		debugf(NHRP_DEBUG_KERNEL, "Netlink: update %s dev %s nud %x",
+			sockunion2str(&addr, buf, sizeof buf),
+			ifp->name, ndm->ndm_state);
+
+		state = (msg->nlmsg_type == RTM_NEWNEIGH) ? ndm->ndm_state : NUD_FAILED;
+		nhrp_cache_set_used(c, state == NUD_REACHABLE);
+	}
+}
+
+static int netlink_route_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)) != 0) {
+			debugf(NHRP_DEBUG_KERNEL, "Netlink: Received msg_type %u, msg_flags %u",
+				n->nlmsg_type, n->nlmsg_flags);
+			switch (n->nlmsg_type) {
+			case RTM_GETNEIGH:
+			case RTM_NEWNEIGH:
+			case RTM_DELNEIGH:
+				netlink_neigh_msg(n, &payload);
+				break;
+			}
+		}
+	}
+
+	thread_add_read(master, netlink_route_recv, 0, fd);
+
+	return 0;
+}
+
+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));
+	if (!ifp)
+		return;
+
+	nhrp_peer_send_indication(ifp, htons(pkthdr->hw_protocol), &pktpl);
+}
+
+static int netlink_log_recv(struct thread *t)
+{
+	uint8_t buf[ZNL_BUFFER_SIZE];
+	int fd = THREAD_FD(t);
+	struct zbuf payload, zb;
+	struct nlmsghdr *n;
+
+	netlink_log_thread = NULL;
+
+	zbuf_init(&zb, buf, sizeof(buf), 0);
+	while (zbuf_recv(&zb, fd) > 0) {
+		while ((n = znl_nlmsg_pull(&zb, &payload)) != 0) {
+			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_READ_ON(master, netlink_log_thread, netlink_log_recv, 0, netlink_log_fd);
+
+	return 0;
+}
+
+void netlink_set_nflog_group(int nlgroup)
+{
+	if (netlink_log_fd >= 0) {
+		THREAD_OFF(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);
+		netlink_log_register(netlink_log_fd, nlgroup);
+		THREAD_READ_ON(master, netlink_log_thread, netlink_log_recv, 0, netlink_log_fd);
+	}
+}
+
+int netlink_init(void)
+{
+	netlink_req_fd = znl_open(NETLINK_ROUTE, 0);
+	netlink_listen_fd = znl_open(NETLINK_ROUTE, RTMGRP_NEIGH);
+	thread_add_read(master, netlink_route_recv, 0, netlink_listen_fd);
+
+	return 0;
+}
+
+int netlink_configure_arp(unsigned int ifindex, int pf)
+{
+	struct nlmsghdr *n;
+	struct ndtmsg *ndtm;
+	struct rtattr *rta;
+	struct zbuf *zb = zbuf_alloc(512);
+	int r;
+
+	n = znl_nlmsg_push(zb, RTM_SETNEIGHTBL, NLM_F_REQUEST | NLM_F_REPLACE);
+	ndtm = znl_push(zb, sizeof(*ndtm));
+	*ndtm = (struct ndtmsg) {
+		.ndtm_family = pf,
+	};
+
+	znl_rta_push(zb, NDTA_NAME, pf == AF_INET ? "arp_cache" : "ndisc_cache", 10);
+
+	rta = znl_rta_nested_push(zb, NDTA_PARMS);
+	znl_rta_push_u32(zb, NDTPA_IFINDEX, ifindex);
+	znl_rta_push_u32(zb, NDTPA_APP_PROBES, 1);
+	znl_rta_push_u32(zb, NDTPA_MCAST_PROBES, 0);
+	znl_rta_push_u32(zb, NDTPA_UCAST_PROBES, 0);
+	znl_rta_nested_complete(zb, rta);
+
+	znl_nlmsg_complete(zb, n);
+	r = zbuf_send(zb, netlink_req_fd);
+	zbuf_recv(zb, netlink_req_fd);
+	zbuf_free(zb);
+
+	return r;
+}

+ 141 - 0
nhrpd/netlink_gre.c

@@ -0,0 +1,141 @@
+/* NHRP netlink/GRE tunnel configuration 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.
+ */
+
+#include <netinet/in.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/if.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/if_tunnel.h>
+
+#include "debug.h"
+#include "netlink.h"
+#include "znl.h"
+
+static int __netlink_gre_get_data(struct zbuf *zb, struct zbuf *data, int ifindex)
+{
+	struct nlmsghdr *n;
+	struct ifinfomsg *ifi;
+	struct zbuf payload, rtapayload;
+	struct rtattr *rta;
+
+	debugf(NHRP_DEBUG_KERNEL, "netlink-link-gre: get-info %u", ifindex);
+
+	n = znl_nlmsg_push(zb, RTM_GETLINK, NLM_F_REQUEST);
+	ifi = znl_push(zb, sizeof(*ifi));
+	*ifi = (struct ifinfomsg) {
+		.ifi_index = ifindex,
+	};
+	znl_nlmsg_complete(zb, n);
+
+	if (zbuf_send(zb, netlink_req_fd) < 0 ||
+	    zbuf_recv(zb, netlink_req_fd) < 0)
+		return -1;
+
+	n = znl_nlmsg_pull(zb, &payload);
+	if (!n) return -1;
+
+	if (n->nlmsg_type != RTM_NEWLINK)
+		return -1;
+
+	ifi = znl_pull(&payload, sizeof(struct ifinfomsg));
+	if (!ifi)
+		return -1;
+
+	debugf(NHRP_DEBUG_KERNEL, "netlink-link-gre: ifindex %u, receive msg_type %u, msg_flags %u",
+		ifi->ifi_index, n->nlmsg_type, n->nlmsg_flags);
+
+	if (ifi->ifi_index != ifindex)
+		return -1;
+
+	while ((rta = znl_rta_pull(&payload, &rtapayload)) != NULL)
+		if (rta->rta_type == IFLA_LINKINFO)
+			break;
+	if (!rta) return -1;
+
+	payload = rtapayload;
+	while ((rta = znl_rta_pull(&payload, &rtapayload)) != NULL)
+		if (rta->rta_type == IFLA_INFO_DATA)
+			break;
+	if (!rta) return -1;
+
+	*data = rtapayload;
+	return 0;
+}
+
+void netlink_gre_get_info(unsigned int ifindex, uint32_t *gre_key, unsigned int *link_index, struct in_addr *saddr)
+{
+	struct zbuf *zb = zbuf_alloc(8192), data, rtapl;
+	struct rtattr *rta;
+
+	*link_index = 0;
+	*gre_key = 0;
+	saddr->s_addr = 0;
+
+	if (__netlink_gre_get_data(zb, &data, ifindex) < 0)
+		goto err;
+
+	while ((rta = znl_rta_pull(&data, &rtapl)) != NULL) {
+		switch (rta->rta_type) {
+		case IFLA_GRE_LINK:
+			*link_index = zbuf_get32(&rtapl);
+			break;
+		case IFLA_GRE_IKEY:
+		case IFLA_GRE_OKEY:
+			*gre_key = zbuf_get32(&rtapl);
+			break;
+		case IFLA_GRE_LOCAL:
+			saddr->s_addr = zbuf_get32(&rtapl);
+			break;
+		}
+	}
+err:
+	zbuf_free(zb);
+}
+
+void netlink_gre_set_link(unsigned int ifindex, unsigned int link_index)
+{
+	struct nlmsghdr *n;
+	struct ifinfomsg *ifi;
+	struct rtattr *rta_info, *rta_data, *rta;
+	struct zbuf *zr = zbuf_alloc(8192), data, rtapl;
+	struct zbuf *zb = zbuf_alloc(8192);
+	size_t len;
+
+	if (__netlink_gre_get_data(zr, &data, ifindex) < 0)
+		goto err;
+
+	n = znl_nlmsg_push(zb, RTM_NEWLINK, NLM_F_REQUEST);
+	ifi = znl_push(zb, sizeof(*ifi));
+	*ifi = (struct ifinfomsg) {
+		.ifi_index = ifindex,
+	};
+	rta_info = znl_rta_nested_push(zb, IFLA_LINKINFO);
+	znl_rta_push(zb, IFLA_INFO_KIND, "gre", 3);
+	rta_data = znl_rta_nested_push(zb, IFLA_INFO_DATA);
+
+	znl_rta_push_u32(zb, IFLA_GRE_LINK, link_index);
+	while ((rta = znl_rta_pull(&data, &rtapl)) != NULL) {
+		if (rta->rta_type == IFLA_GRE_LINK)
+			continue;
+		len = zbuf_used(&rtapl);
+		znl_rta_push(zb, rta->rta_type, zbuf_pulln(&rtapl, len), len);
+	}
+
+	znl_rta_nested_complete(zb, rta_data);
+	znl_rta_nested_complete(zb, rta_info);
+
+	znl_nlmsg_complete(zb, n);
+	zbuf_send(zb, netlink_req_fd);
+	zbuf_recv(zb, netlink_req_fd);
+err:
+	zbuf_free(zb);
+	zbuf_free(zr);
+}

+ 341 - 0
nhrpd/nhrp_cache.c

@@ -0,0 +1,341 @@
+/* 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"
+
+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(void *peer_data)
+{
+	struct nhrp_cache *p = peer_data;
+	return sockunion_hash(&p->remote_addr);
+}
+
+static int 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));
+	if (p) {
+		*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;
+
+	zassert(c->cur.type == NHRP_CACHE_INVALID && c->cur.peer == NULL);
+	zassert(c->new.type == NHRP_CACHE_INVALID && c->new.peer == NULL);
+	nhrp_cache_counts[c->cur.type]--;
+	notifier_call(&c->notifier_list, NOTIFY_CACHE_DELETE);
+	zassert(!notifier_active(&c->notifier_list));
+	hash_release(nifp->cache_hash, c);
+	XFREE(MTYPE_NHRP_CACHE, c);
+}
+
+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);
+		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 int nhrp_cache_do_free(struct thread *t)
+{
+	struct nhrp_cache *c = THREAD_ARG(t);
+	c->t_timeout = NULL;
+	nhrp_cache_free(c);
+	return 0;
+}
+
+static int 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);
+	return 0;
+}
+
+static void nhrp_cache_update_route(struct nhrp_cache *c)
+{
+	struct prefix pfx;
+	struct nhrp_peer *p = c->cur.peer;
+
+	sockunion2hostprefix(&c->remote_addr, &pfx);
+
+	if (p && nhrp_peer_check(p, 1)) {
+		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 {
+		if (c->nhrp_route_installed) {
+			nhrp_route_update_nhrp(&pfx, NULL);
+			c->nhrp_route_installed = 0;
+		}
+		if (c->route_installed) {
+			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);
+		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 (list_hashed(&c->newpeer_notifier.notifier_entry))
+		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_TIMER_MSEC_ON(master, c->t_timeout, nhrp_cache_do_free, c, 10);
+		break;
+	default:
+		if (c->cur.expires)
+			THREAD_TIMER_ON(master, c->t_timeout, nhrp_cache_do_timeout, c, c->cur.expires - recent_relative_time().tv_sec);
+		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[SU_ADDRSTRLEN];
+
+	debugf(NHRP_DEBUG_COMMON, "cache: %s %s: %s",
+		c->ifp->name, sockunion2str(&c->remote_addr, buf, sizeof buf),
+		(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);
+		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 int 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");
+	return 0;
+}
+
+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_TIMER_ON(master, c->t_auth, nhrp_cache_do_auth_timeout, c, 10);
+		}
+		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)
+{
+	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;
+	}
+
+	nhrp_cache_reset_new(c);
+	if (c->cur.type == type && c->cur.peer == p && c->cur.mtu == mtu) {
+		if (holding_time > 0) c->cur.expires = recent_relative_time().tv_sec + 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);
+		nhrp_peer_unref(p);
+	} else {
+		c->new.type = type;
+		c->new.peer = p;
+		c->new.mtu = mtu;
+		if (nbma_oa) c->new.remote_nbma_natoa = *nbma_oa;
+
+		if (holding_time > 0)
+			c->new.expires = recent_relative_time().tv_sec + holding_time;
+		else if (holding_time < 0)
+			c->new.type = NHRP_CACHE_INVALID;
+
+		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_TIMER_ON(master, c->t_auth, nhrp_cache_do_auth_timeout, c, 60);
+		}
+	}
+	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;
+};
+
+static void nhrp_cache_iterator(struct hash_backet *b, void *ctx)
+{
+	struct nhrp_cache_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_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);
+}

+ 280 - 0
nhrpd/nhrp_event.c

@@ -0,0 +1,280 @@
+/* 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.
+ */
+
+#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 int 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_TIMER_MSEC_ON(master, evmgr->t_reconnect, evmgr_reconnect,
+				     evmgr, 10);
+}
+
+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);
+		sscanf(buf, "eventid=%d", &eventid);
+		sscanf(buf, "result=%63s", result);
+	}
+	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 int evmgr_read(struct thread *t)
+{
+	struct event_manager *evmgr = THREAD_ARG(t);
+	struct zbuf *ibuf = &evmgr->ibuf;
+	struct zbuf msg;
+
+	evmgr->t_read = NULL;
+	if (zbuf_read(ibuf, evmgr->fd, (size_t) -1) < 0) {
+		evmgr_connection_error(evmgr);
+		return 0;
+	}
+
+	/* Process all messages in buffer */
+	while (zbuf_may_pull_until(ibuf, "\n\n", &msg))
+		evmgr_recv_message(evmgr, &msg);
+
+	THREAD_READ_ON(master, evmgr->t_read, evmgr_read, evmgr, evmgr->fd);
+	return 0;
+}
+
+static int evmgr_write(struct thread *t)
+{
+	struct event_manager *evmgr = THREAD_ARG(t);
+	int r;
+
+	evmgr->t_write = NULL;
+	r = zbufq_write(&evmgr->obuf, evmgr->fd);
+	if (r > 0) {
+		THREAD_WRITE_ON(master, evmgr->t_write, evmgr_write, evmgr, evmgr->fd);
+	} else if (r < 0) {
+		evmgr_connection_error(evmgr);
+	}
+
+	return 0;
+}
+
+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_WRITE_ON(master, evmgr->t_write, evmgr_write, evmgr, evmgr->fd);
+}
+
+static int evmgr_reconnect(struct thread *t)
+{
+	struct event_manager *evmgr = THREAD_ARG(t);
+	int fd;
+
+	evmgr->t_reconnect = NULL;
+	if (evmgr->fd >= 0 || !nhrp_event_socket_path) return 0;
+
+	fd = sock_open_unix(nhrp_event_socket_path);
+	if (fd < 0) {
+		zlog_warn("%s: failure connecting nhrp-event socket: %s",
+			__PRETTY_FUNCTION__, strerror(errno));
+		zbufq_reset(&evmgr->obuf);
+		THREAD_TIMER_ON(master, evmgr->t_reconnect, evmgr_reconnect, evmgr, 10);
+		return 0;
+	}
+
+	zlog_info("Connected to Event Manager");
+	evmgr->fd = fd;
+	THREAD_READ_ON(master, evmgr->t_read, evmgr_read, evmgr, evmgr->fd);
+
+	return 0;
+}
+
+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_TIMER_MSEC_ON(master, evmgr->t_reconnect, evmgr_reconnect, evmgr, 10);
+}
+
+void evmgr_set_socket(const char *socket)
+{
+	if (nhrp_event_socket_path)
+		free((char *) nhrp_event_socket_path);
+	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);
+}
+

+ 404 - 0
nhrpd/nhrp_interface.c

@@ -0,0 +1,404 @@
+/* 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.
+ */
+
+#include <net/if_arp.h>
+#include "zebra.h"
+#include "linklist.h"
+#include "memory.h"
+#include "thread.h"
+
+#include "nhrpd.h"
+#include "os.h"
+#include "netlink.h"
+
+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));
+	if (!nifp) return 0;
+
+	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;
+		list_init(&ad->nhslist_head);
+	}
+
+	return 0;
+}
+
+static int nhrp_if_delete_hook(struct interface *ifp)
+{
+	XFREE(MTYPE_NHRP_IF, ifp->info);
+	return 0;
+}
+
+void nhrp_interface_init(void)
+{
+	if_add_hook(IF_NEW_HOOK,    nhrp_if_new_hook);
+	if_add_hook(IF_DELETE_HOOK, nhrp_if_delete_hook);
+}
+
+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 ||
+	    nifp->linkidx == nifp->nbmaifp->ifindex)
+		return;
+
+	nifp->linkidx = nifp->nbmaifp->ifindex;
+	debugf(NHRP_DEBUG_IF, "%s: bound device index changed to %d", ifp->name, nifp->linkidx);
+	netlink_gre_set_link(ifp->ifindex, nifp->linkidx);
+}
+
+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;
+	char buf[SU_ADDRSTRLEN];
+
+	switch (cmd) {
+	case NOTIFY_INTERFACE_CHANGED:
+		nhrp_interface_update_mtu(nifp->ifp, AFI_IP);
+		nhrp_interface_update_source(nifp->ifp);
+		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 %s",
+			nifp->ifp->name,
+			sockunion2str(&nifp->nbma, buf, sizeof buf));
+		break;
+	}
+}
+
+static void nhrp_interface_update_nbma(struct interface *ifp)
+{
+	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);
+
+	switch (ifp->ll_type) {
+	case ZEBRA_LLT_IPGRE: {
+			struct in_addr saddr = {0};
+			netlink_gre_get_info(ifp->ifindex, &nifp->grekey, &nifp->linkidx, &saddr);
+			debugf(NHRP_DEBUG_IF, "%s: GRE: %x %x %x", ifp->name, nifp->grekey, nifp->linkidx, saddr.s_addr);
+			if (saddr.s_addr)
+				sockunion_set(&nbma, AF_INET, (u_char *) &saddr.s_addr, sizeof(saddr.s_addr));
+			else if (!nbmaifp && nifp->linkidx != IFINDEX_INTERNAL)
+				nbmaifp = if_lookup_by_index(nifp->linkidx);
+		}
+		break;
+	default:
+		break;
+	}
+
+	if (nbmaifp)
+		nbmanifp = nbmaifp->info;
+
+	if (nbmaifp != nifp->nbmaifp) {
+		if (nifp->nbmaifp)
+			notifier_del(&nifp->nbmanifp_notifier);
+		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: %s is not a host prefix", ifp->name,
+			prefix2str(best->address, buf, sizeof buf));
+		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);
+	}
+
+	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);
+	}
+
+	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));
+			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_interface_add(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id)
+{
+	struct interface *ifp;
+
+	/* read and add the interface in the iflist. */
+	ifp = zebra_interface_add_read(client->ibuf, vrf_id);
+	if (ifp == NULL)
+		return 0;
+
+	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);
+
+	return 0;
+}
+
+int nhrp_interface_delete(int cmd, struct zclient *client,
+			  zebra_size_t length, vrf_id_t vrf_id)
+{
+	struct interface *ifp;
+	struct stream *s;
+
+	s = client->ibuf;
+	ifp = zebra_interface_state_read(s, vrf_id);
+	if (ifp == NULL)
+		return 0;
+
+	debugf(NHRP_DEBUG_IF, "if-delete: %s", ifp->name);
+	ifp->ifindex = IFINDEX_INTERNAL;
+	nhrp_interface_update(ifp);
+	/* if_delete(ifp); */
+	return 0;
+}
+
+int nhrp_interface_up(int cmd, struct zclient *client,
+		      zebra_size_t length, vrf_id_t vrf_id)
+{
+	struct interface *ifp;
+
+	ifp = zebra_interface_state_read(client->ibuf, vrf_id);
+	if (ifp == NULL)
+		return 0;
+
+	debugf(NHRP_DEBUG_IF, "if-up: %s", ifp->name);
+	nhrp_interface_update_nbma(ifp);
+
+	return 0;
+}
+
+int nhrp_interface_down(int cmd, struct zclient *client,
+			zebra_size_t length, vrf_id_t vrf_id)
+{
+	struct interface *ifp;
+
+	ifp = zebra_interface_state_read(client->ibuf, vrf_id);
+	if (ifp == NULL)
+		return 0;
+
+	debugf(NHRP_DEBUG_IF, "if-down: %s", ifp->name);
+	nhrp_interface_update(ifp);
+	return 0;
+}
+
+int nhrp_interface_address_add(int cmd, struct zclient *client,
+			       zebra_size_t length, vrf_id_t vrf_id)
+{
+	struct connected *ifc;
+	char buf[PREFIX_STRLEN];
+
+	ifc = zebra_interface_address_read(cmd, client->ibuf, vrf_id);
+	if (ifc == NULL)
+		return 0;
+
+	debugf(NHRP_DEBUG_IF, "if-addr-add: %s: %s",
+		ifc->ifp->name,
+		prefix2str(ifc->address, buf, sizeof buf));
+
+	nhrp_interface_update_address(ifc->ifp, family2afi(PREFIX_FAMILY(ifc->address)), 0);
+
+	return 0;
+}
+
+int nhrp_interface_address_delete(int cmd, struct zclient *client,
+				  zebra_size_t length, vrf_id_t vrf_id)
+{
+	struct connected *ifc;
+	char buf[PREFIX_STRLEN];
+
+	ifc = zebra_interface_address_read(cmd, client->ibuf, vrf_id);
+	if (ifc == NULL)
+		return 0;
+
+	debugf(NHRP_DEBUG_IF, "if-addr-del: %s: %s",
+		ifc->ifp->name,
+		prefix2str(ifc->address, buf, sizeof buf));
+
+	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)
+{
+	notifier_del(n);
+}
+
+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) free(nifp->ipsec_profile);
+	nifp->ipsec_profile = profile ? strdup(profile) : NULL;
+
+	if (nifp->ipsec_fallback_profile) free(nifp->ipsec_fallback_profile);
+	nifp->ipsec_fallback_profile = fallback_profile ? strdup(fallback_profile) : NULL;
+}
+
+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);
+}

+ 246 - 0
nhrpd/nhrp_main.c

@@ -0,0 +1,246 @@
+/* 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.
+ */
+
+#include <unistd.h>
+
+#include "zebra.h"
+#include "privs.h"
+#include "getopt.h"
+#include "thread.h"
+#include "sigevent.h"
+#include "version.h"
+#include "log.h"
+#include "memory.h"
+#include "command.h"
+
+#include "nhrpd.h"
+#include "netlink.h"
+
+unsigned int debug_flags = 0;
+
+struct thread_master *master;
+struct timeval current_time;
+static const char *pid_file = PATH_NHRPD_PID;
+static char config_default[] = SYSCONFDIR NHRP_DEFAULT_CONFIG;
+static char *config_file = NULL;
+static char *vty_addr = NULL;
+static int vty_port = NHRP_VTY_PORT;
+static int do_daemonise = 0;
+
+/* nhrpd options. */
+struct option longopts[] = {
+	{ "daemon",      no_argument,       NULL, 'd'},
+	{ "config_file", required_argument, NULL, 'f'},
+	{ "pid_file",    required_argument, NULL, 'i'},
+	{ "socket",      required_argument, NULL, 'z'},
+	{ "help",        no_argument,       NULL, 'h'},
+	{ "vty_addr",    required_argument, NULL, 'A'},
+	{ "vty_port",    required_argument, NULL, 'P'},
+	{ "user",        required_argument, NULL, 'u'},
+	{ "group",       required_argument, NULL, 'g'},
+	{ "version",     no_argument,       NULL, 'v'},
+	{ 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 */
+};
+
+static struct zebra_privs_t nhrpd_privs = {
+#ifdef QUAGGA_USER
+	.user = QUAGGA_USER,
+#endif
+#ifdef QUAGGA_GROUP
+	.group = QUAGGA_GROUP,
+#endif
+#ifdef VTY_GROUP
+	.vty_group = VTY_GROUP,
+#endif
+	.caps_p = _caps_p,
+	.cap_num_p = ZEBRA_NUM_OF(_caps_p),
+};
+
+static void usage(const char *progname, int status)
+{
+	if (status != 0)
+		fprintf(stderr, "Try `%s --help' for more information.\n", progname);
+	else
+		printf(
+"Usage : %s [OPTION...]\n\
+Daemon which manages NHRP protocol.\n\n\
+-d, --daemon       Runs in daemon mode\n\
+-f, --config_file  Set configuration file name\n\
+-i, --pid_file     Set process identifier file name\n\
+-z, --socket       Set path of zebra socket\n\
+-A, --vty_addr     Set vty's bind address\n\
+-P, --vty_port     Set vty's port number\n\
+-u, --user         User to run as\n\
+-g, --group        Group to run as\n\
+-v, --version      Print program version\n\
+-h, --help         Display this help and exit\n\
+\n\
+Report bugs to %s\n", progname, ZEBRA_BUG_ADDRESS);
+
+	exit(status);
+}
+
+static void parse_arguments(const char *progname, int argc, char **argv)
+{
+	int opt;
+
+	while (1) {
+		opt = getopt_long(argc, argv, "df:i:z:hA:P:u:g:v", longopts, 0);
+		if(opt < 0) break;
+
+		switch (opt) {
+		case 0:
+			break;
+		case 'd':
+			do_daemonise = -1;
+			break;
+		case 'f':
+			config_file = optarg;
+			break;
+		case 'i':
+			pid_file = optarg;
+			break;
+		case 'z':
+			zclient_serv_path_set(optarg);
+			break;
+		case 'A':
+			vty_addr = optarg;
+			break;
+		case 'P':
+			vty_port = atoi (optarg);
+			if (vty_port <= 0 || vty_port > 0xffff)
+				vty_port = NHRP_VTY_PORT;
+			break;
+		case 'u':
+			nhrpd_privs.user = optarg;
+			break;
+		case 'g':
+			nhrpd_privs.group = optarg;
+			break;
+		case 'v':
+			print_version(progname);
+			exit(0);
+			break;
+		case 'h':
+			usage(progname, 0);
+			break;
+		default:
+			usage(progname, 1);
+			break;
+		}
+	}
+}
+
+static void nhrp_sigusr1(void)
+{
+	zlog_rotate(NULL);
+}
+
+static void nhrp_request_stop(void)
+{
+	debugf(NHRP_DEBUG_COMMON, "Exiting...");
+
+	nhrp_shortcut_terminate();
+	nhrp_nhs_terminate();
+	nhrp_zebra_terminate();
+	vici_terminate();
+	evmgr_terminate();
+	nhrp_vc_terminate();
+	vrf_terminate();
+	/* memory_terminate(); */
+	/* vty_terminate(); */
+	cmd_terminate();
+	/* signal_terminate(); */
+	zprivs_terminate(&nhrpd_privs);
+
+	debugf(NHRP_DEBUG_COMMON, "Remove pid file.");
+	if (pid_file) unlink(pid_file);
+	debugf(NHRP_DEBUG_COMMON, "Done.");
+
+	closezlog(zlog_default);
+
+	exit(0);
+}
+
+static struct quagga_signal_t sighandlers[] = {
+	{ .signal = SIGUSR1, .handler = &nhrp_sigusr1, },
+	{ .signal = SIGINT,  .handler = &nhrp_request_stop, },
+	{ .signal = SIGTERM, .handler = &nhrp_request_stop, },
+};
+
+int main(int argc, char **argv)
+{
+	struct thread thread;
+	const char *progname;
+
+	/* Set umask before anything for security */
+	umask(0027);
+	progname = basename(argv[0]);
+	zlog_default = openzlog(progname, ZLOG_NHRP, LOG_CONS|LOG_NDELAY|LOG_PID, LOG_DAEMON);
+	zlog_set_level(NULL, ZLOG_DEST_STDOUT, LOG_WARNING);
+
+	parse_arguments(progname, argc, argv);
+
+	/* Library inits. */
+	master = thread_master_create();
+	zprivs_init(&nhrpd_privs);
+	signal_init(master, array_size(sighandlers), sighandlers);
+	cmd_init(1);
+	vty_init(master);
+	memory_init();
+	nhrp_interface_init();
+	vrf_init();
+	resolver_init();
+
+	/* Run with elevated capabilities, as for all netlink activity
+	 * we need privileges anyway. */
+	nhrpd_privs.change(ZPRIVS_RAISE);
+
+	netlink_init();
+	evmgr_init();
+	nhrp_vc_init();
+	nhrp_packet_init();
+	vici_init();
+	nhrp_zebra_init();
+	nhrp_shortcut_init();
+
+	nhrp_config_init();
+
+	/* Get zebra configuration file. */
+	zlog_set_level(NULL, ZLOG_DEST_STDOUT, do_daemonise ? ZLOG_DISABLED : LOG_DEBUG);
+	vty_read_config(config_file, config_default);
+
+	if (do_daemonise && daemon(0, 0) < 0) {
+		zlog_err("daemonise: %s", safe_strerror(errno));
+		exit (1);
+	}
+
+	/* write pid file */
+	if (pid_output(pid_file) < 0) {
+		zlog_err("error while writing pidfile");
+		exit (1);
+	}
+
+	/* Create VTY socket */
+	vty_serv_sock(vty_addr, vty_port, NHRP_VTYSH_PATH);
+	zlog_notice("nhrpd starting: vty@%d", vty_port);
+
+	/* Main loop */
+	while (thread_fetch(master, &thread))
+		thread_call(&thread);
+
+	return 0;
+}

+ 369 - 0
nhrpd/nhrp_nhs.c

@@ -0,0 +1,369 @@
+/* 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"
+
+static int nhrp_nhs_resolve(struct thread *t);
+
+struct nhrp_registration {
+	struct list_head 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;
+};
+
+static int 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_proto, *proto;
+	char buf[64];
+	int ok = 0, holdtime;
+
+	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: %s: %d",
+			sockunion2str(proto, buf, sizeof(buf)),
+			cie->code);
+		if (!((cie->code == NHRP_CODE_SUCCESS) ||
+                      (cie->code == NHRP_CODE_ADMINISTRATIVELY_PROHIBITED && nhs->hub)))
+			ok = 0;
+	}
+
+	if (!ok)
+		return;
+
+	/* Parse extensions */
+	sockunion_family(&nifp->nat_nbma) = 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: %s",
+					ifp->name, sockunion2str(&nifp->nbma, buf, sizeof(buf)));
+			}
+			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_TIMER_ON(master, r->t_register, nhrp_reg_send_req, r, holdtime / 3);
+
+	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), 0, NULL);
+}
+
+static int nhrp_reg_timeout(struct thread *t)
+{
+	struct nhrp_registration *r = THREAD_ARG(t);
+	struct nhrp_cache *c;
+
+	r->t_register = NULL;
+
+	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);
+		sockunion_family(&r->proto_addr) = AF_UNSPEC;
+	}
+
+	r->timeout <<= 1;
+	if (r->timeout > 64) r->timeout = 2;
+	THREAD_TIMER_MSEC_ON(master, r->t_register, nhrp_reg_send_req, r, 10);
+
+	return 0;
+}
+
+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);
+	char buf[SU_ADDRSTRLEN];
+
+	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 %s",
+			sockunion2str(&r->peer->vc->remote.nbma, buf, sizeof buf));
+		THREAD_TIMER_OFF(r->t_register);
+		THREAD_TIMER_MSEC_ON(master, r->t_register, nhrp_reg_send_req, r, 10);
+		break;
+	}
+}
+
+static int nhrp_reg_send_req(struct thread *t)
+{
+	struct nhrp_registration *r = THREAD_ARG(t);
+	struct nhrp_nhs *nhs = r->nhs;
+	char buf1[SU_ADDRSTRLEN], buf2[SU_ADDRSTRLEN];
+	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;
+	struct zbuf *zb;
+	struct nhrp_packet_header *hdr;
+	struct nhrp_extension_header *ext;
+	struct nhrp_cie_header *cie;
+
+	r->t_register = NULL;
+	if (!nhrp_peer_check(r->peer, 2)) {
+		debugf(NHRP_DEBUG_COMMON, "NHS: Waiting link for %s",
+			sockunion2str(&r->peer->vc->remote.nbma, buf1, sizeof buf1));
+		THREAD_TIMER_ON(master, r->t_register, nhrp_reg_send_req, r, 120);
+		return 0;
+	}
+
+	THREAD_TIMER_ON(master, r->t_register, nhrp_reg_timeout, r, r->timeout);
+
+	/* 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;
+
+	sockunion2str(&if_ad->addr, buf1, sizeof(buf1));
+	sockunion2str(dst_proto, buf2, sizeof(buf2));
+	debugf(NHRP_DEBUG_COMMON, "NHS: Register %s -> %s (timeout %d)", buf1, buf2, r->timeout);
+
+	/* No protocol address configured for tunnel interface */
+	if (sockunion_family(&if_ad->addr) == AF_UNSPEC)
+		return 0;
+
+	zb = zbuf_alloc(1400);
+	hdr = nhrp_packet_push(zb, NHRP_PACKET_REGISTRATION_REQUEST, &nifp->nbma, &if_ad->addr, dst_proto);
+	hdr->hop_count = 0;
+	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);
+	cie->prefix_length = 0xff;
+	cie->holding_time = htons(if_ad->holdtime);
+	cie->mtu = htons(if_ad->mtu);
+
+	nhrp_ext_request(zb, hdr, ifp);
+
+	/* Cisco NAT detection extension */
+	hdr->flags |= htons(NHRP_FLAG_REGISTRATION_NAT);
+	ext = nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS);
+	cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nbma, &if_ad->addr);
+	cie->prefix_length = 8 * sockunion_get_addrlen(&nifp->nbma);
+	nhrp_ext_complete(zb, ext);
+
+	nhrp_packet_complete(zb, hdr);
+	nhrp_peer_send(r->peer, zb);
+	zbuf_free(zb);
+
+	return 0;
+}
+
+static void nhrp_reg_delete(struct nhrp_registration *r)
+{
+	nhrp_peer_notify_del(r->peer, &r->peer_notifier);
+	nhrp_peer_unref(r->peer);
+	list_del(&r->reglist_entry);
+	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;
+
+	list_for_each_entry(r, &nhs->reglist_head, reglist_entry)
+		if (sockunion_same(&r->peer->vc->remote.nbma, nbma_addr))
+			return r;
+	return NULL;
+}
+
+static void nhrp_nhs_resolve_cb(struct resolver_query *q, 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, *regn;
+	int i;
+
+	nhs->t_resolve = NULL;
+	if (n < 0) {
+		/* Failed, retry in a moment */
+		THREAD_TIMER_ON(master, nhs->t_resolve, nhrp_nhs_resolve, nhs, 5);
+		return;
+	}
+
+	THREAD_TIMER_ON(master, nhs->t_resolve, nhrp_nhs_resolve, nhs, 2*60*60);
+
+	list_for_each_entry(reg, &nhs->reglist_head, reglist_entry)
+		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;
+		list_init(&reg->reglist_entry);
+		list_add_tail(&reg->reglist_entry, &nhs->reglist_head);
+		nhrp_peer_notify_add(reg->peer, &reg->peer_notifier, nhrp_reg_peer_notify);
+		THREAD_TIMER_MSEC_ON(master, reg->t_register, nhrp_reg_send_req, reg, 50);
+	}
+
+	list_for_each_entry_safe(reg, regn, &nhs->reglist_head, reglist_entry) {
+		if (reg->mark)
+			nhrp_reg_delete(reg);
+	}
+}
+
+static int nhrp_nhs_resolve(struct thread *t)
+{
+	struct nhrp_nhs *nhs = THREAD_ARG(t);
+
+	resolver_resolve(&nhs->dns_resolve, AF_INET, nhs->nbma_fqdn, nhrp_nhs_resolve_cb);
+
+	return 0;
+}
+
+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;
+