Discussion:
[quagga-dev 16314] [PATCH 3/5] bgpd: honor disable-connected-check option with next hop tracking
Timo Teräs
2016-10-19 13:02:33 UTC
Permalink
Make bgpd ignore connected state again if configured to do so.
---
bgpd/bgp_fsm.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/bgpd/bgp_fsm.c b/bgpd/bgp_fsm.c
index 2e07d93..7da63ea 100644
--- a/bgpd/bgp_fsm.c
+++ b/bgpd/bgp_fsm.c
@@ -716,7 +716,8 @@ bgp_start (struct peer *peer)
}

/* Register to be notified on peer up */
- if ((peer->ttl == 1) || (peer->gtsm_hops == 1))
+ if ((peer->ttl == 1 || peer->gtsm_hops == 1) &&
+ ! CHECK_FLAG (peer->flags, PEER_FLAG_DISABLE_CONNECTED_CHECK))
connected = 1;

bgp_find_or_add_nexthop(family2afi(peer->su.sa.sa_family), NULL, peer,
--
2.10.1
Timo Teräs
2016-10-19 13:02:31 UTC
Permalink
The FIB override routes can override ZEBRA_FLAG_SELECTED routes
in FIB. Use the FIB state instead to report correct nexthop when
FIB override routes are present.
---
zebra/zebra_rnh.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/zebra/zebra_rnh.c b/zebra/zebra_rnh.c
index 7b6d0f8..97d3597 100644
--- a/zebra/zebra_rnh.c
+++ b/zebra/zebra_rnh.c
@@ -223,7 +223,7 @@ zebra_evaluate_rnh_table (vrf_id_t vrfid, int family)
{
if (CHECK_FLAG (rib->status, RIB_ENTRY_REMOVED))
continue;
- if (CHECK_FLAG (rib->flags, ZEBRA_FLAG_SELECTED))
+ if (CHECK_FLAG (rib->status, RIB_ENTRY_SELECTED_FIB))
{
if (CHECK_FLAG(rnh->flags, ZEBRA_NHT_CONNECTED))
{
--
2.10.1
Timo Teräs
2016-10-19 13:02:34 UTC
Permalink
Change to track configured value in ->ttl and ->gtsm_hops;
not the value set to sockopt. Instead, setting of socket's ttl
and minttl options are now merged to one function which calculates
it on demand. This greatly simplifies the code.
---
bgpd/bgp_fsm.c | 2 +-
bgpd/bgp_network.c | 58 ++++++-------
bgpd/bgp_network.h | 1 +
bgpd/bgp_route.c | 4 +-
bgpd/bgp_vty.c | 26 ++----
bgpd/bgp_zebra.c | 13 ++-
bgpd/bgpd.c | 237 +++++++++++------------------------------------------
bgpd/bgpd.h | 7 +-
8 files changed, 95 insertions(+), 253 deletions(-)

diff --git a/bgpd/bgp_fsm.c b/bgpd/bgp_fsm.c
index 7da63ea..67c50c4 100644
--- a/bgpd/bgp_fsm.c
+++ b/bgpd/bgp_fsm.c
@@ -716,7 +716,7 @@ bgp_start (struct peer *peer)
}

/* Register to be notified on peer up */
- if ((peer->ttl == 1 || peer->gtsm_hops == 1) &&
+ if ((peer_ttl(peer) == 1 || peer->gtsm_hops == 1) &&
! CHECK_FLAG (peer->flags, PEER_FLAG_DISABLE_CONNECTED_CHECK))
connected = 1;

diff --git a/bgpd/bgp_network.c b/bgpd/bgp_network.c
index 51a6f60..c7d3389 100644
--- a/bgpd/bgp_network.c
+++ b/bgpd/bgp_network.c
@@ -146,47 +146,39 @@ bgp_update_sock_send_buffer_size (int fd)
}
}

-static void
+void
bgp_set_socket_ttl (struct peer *peer, int bgp_sock)
{
char buf[INET_ADDRSTRLEN];
- int ret;
+ int ret, ttl, minttl;

- /* In case of peer is EBGP, we should set TTL for this connection. */
- if (!peer->gtsm_hops && (peer_sort (peer) == BGP_PEER_EBGP))
+ if (bgp_sock < 0)
+ return;
+
+ if (peer->gtsm_hops)
{
- ret = sockopt_ttl (peer->su.sa.sa_family, bgp_sock, peer->ttl);
- if (ret)
- {
- zlog_err ("%s: Can't set TxTTL on peer (rtrid %s) socket, err = %d",
- __func__,
- inet_ntop (AF_INET, &peer->remote_id, buf, sizeof(buf)),
- errno);
- }
+ ttl = 255;
+ minttl = 256 - peer->gtsm_hops;
}
- else if (peer->gtsm_hops)
+ else
{
- /* On Linux, setting minttl without setting ttl seems to mess with the
- outgoing ttl. Therefore setting both.
- */
- ret = sockopt_ttl (peer->su.sa.sa_family, bgp_sock, MAXTTL);
- if (ret)
- {
- zlog_err ("%s: Can't set TxTTL on peer (rtrid %s) socket, err = %d",
- __func__,
- inet_ntop (AF_INET, &peer->remote_id, buf, sizeof(buf)),
- errno);
- }
- ret = sockopt_minttl (peer->su.sa.sa_family, bgp_sock,
- MAXTTL + 1 - peer->gtsm_hops);
- if (ret)
- {
- zlog_err ("%s: Can't set MinTTL on peer (rtrid %s) socket, err = %d",
- __func__,
- inet_ntop (AF_INET, &peer->remote_id, buf, sizeof(buf)),
- errno);
- }
+ ttl = peer_ttl (peer);
+ minttl = 0;
}
+
+ ret = sockopt_ttl (peer->su.sa.sa_family, bgp_sock, ttl);
+ if (ret)
+ zlog_err ("%s: Can't set TxTTL on peer (rtrid %s) socket, err = %d",
+ __func__,
+ inet_ntop (AF_INET, &peer->remote_id, buf, sizeof(buf)),
+ errno);
+
+ ret = sockopt_minttl (peer->su.sa.sa_family, bgp_sock, minttl);
+ if (ret && (errno != ENOTSUP || minttl))
+ zlog_err ("%s: Can't set MinTTL on peer (rtrid %s) socket, err = %d",
+ __func__,
+ inet_ntop (AF_INET, &peer->remote_id, buf, sizeof(buf)),
+ errno);
}

/* Accept bgp connection. */
diff --git a/bgpd/bgp_network.h b/bgpd/bgp_network.h
index 1276843..31995ca 100644
--- a/bgpd/bgp_network.h
+++ b/bgpd/bgp_network.h
@@ -28,6 +28,7 @@ extern void bgp_close (void);
extern int bgp_connect (struct peer *);
extern void bgp_getsockname (struct peer *);

+extern void bgp_set_socket_ttl (struct peer *peer, int bgp_sock);
extern int bgp_md5_set (struct peer *);

#endif /* _QUAGGA_BGP_NETWORK_H */
diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c
index 4cb6c14..010fd4d 100644
--- a/bgpd/bgp_route.c
+++ b/bgpd/bgp_route.c
@@ -2339,7 +2339,7 @@ bgp_update_main (struct peer *peer, struct prefix *p, struct attr *attr,
/* Nexthop reachability check. */
if ((afi == AFI_IP || afi == AFI_IP6) && safi == SAFI_UNICAST)
{
- if (peer->sort == BGP_PEER_EBGP && peer->ttl == 1 &&
+ if (peer->sort == BGP_PEER_EBGP && peer_ttl (peer) == 1 &&
! CHECK_FLAG (peer->flags, PEER_FLAG_DISABLE_CONNECTED_CHECK))
connected = 1;
else
@@ -2391,7 +2391,7 @@ bgp_update_main (struct peer *peer, struct prefix *p, struct attr *attr,
/* Nexthop reachability check. */
if ((afi == AFI_IP || afi == AFI_IP6) && safi == SAFI_UNICAST)
{
- if (peer->sort == BGP_PEER_EBGP && peer->ttl == 1 &&
+ if (peer->sort == BGP_PEER_EBGP && peer_ttl (peer) == 1 &&
! CHECK_FLAG (peer->flags, PEER_FLAG_DISABLE_CONNECTED_CHECK))
connected = 1;
else
diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c
index 7af4e81..8eeaff9 100644
--- a/bgpd/bgp_vty.c
+++ b/bgpd/bgp_vty.c
@@ -3058,7 +3058,7 @@ peer_ebgp_multihop_unset_vty (struct vty *vty, const char *ip_str)
if (! peer)
return CMD_WARNING;

- return bgp_vty_return (vty, peer_ebgp_multihop_unset (peer));
+ return bgp_vty_return (vty, peer_ebgp_multihop_set (peer, 0));
}

/* neighbor ebgp-multihop. */
@@ -4380,7 +4380,7 @@ DEFUN (no_neighbor_ttl_security,
if (! peer)
return CMD_WARNING;

- return bgp_vty_return (vty, peer_ttl_security_hops_unset (peer));
+ return bgp_vty_return (vty, peer_ttl_security_hops_set (peer, 0));
}

/* Address family configuration. */
@@ -8364,6 +8364,7 @@ bgp_show_peer (struct vty *vty, struct peer *p)
char timebuf[BGP_UPTIME_LEN];
afi_t afi;
safi_t safi;
+ int ttl;

bgp = p->bgp;

@@ -8663,21 +8664,12 @@ bgp_show_peer (struct vty *vty, struct peer *p)
}

/* EBGP Multihop and GTSM */
- if (p->sort != BGP_PEER_IBGP)
- {
- if (p->gtsm_hops > 0)
- vty_out (vty, " External BGP neighbor may be up to %d hops away.%s",
- p->gtsm_hops, VTY_NEWLINE);
- else if (p->ttl > 1)
- vty_out (vty, " External BGP neighbor may be up to %d hops away.%s",
- p->ttl, VTY_NEWLINE);
- }
- else
- {
- if (p->gtsm_hops > 0)
- vty_out (vty, " Internal BGP neighbor may be up to %d hops away.%s",
- p->gtsm_hops, VTY_NEWLINE);
- }
+ ttl = p->gtsm_hops;
+ if (! ttl)
+ ttl = peer_ttl (p);
+ vty_out (vty, " %s BGP neighbor may be up to %d hops away.%s",
+ p->sort == BGP_PEER_IBGP ? "Internal" : "External",
+ ttl, VTY_NEWLINE);

/* Local address. */
if (p->su_local)
diff --git a/bgpd/bgp_zebra.c b/bgpd/bgp_zebra.c
index 45d502a..ba87ad1 100644
--- a/bgpd/bgp_zebra.c
+++ b/bgpd/bgp_zebra.c
@@ -172,11 +172,10 @@ bgp_interface_down (int command, struct zclient *zclient, zebra_size_t length,

for (ALL_LIST_ELEMENTS (bgp->peer, node, nnode, peer))
{
- if ((peer->ttl != 1) && (peer->gtsm_hops != 1))
- continue;
-
- if (ifp == peer->nexthop.ifp)
- BGP_EVENT_ADD (peer, BGP_Stop);
+ if (peer->gtsm_hops != 1 && peer_ttl (peer) != 1)
+ continue;
+ if (ifp == peer->nexthop.ifp)
+ BGP_EVENT_ADD (peer, BGP_Stop);
}
}
}
@@ -716,7 +715,7 @@ bgp_zebra_announce (struct prefix *p, struct bgp_info *info, struct bgp *bgp, sa
SET_FLAG (flags, ZEBRA_FLAG_INTERNAL);
}

- if ((peer->sort == BGP_PEER_EBGP && peer->ttl != 1)
+ if ((peer->sort == BGP_PEER_EBGP && peer_ttl (peer) != 1)
|| CHECK_FLAG (peer->flags, PEER_FLAG_DISABLE_CONNECTED_CHECK))
SET_FLAG (flags, ZEBRA_FLAG_INTERNAL);

@@ -991,7 +990,7 @@ bgp_zebra_withdraw (struct prefix *p, struct bgp_info *info, safi_t safi)
SET_FLAG (flags, ZEBRA_FLAG_IBGP);
}

- if ((peer->sort == BGP_PEER_EBGP && peer->ttl != 1)
+ if ((peer->sort == BGP_PEER_EBGP && peer_ttl (peer) != 1)
|| CHECK_FLAG (peer->flags, PEER_FLAG_DISABLE_CONNECTED_CHECK))
SET_FLAG (flags, ZEBRA_FLAG_INTERNAL);

diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c
index 018a599..56e4322 100644
--- a/bgpd/bgpd.c
+++ b/bgpd/bgpd.c
@@ -646,7 +646,8 @@ peer_global_config_reset (struct peer *peer)
{
peer->weight = 0;
peer->change_local_as = 0;
- peer->ttl = (peer_sort (peer) == BGP_PEER_IBGP ? 255 : 1);
+ peer->ttl = 0;
+ peer->gtsm_hops = 0;
if (peer->update_source)
{
sockunion_free (peer->update_source);
@@ -925,9 +926,6 @@ peer_create (union sockunion *su, struct bgp *bgp, as_t local_as,
/* Last read and reset time set */
peer->readtime = peer->resettime = bgp_clock ();

- /* Default TTL set. */
- peer->ttl = (peer->sort == BGP_PEER_IBGP) ? 255 : 1;
-
/* Make peer's address string. */
sockunion2str (su, buf, SU_ADDRSTRLEN);
peer->host = XSTRDUP (MTYPE_BGP_PEER_HOST, buf);
@@ -957,7 +955,6 @@ peer_create_accept (struct bgp *bgp)
static void
peer_as_change (struct peer *peer, as_t as)
{
- bgp_peer_sort_t type;
struct peer *conf;

/* Stop peer. */
@@ -972,7 +969,6 @@ peer_as_change (struct peer *peer, as_t as)
else
BGP_EVENT_ADD (peer, BGP_Stop);
}
- type = peer_sort (peer);
peer->as = as;

if (bgp_config_check (peer->bgp, BGP_CONFIG_CONFEDERATION)
@@ -995,12 +991,6 @@ peer_as_change (struct peer *peer, as_t as)
else
peer->v_routeadv = BGP_DEFAULT_EBGP_ROUTEADV;

- /* TTL reset */
- if (peer_sort (peer) == BGP_PEER_IBGP)
- peer->ttl = 255;
- else if (type == BGP_PEER_IBGP)
- peer->ttl = 1;
-
/* reflector-client reset */
if (peer_sort (peer) != BGP_PEER_IBGP)
{
@@ -1506,7 +1496,7 @@ peer_group_get (struct bgp *bgp, const char *name)
group->conf->host = XSTRDUP (MTYPE_BGP_PEER_HOST, name);
group->conf->group = group;
group->conf->as = 0;
- group->conf->ttl = 1;
+ group->conf->ttl = 0;
group->conf->gtsm_hops = 0;
group->conf->v_routeadv = BGP_DEFAULT_EBGP_ROUTEADV;
UNSET_FLAG (group->conf->config, PEER_CONFIG_TIMER);
@@ -1807,6 +1797,16 @@ peer_group_remote_as (struct bgp *bgp, const char *group_name, as_t *as)
}

int
+peer_ttl (struct peer *peer)
+{
+ if (peer->ttl)
+ return peer->ttl;
+ if (peer->gtsm_hops || peer->sort == BGP_PEER_IBGP)
+ return 255;
+ return 1;
+}
+
+int
peer_group_delete (struct peer_group *group)
{
struct bgp *bgp;
@@ -1938,10 +1938,6 @@ peer_group_bind (struct bgp *bgp, union sockunion *su,
group->conf->v_routeadv = BGP_DEFAULT_EBGP_ROUTEADV;
}

- /* ebgp-multihop reset */
- if (peer_sort (group->conf) == BGP_PEER_IBGP)
- group->conf->ttl = 255;
-
/* local-as reset */
if (peer_sort (group->conf) != BGP_PEER_EBGP)
{
@@ -2870,29 +2866,17 @@ peer_ebgp_multihop_set (struct peer *peer, int ttl)
struct peer *peer1;

if (peer->sort == BGP_PEER_IBGP)
- return 0;
+ return BGP_ERR_NO_IBGP_WITH_TTLHACK;

- /* see comment in peer_ttl_security_hops_set() */
- if (ttl != MAXTTL)
- {
- if (CHECK_FLAG (peer->sflags, PEER_STATUS_GROUP))
- {
- group = peer->group;
- if (group->conf->gtsm_hops != 0)
- return BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK;
-
- for (ALL_LIST_ELEMENTS (group->peer, node, nnode, peer1))
- {
- if (peer1->sort == BGP_PEER_IBGP)
- continue;
+ if (peer->gtsm_hops != 0)
+ return BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK;

- if (peer1->gtsm_hops != 0)
- return BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK;
- }
- }
- else
+ if (CHECK_FLAG (peer->sflags, PEER_STATUS_GROUP))
+ {
+ group = peer->group;
+ for (ALL_LIST_ELEMENTS (group->peer, node, nnode, peer1))
{
- if (peer->gtsm_hops != 0)
+ if (peer1->gtsm_hops != 0)
return BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK;
}
}
@@ -2901,62 +2885,18 @@ peer_ebgp_multihop_set (struct peer *peer, int ttl)

if (! CHECK_FLAG (peer->sflags, PEER_STATUS_GROUP))
{
- if (peer->fd >= 0 && peer->sort != BGP_PEER_IBGP)
- sockopt_ttl (peer->su.sa.sa_family, peer->fd, peer->ttl);
+ bgp_set_socket_ttl (peer, peer->fd);
}
else
{
group = peer->group;
for (ALL_LIST_ELEMENTS (group->peer, node, nnode, peer))
- {
- if (peer->sort == BGP_PEER_IBGP)
- continue;
-
- peer->ttl = group->conf->ttl;
-
- if (peer->fd >= 0)
- sockopt_ttl (peer->su.sa.sa_family, peer->fd, peer->ttl);
- }
- }
- return 0;
-}
-
-int
-peer_ebgp_multihop_unset (struct peer *peer)
-{
- struct peer_group *group;
- struct listnode *node, *nnode;
-
- if (peer->sort == BGP_PEER_IBGP)
- return 0;
-
- if (peer->gtsm_hops != 0 && peer->ttl != MAXTTL)
- return BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK;
-
- if (peer_group_active (peer))
- peer->ttl = peer->group->conf->ttl;
- else
- peer->ttl = 1;
-
- if (! CHECK_FLAG (peer->sflags, PEER_STATUS_GROUP))
- {
- if (peer->fd >= 0 && peer->sort != BGP_PEER_IBGP)
- sockopt_ttl (peer->su.sa.sa_family, peer->fd, peer->ttl);
+ {
+ peer->ttl = ttl;
+ bgp_set_socket_ttl (peer, peer->fd);
+ }
}
- else
- {
- group = peer->group;
- for (ALL_LIST_ELEMENTS (group->peer, node, nnode, peer))
- {
- if (peer->sort == BGP_PEER_IBGP)
- continue;

- peer->ttl = 1;
-
- if (peer->fd >= 0)
- sockopt_ttl (peer->su.sa.sa_family, peer->fd, peer->ttl);
- }
- }
return 0;
}

@@ -4652,78 +4592,41 @@ peer_maximum_prefix_unset (struct peer *peer, afi_t afi, safi_t safi)
return 0;
}

-static int is_ebgp_multihop_configured (struct peer *peer)
-{
- struct peer_group *group;
- struct listnode *node, *nnode;
- struct peer *peer1;
-
- if (CHECK_FLAG (peer->sflags, PEER_STATUS_GROUP))
- {
- group = peer->group;
- if ((peer_sort(peer) != BGP_PEER_IBGP) &&
- (group->conf->ttl != 1))
- return 1;
-
- for (ALL_LIST_ELEMENTS (group->peer, node, nnode, peer1))
- {
- if ((peer_sort (peer1) != BGP_PEER_IBGP) &&
- (peer1->ttl != 1))
- return 1;
- }
- }
- else
- {
- if ((peer_sort(peer) != BGP_PEER_IBGP) &&
- (peer->ttl != 1))
- return 1;
- }
- return 0;
-}
-
/* Set # of hops between us and BGP peer. */
int
peer_ttl_security_hops_set (struct peer *peer, int gtsm_hops)
{
struct peer_group *group;
struct listnode *node, *nnode;
- int ret;
+ struct peer *peer1;

zlog_debug ("peer_ttl_security_hops_set: set gtsm_hops to %d for %s", gtsm_hops, peer->host);

- /* We cannot configure ttl-security hops when ebgp-multihop is already
- set. For non peer-groups, the check is simple. For peer-groups, it's
- slightly messy, because we need to check both the peer-group structure
- and all peer-group members for any trace of ebgp-multihop configuration
- before actually applying the ttl-security rules. Cisco really made a
- mess of this configuration parameter, and OpenBGPD got it right.
- */
-
- if (peer->gtsm_hops == 0)
- {
- if (is_ebgp_multihop_configured (peer))
- return BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK;
+ if (peer->ttl != 0)
+ return BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK;

- /* specify MAXTTL on outgoing packets */
- /* Routine handles iBGP peers correctly */
- ret = peer_ebgp_multihop_set (peer, MAXTTL);
- if (ret != 0)
- return ret;
+ if (CHECK_FLAG (peer->sflags, PEER_STATUS_GROUP))
+ {
+ group = peer->group;
+ for (ALL_LIST_ELEMENTS (group->peer, node, nnode, peer1))
+ {
+ if (peer1->ttl != 0)
+ return BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK;
+ }
}
-
+
peer->gtsm_hops = gtsm_hops;

if (! CHECK_FLAG (peer->sflags, PEER_STATUS_GROUP))
{
- if (peer->fd >= 0)
- sockopt_minttl (peer->su.sa.sa_family, peer->fd, MAXTTL + 1 - gtsm_hops);
+ bgp_set_socket_ttl (peer, peer->fd);
}
else
{
group = peer->group;
for (ALL_LIST_ELEMENTS (group->peer, node, nnode, peer))
{
- peer->gtsm_hops = group->conf->gtsm_hops;
+ peer->gtsm_hops = gtsm_hops;

/* Change setting of existing peer
* established then change value (may break connectivity)
@@ -4732,9 +4635,7 @@ peer_ttl_security_hops_set (struct peer *peer, int gtsm_hops)
*/
if (peer->status == Established)
{
- if (peer->fd >= 0 && peer->gtsm_hops != 0)
- sockopt_minttl (peer->su.sa.sa_family, peer->fd,
- MAXTTL + 1 - peer->gtsm_hops);
+ bgp_set_socket_ttl (peer, peer->fd);
}
else if (peer->status < Established)
{
@@ -4749,42 +4650,6 @@ peer_ttl_security_hops_set (struct peer *peer, int gtsm_hops)
}

int
-peer_ttl_security_hops_unset (struct peer *peer)
-{
- struct peer_group *group;
- struct listnode *node, *nnode;
- struct peer *opeer;
-
- zlog_debug ("peer_ttl_security_hops_unset: set gtsm_hops to zero for %s", peer->host);
-
- /* if a peer-group member, then reset to peer-group default rather than 0 */
- if (peer_group_active (peer))
- peer->gtsm_hops = peer->group->conf->gtsm_hops;
- else
- peer->gtsm_hops = 0;
-
- opeer = peer;
- if (! CHECK_FLAG (peer->sflags, PEER_STATUS_GROUP))
- {
- if (peer->fd >= 0)
- sockopt_minttl (peer->su.sa.sa_family, peer->fd, 0);
- }
- else
- {
- group = peer->group;
- for (ALL_LIST_ELEMENTS (group->peer, node, nnode, peer))
- {
- peer->gtsm_hops = 0;
-
- if (peer->fd >= 0)
- sockopt_minttl (peer->su.sa.sa_family, peer->fd, 0);
- }
- }
-
- return peer_ebgp_multihop_unset (opeer);
-}
-
-int
peer_clear (struct peer *peer)
{
if (! CHECK_FLAG (peer->flags, PEER_FLAG_SHUTDOWN))
@@ -5094,19 +4959,13 @@ bgp_config_write_peer (struct vty *vty, struct bgp *bgp,
! CHECK_FLAG (g_peer->flags, PEER_FLAG_PASSIVE))
vty_out (vty, " neighbor %s passive%s", addr, VTY_NEWLINE);

- /* EBGP multihop. */
- if (peer->sort != BGP_PEER_IBGP && peer->ttl != 1 &&
- !(peer->gtsm_hops != 0 && peer->ttl == MAXTTL))
- if (! peer_group_active (peer) ||
- g_peer->ttl != peer->ttl)
- vty_out (vty, " neighbor %s ebgp-multihop %d%s", addr, peer->ttl,
- VTY_NEWLINE);
-
- /* ttl-security hops */
- if (peer->gtsm_hops != 0)
- if (! peer_group_active (peer) || g_peer->gtsm_hops != peer->gtsm_hops)
- vty_out (vty, " neighbor %s ttl-security hops %d%s", addr,
- peer->gtsm_hops, VTY_NEWLINE);
+ /* TTL option */
+ if (peer->gtsm_hops && ! peer_group_active (peer))
+ vty_out (vty, " neighbor %s ttl-security hops %d%s", addr,
+ peer->gtsm_hops, VTY_NEWLINE);
+ else if (peer->ttl && ! peer_group_active (peer))
+ vty_out (vty, " neighbor %s ebgp-multihop %d%s", addr, peer->ttl,
+ VTY_NEWLINE);

/* disable-connected-check. */
if (CHECK_FLAG (peer->flags, PEER_FLAG_DISABLE_CONNECTED_CHECK))
diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h
index 0058b58..a4c608d 100644
--- a/bgpd/bgpd.h
+++ b/bgpd/bgpd.h
@@ -832,8 +832,8 @@ enum bgp_clear_type
#define BGP_ERR_TCPSIG_FAILED -29
#define BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK -30
#define BGP_ERR_NO_IBGP_WITH_TTLHACK -31
-#define BGP_ERR_MAX -32
-#define BGP_ERR_CANNOT_HAVE_LOCAL_AS_SAME_AS_REMOTE_AS -33
+#define BGP_ERR_CANNOT_HAVE_LOCAL_AS_SAME_AS_REMOTE_AS -32
+#define BGP_ERR_MAX -33

extern struct bgp_master *bm;

@@ -913,6 +913,7 @@ extern int peer_rsclient_active (struct peer *);

extern int peer_remote_as (struct bgp *, union sockunion *, as_t *, afi_t, safi_t);
extern int peer_group_remote_as (struct bgp *, const char *, as_t *);
+extern int peer_ttl (struct peer *peer);
extern int peer_delete (struct peer *peer);
extern int peer_group_delete (struct peer_group *);
extern int peer_group_remote_as_delete (struct peer_group *);
@@ -934,7 +935,6 @@ extern int peer_af_flag_unset (struct peer *, afi_t, safi_t, u_int32_t);
extern int peer_af_flag_check (struct peer *, afi_t, safi_t, u_int32_t);

extern int peer_ebgp_multihop_set (struct peer *, int);
-extern int peer_ebgp_multihop_unset (struct peer *);

extern int peer_description_set (struct peer *, const char *);
extern int peer_description_unset (struct peer *);
@@ -996,7 +996,6 @@ extern int peer_clear (struct peer *);
extern int peer_clear_soft (struct peer *, afi_t, safi_t, enum bgp_clear_type);

extern int peer_ttl_security_hops_set (struct peer *, int);
-extern int peer_ttl_security_hops_unset (struct peer *);

extern void bgp_scan_finish (void);
#endif /* _QUAGGA_BGPD_H */
--
2.10.1
Timo Teräs
2016-10-19 13:02:32 UTC
Permalink
This reverts commit 7e3a435bd99 "A valid BGP nexthop is flagged as invalid"

Problem is BGP thinks the nexthop is accessible when it's recursive, and
selects it, but zebra rejects it at route install time. Causing FIB and
BGP state to be out-of-sync. Fix nht to follow same rules as zebra rib.
---
zebra/zebra_rnh.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/zebra/zebra_rnh.c b/zebra/zebra_rnh.c
index 97d3597..5762d3f 100644
--- a/zebra/zebra_rnh.c
+++ b/zebra/zebra_rnh.c
@@ -498,8 +498,8 @@ send_client (struct rnh *rnh, struct zserv *client, vrf_id_t vrf_id)
nump = stream_get_endp(s);
stream_putc (s, 0);
for (nexthop = rib->nexthop; nexthop; nexthop = nexthop->next)
- if ((CHECK_FLAG (nexthop->flags, NEXTHOP_FLAG_FIB) ||
- CHECK_FLAG (nexthop->flags, NEXTHOP_FLAG_RECURSIVE)) &&
+ if (CHECK_FLAG (nexthop->flags, NEXTHOP_FLAG_FIB) &&
+ ! CHECK_FLAG (nexthop->flags, NEXTHOP_FLAG_RECURSIVE) &&
CHECK_FLAG (nexthop->flags, NEXTHOP_FLAG_ACTIVE))
{
stream_putc (s, nexthop->type);
--
2.10.1
Timo Teräs
2016-10-19 13:02:35 UTC
Permalink
This provides DMVPN support and integrates to strongSwan. Please read
README.nhrpd and README.kernel for more details.
---
.gitignore | 2 +
Makefile.am | 4 +-
SERVICES | 1 +
configure.ac | 27 +-
lib/log.c | 5 +
lib/log.h | 3 +-
lib/memtypes.c | 14 +
lib/route_types.txt | 2 +
nhrpd/Makefile.am | 34 ++
nhrpd/README.kernel | 145 ++++++++
nhrpd/README.nhrpd | 137 ++++++++
nhrpd/linux.c | 153 ++++++++
nhrpd/list.h | 191 ++++++++++
nhrpd/netlink.c | 398 +++++++++++++++++++++
nhrpd/netlink.h | 21 ++
nhrpd/nhrp_cache.c | 341 ++++++++++++++++++
nhrpd/nhrp_event.c | 280 +++++++++++++++
nhrpd/nhrp_interface.c | 404 +++++++++++++++++++++
nhrpd/nhrp_main.c | 246 +++++++++++++
nhrpd/nhrp_nhs.c | 369 ++++++++++++++++++++
nhrpd/nhrp_packet.c | 312 +++++++++++++++++
nhrpd/nhrp_peer.c | 860 +++++++++++++++++++++++++++++++++++++++++++++
nhrpd/nhrp_protocol.h | 128 +++++++
nhrpd/nhrp_route.c | 345 ++++++++++++++++++
nhrpd/nhrp_shortcut.c | 402 +++++++++++++++++++++
nhrpd/nhrp_vc.c | 217 ++++++++++++
nhrpd/nhrp_vty.c | 928 +++++++++++++++++++++++++++++++++++++++++++++++++
nhrpd/nhrpd.h | 440 +++++++++++++++++++++++
nhrpd/os.h | 5 +
nhrpd/reqid.c | 49 +++
nhrpd/resolver.c | 190 ++++++++++
nhrpd/vici.c | 482 +++++++++++++++++++++++++
nhrpd/vici.h | 24 ++
nhrpd/zbuf.c | 219 ++++++++++++
nhrpd/zbuf.h | 189 ++++++++++
nhrpd/znl.c | 160 +++++++++
nhrpd/znl.h | 29 ++
vtysh/Makefile.am | 1 +
vtysh/vtysh.c | 1 +
vtysh/vtysh.h | 5 +-
zebra/client_main.c | 1 +
zebra/zebra_rib.c | 2 +
zebra/zebra_rnh.c | 19 +-
zebra/zebra_vty.c | 2 +
44 files changed, 7776 insertions(+), 11 deletions(-)
create mode 100644 nhrpd/Makefile.am
create mode 100644 nhrpd/README.kernel
create mode 100644 nhrpd/README.nhrpd
create mode 100644 nhrpd/linux.c
create mode 100644 nhrpd/list.h
create mode 100644 nhrpd/netlink.c
create mode 100644 nhrpd/netlink.h
create mode 100644 nhrpd/nhrp_cache.c
create mode 100644 nhrpd/nhrp_event.c
create mode 100644 nhrpd/nhrp_interface.c
create mode 100644 nhrpd/nhrp_main.c
create mode 100644 nhrpd/nhrp_nhs.c
create mode 100644 nhrpd/nhrp_packet.c
create mode 100644 nhrpd/nhrp_peer.c
create mode 100644 nhrpd/nhrp_protocol.h
create mode 100644 nhrpd/nhrp_route.c
create mode 100644 nhrpd/nhrp_shortcut.c
create mode 100644 nhrpd/nhrp_vc.c
create mode 100644 nhrpd/nhrp_vty.c
create mode 100644 nhrpd/nhrpd.h
create mode 100644 nhrpd/os.h
create mode 100644 nhrpd/reqid.c
create mode 100644 nhrpd/resolver.c
create mode 100644 nhrpd/vici.c
create mode 100644 nhrpd/vici.h
create mode 100644 nhrpd/zbuf.c
create mode 100644 nhrpd/zbuf.h
create mode 100644 nhrpd/znl.c
create mode 100644 nhrpd/znl.h

diff --git a/.gitignore b/.gitignore
index a281555..a8da62f 100644
--- a/.gitignore
+++ b/.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
diff --git a/Makefile.am b/Makefile.am
index 0cd75be..3dea489 100644
--- a/Makefile.am
+++ b/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

diff --git a/SERVICES b/SERVICES
index c69d0c1..0322d45 100644
--- a/SERVICES
+++ b/SERVICES
@@ -18,3 +18,4 @@ ospf6d 2606/tcp
ospfapi 2607/tcp
isisd 2608/tcp
pimd 2611/tcp
+nhrpd 2612/tcp
diff --git a/configure.ac b/configure.ac
index f027762..dc41418 100755
--- a/configure.ac
+++ b/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,
@@ -1186,6 +1189,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
@@ -1241,6 +1255,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)
@@ -1276,6 +1291,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
@@ -1551,6 +1574,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)
@@ -1561,6 +1585,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)
@@ -1588,7 +1613,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
diff --git a/lib/log.c b/lib/log.c
index 42b7717..d437066 100644
--- a/lib/log.c
+++ b/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;
}
diff --git a/lib/log.h b/lib/log.h
index 7aa0896..c172009 100644
--- a/lib/log.h
+++ b/lib/log.h
@@ -54,7 +54,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
diff --git a/lib/memtypes.c b/lib/memtypes.c
index 8abe99d..ba2bacf 100644
--- a/lib/memtypes.c
+++ b/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},
};
diff --git a/lib/route_types.txt b/lib/route_types.txt
index 1b85607..811d24e 100644
--- a/lib/route_types.txt
+++ b/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)"
diff --git a/nhrpd/Makefile.am b/nhrpd/Makefile.am
new file mode 100644
index 0000000..d7e9e3a
--- /dev/null
+++ b/nhrpd/Makefile.am
@@ -0,0 +1,34 @@
+## 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.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
diff --git a/nhrpd/README.kernel b/nhrpd/README.kernel
new file mode 100644
index 0000000..5831316
--- /dev/null
+++ b/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.
+
diff --git a/nhrpd/README.nhrpd b/nhrpd/README.nhrpd
new file mode 100644
index 0000000..569b3f4
--- /dev/null
+++ b/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
+
diff --git a/nhrpd/linux.c b/nhrpd/linux.c
new file mode 100644
index 0000000..1e9c69e
--- /dev/null
+++ b/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;
+}
diff --git a/nhrpd/list.h b/nhrpd/list.h
new file mode 100644
index 0000000..32f21ed
--- /dev/null
+++ b/nhrpd/list.h
@@ -0,0 +1,191 @@
+/* Linux kernel style list handling function
+ *
+ * Written from scratch by Timo Teräs <***@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
diff --git a/nhrpd/netlink.c b/nhrpd/netlink.c
new file mode 100644
index 0000000..7fe2e01
--- /dev/null
+++ b/nhrpd/netlink.c
@@ -0,0 +1,398 @@
+/* NHRP netlink/neighbor table arpd code
+ * 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 <net/if.h>
+#include <netinet/ip.h>
+#include <netinet/if_ether.h>
+#include <linux/netlink.h>
+#include <linux/neighbour.h>
+#include <linux/netfilter/nfnetlink_log.h>
+#include <linux/if_tunnel.h>
+
+#include "thread.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;
+static int netlink_req_fd = -1;
+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;
+}
+
+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);
+}
diff --git a/nhrpd/netlink.h b/nhrpd/netlink.h
new file mode 100644
index 0000000..2285093
--- /dev/null
+++ b/nhrpd/netlink.h
@@ -0,0 +1,21 @@
+/* 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.
+ */
+
+union sockunion;
+struct interface;
+
+extern int netlink_nflog_group;
+
+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);
diff --git a/nhrpd/nhrp_cache.c b/nhrpd/nhrp_cache.c
new file mode 100644
index 0000000..447a814
--- /dev/null
+++ b/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);
+}
diff --git a/nhrpd/nhrp_event.c b/nhrpd/nhrp_event.c
new file mode 100644
index 0000000..aab9ec6
--- /dev/null
+++ b/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);
+}
+
diff --git a/nhrpd/nhrp_interface.c b/nhrpd/nhrp_interface.c
new file mode 100644
index 0000000..8118927
--- /dev/null
+++ b/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);
+}
diff --git a/nhrpd/nhrp_main.c b/nhrpd/nhrp_main.c
new file mode 100644
index 0000000..29349a0
--- /dev/null
+++ b/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;
+}
diff --git a/nhrpd/nhrp_nhs.c b/nhrpd/nhrp_nhs.c
new file mode 100644
index 0000000..d463e06
--- /dev/null
+++ b/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;
+
+ if (sockunion_family(proto_addr) != AF_UNSPEC &&
+ sockunion_family(proto_addr) != afi2family(afi))
+ return NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH;
+
+ list_for_each_entry(nhs, &nifp->afi[afi].nhslist_head, nhslist_entry) {
+ 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));
+ if (!nhs) return NHRP_ERR_NO_MEMORY;
+
+ *nhs = (struct nhrp_nhs) {
+ .afi = afi,
+ .ifp = ifp,
+ .proto_addr = *proto_addr,
+ .nbma_fqdn = strdup(nbma_fqdn),
+ .reglist_head = LIST_INITIALIZER(nhs->reglist_head),
+ };
+ list_add_tail(&nhs->nhslist_entry, &nifp->afi[afi].nhslist_head);
+ THREAD_TIMER_MSEC_ON(master, nhs->t_resolve, nhrp_nhs_resolve, nhs, 1000);
+
+ 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, *nnhs;
+ 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;
+
+ list_for_each_entry_safe(nhs, nnhs, &nifp->afi[afi].nhslist_head, nhslist_entry) {
+ if (!sockunion_same(&nhs->proto_addr, proto_addr))
+ continue;
+ if (strcmp(nhs->nbma_fqdn, nbma_fqdn) != 0)
+ continue;
+
+ nhrp_nhs_free(nhs);
+ ret = NHRP_OK;
+ }
+
+ return ret;
+}
+
+int nhrp_nhs_free(struct nhrp_nhs *nhs)
+{
+ struct nhrp_registration *r, *rn;
+
+ list_for_each_entry_safe(r, rn, &nhs->reglist_head, reglist_entry)
+ nhrp_reg_delete(r);
+ THREAD_OFF(nhs->t_resolve);
+ list_del(&nhs->nhslist_entry);
+ free((void*) nhs->nbma_fqdn);
+ XFREE(MTYPE_NHRP_NHS, nhs);
+ return 0;
+}
+
+void nhrp_nhs_terminate(void)
+{
+ struct interface *ifp;
+ struct nhrp_interface *nifp;
+ struct nhrp_nhs *nhs, *tmp;
+ struct listnode *node;
+ afi_t afi;
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+ nifp = ifp->info;
+ for (afi = 0; afi < AFI_MAX; afi++) {
+ list_for_each_entry_safe(nhs, tmp, &nifp->afi[afi].nhslist_head, nhslist_entry)
+ nhrp_nhs_free(nhs);
+ }
+ }
+}
diff --git a/nhrpd/nhrp_packet.c b/nhrpd/nhrp_packet.c
new file mode 100644
index 0000000..5d2866a
--- /dev/null
+++ b/nhrpd/nhrp_packet.c
@@ -0,0 +1,312 @@
+/* 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.
+ */
+
+#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) {
+ 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->holding_time = htons(ad->holdtime);
+ break;
+ default:
+ if (type & NHRP_EXTENSION_FLAG_COMPULSORY)
+ goto err;
+ 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 int 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);
+
+ zb = zbuf_alloc(1500);
+ if (!zb) return 0;
+
+ 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);
+ 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 0;
+
+err:
+ zbuf_free(zb);
+ return 0;
+}
+
+int nhrp_packet_init(void)
+{
+ thread_add_read(master, nhrp_packet_recvraw, 0, os_socket());
+ return 0;
+}
diff --git a/nhrpd/nhrp_peer.c b/nhrpd/nhrp_peer.c
new file mode 100644
index 0000000..45bfd7d
--- /dev/null
+++ b/nhrpd/nhrp_peer.c
@@ -0,0 +1,860 @@
+/* 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.
+ */
+
+#include <netinet/if_ether.h>
+
+#include "zebra.h"
+#include "memory.h"
+#include "thread.h"
+#include "hash.h"
+
+#include "nhrpd.h"
+#include "nhrp_protocol.h"
+#include "os.h"
+
+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;
+
+ THREAD_OFF(p->t_fallback);
+ 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 int 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);
+ }
+
+ return 0;
+}
+
+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_TIMER_MSEC_ON(
+ master, p->t_fallback,
+ nhrp_peer_notify_up, p, 50);
+ } 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);
+ }
+ /* Fall-through to post config update */
+ case NOTIFY_INTERFACE_ADDRESS_CHANGED:
+ 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(void *peer_data)
+{
+ struct nhrp_peer *p = peer_data;
+ return sockunion_hash(&p->vc->remote.nbma);
+}
+
+static int 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));
+ if (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;
+}
+
+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);
+ 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 int 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;
+
+ p->t_fallback = NULL;
+
+ if (p->online)
+ return 0;
+
+ 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_TIMER_ON(master, p->t_fallback, nhrp_peer_request_timeout, p, 30);
+ } else {
+ p->requested = p->fallback_requested = 0;
+ }
+
+ return 0;
+}
+
+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 (sockunion_family(&vc->local.nbma) == AF_UNSPEC)
+ return 0;
+
+ p->prio = establish > 1;
+ p->requested = 1;
+ vici_request_vc(nifp->ipsec_profile, &vc->local.nbma, &vc->remote.nbma, p->prio);
+ THREAD_TIMER_ON(master, p->t_fallback, nhrp_peer_request_timeout, p,
+ (nifp->ipsec_fallback_profile && !p->prio) ? 15 : 30);
+
+ 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);
+ nhrp_peer_check_delete(p);
+}
+
+void nhrp_peer_send(struct nhrp_peer *p, struct zbuf *zb)
+{
+ char buf[2][256];
+
+ nhrp_packet_debug(zb, "Send");
+
+ if (!p->online)
+ return;
+
+ debugf(NHRP_DEBUG_KERNEL, "PACKET: Send %s -> %s",
+ sockunion2str(&p->vc->local.nbma, buf[0], sizeof buf[0]),
+ sockunion2str(&p->vc->remote.nbma, buf[1], sizeof buf[1]));
+
+ os_sendmsg(zb->head, zbuf_used(zb),
+ p->ifp->ifindex,
+ sockunion_get_addr(&p->vc->remote.nbma),
+ sockunion_get_addrlen(&p->vc->remote.nbma));
+ zbuf_reset(zb);
+}
+
+static void nhrp_handle_resolution_req(struct nhrp_packet_parser *p)
+{
+ struct zbuf *zb, payload;
+ struct nhrp_packet_header *hdr;
+ struct nhrp_cie_header *cie;
+ struct nhrp_extension_header *ext;
+ struct nhrp_interface *nifp;
+ struct nhrp_peer *peer;
+
+ if (!(p->if_ad->flags & NHRP_IFF_SHORTCUT)) {
+ debugf(NHRP_DEBUG_COMMON, "Shortcuts disabled");
+ /* FIXME: Send error indication? */
+ return;
+ }
+
+ if (p->if_ad->network_id &&
+ p->route_type == NHRP_ROUTE_OFF_NBMA &&
+ p->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(p->ifp, &p->src_proto, NULL, &peer) != NHRP_ROUTE_NBMA_NEXTHOP)
+ return;
+
+#if 0
+ /* FIXME: Update requestors binding if CIE specifies holding time */
+ nhrp_cache_update_binding(
+ NHRP_CACHE_CACHED, &p->src_proto,
+ nhrp_peer_get(p->ifp, &p->src_nbma),
+ htons(cie->holding_time));
+#endif
+
+ nifp = peer->ifp->info;
+
+ /* Create reply */
+ zb = zbuf_alloc(1500);
+ hdr = nhrp_packet_push(zb, NHRP_PACKET_RESOLUTION_REPLY, &p->src_nbma, &p->src_proto, &p->dst_proto);
+
+ /* Copied information from request */
+ hdr->flags = p->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 = p->hdr->u.request_id;
+
+ /* CIE payload */
+ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nbma, &p->if_ad->addr);
+ cie->holding_time = htons(p->if_ad->holdtime);
+ cie->mtu = htons(p->if_ad->mtu);
+ if (p->if_ad->network_id && p->route_type == NHRP_ROUTE_OFF_NBMA)
+ cie->prefix_length = p->route_prefix.prefixlen;
+ else
+ cie->prefix_length = 8 * sockunion_get_addrlen(&p->if_ad->addr);
+
+ /* Handle extensions */
+ while ((ext = nhrp_ext_pull(&p->extensions, &payload)) != NULL) {
+ switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) {
+ case NHRP_EXTENSION_NAT_ADDRESS:
+ if (sockunion_family(&nifp->nat_nbma) == AF_UNSPEC)
+ break;
+ ext = nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS);
+ if (!ext) goto err;
+ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nat_nbma, &p->if_ad->addr);
+ if (!cie) goto err;
+ nhrp_ext_complete(zb, ext);
+ break;
+ default:
+ if (nhrp_ext_reply(zb, hdr, p->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, natted = 0;
+ size_t paylen;
+ void *pay;
+
+ debugf(NHRP_DEBUG_COMMON, "Parsing and replying to Registration Req");
+
+ 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) {
+ if (cie->prefix_length != 0xff && !(p->hdr->flags & htons(NHRP_FLAG_REGISTRATION_UNIQUE))) {
+ cie->code = NHRP_CODE_BINDING_NON_UNIQUE;
+ continue;
+ }
+
+ /* We currently support only unique prefix registrations */
+ if (cie->prefix_length != 0xff) {
+ 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 = nbma_addr;
+ 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)) {
+ 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) {
+ nhrp_cie_push(zb, NHRP_CODE_SUCCESS,
+ &p->peer->vc->remote.nbma,
+ &p->src_proto);
+ }
+ 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;
+ char buf[2][SU_ADDRSTRLEN];
+
+ 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 %s about packet to %s ignored",
+ sockunion2str(&p->vc->remote.nbma, buf[0], sizeof buf[0]),
+ sockunion2str(&dst, buf[1], sizeof buf[1]));
+ return;
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "Send Traffic Indication to %s (online=%d) about packet to %s",
+ sockunion2str(&p->vc->remote.nbma, buf[0], sizeof buf[0]),
+ p->online,
+ sockunion2str(&dst, buf[1], sizeof buf[1]));
+
+ /* Create reply */
+ zb = zbuf_alloc(1500);
+ hdr = nhrp_packet_push(zb, NHRP_PACKET_TRAFFIC_INDICATION, &nifp->nbma, &if_ad->addr, &dst);
+ hdr->hop_count = 0;
+
+ /* 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;
+ char buf[2][SU_ADDRSTRLEN];
+
+ hdr = nhrp_packet_pull(&origmsg, &src_nbma, &src_proto, &dst_proto);
+ if (!hdr) return;
+
+ debugf(NHRP_DEBUG_COMMON, "Error Indication from %s about packet to %s ignored",
+ sockunion2str(&pp->src_proto, buf[0], sizeof buf[0]),
+ sockunion2str(&dst_proto, buf[1], sizeof buf[1]));
+
+ 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;
+ char buf[2][SU_ADDRSTRLEN];
+
+ if (!parse_ether_packet(&p->payload, htons(p->hdr->protocol_type), NULL, &dst))
+ return;
+
+ debugf(NHRP_DEBUG_COMMON, "Traffic Indication from %s about packet to %s: %s",
+ sockunion2str(&p->src_proto, buf[0], sizeof buf[0]),
+ sockunion2str(&dst, buf[1], sizeof buf[1]),
+ (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[] = {
+ [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, 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;
+ uint16_t type, len;
+
+ if (pp->hdr->hop_count == 0)
+ return;
+
+ /* Create forward packet - copy header */
+ zb = 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(zb, &pp->payload, zbuf_used(&pp->payload));
+
+ /* 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 ((cie = 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->holding_time = htons(if_ad->holdtime);
+ }
+ 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;
+ 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);
+ return;
+err:
+ nhrp_packet_debug(pp->pkt, "FWD-FAIL");
+ zbuf_free(zb);
+}
+
+static void nhrp_packet_debug(struct zbuf *zb, const char *dir)
+{
+ char buf[2][SU_ADDRSTRLEN];
+ 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);
+
+ sockunion2str(&src_proto, buf[0], sizeof buf[0]);
+ sockunion2str(&dst_proto, buf[1], sizeof buf[1]);
+
+ reply = packet_types[hdr->type].type == PACKET_REPLY;
+ debugf(NHRP_DEBUG_COMMON, "%s %s(%d) %s -> %s",
+ dir,
+ packet_types[hdr->type].name ? : "Unknown",
+ hdr->type,
+ reply ? buf[1] : buf[0],
+ reply ? buf[0] : buf[1]);
+}
+
+struct nhrp_route_info {
+ int local;
+ struct interface *ifp;
+ struct nhrp_vc *vc;
+};
+
+void nhrp_peer_recv(struct nhrp_peer *p, struct zbuf *zb)
+{
+ char buf[2][SU_ADDRSTRLEN];
+ 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 afi;
+
+ debugf(NHRP_DEBUG_KERNEL, "PACKET: Recv %s -> %s",
+ sockunion2str(&vc->remote.nbma, buf[0], sizeof buf[0]),
+ sockunion2str(&vc->local.nbma, buf[1], sizeof buf[1]));
+
+ 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;
+
+ afi = htons(hdr->afnum);
+ if (hdr->type > ZEBRA_NUM_OF(packet_types) ||
+ hdr->version != NHRP_VERSION_RFC2332 ||
+ afi >= AFI_MAX ||
+ packet_types[hdr->type].type == PACKET_UNKNOWN ||
+ htons(hdr->packet_size) > realsize) {
+ zlog_info("From %s: error: packet type %d, version %d, AFI %d, size %d (real size %d)",
+ sockunion2str(&vc->remote.nbma, buf[0], sizeof buf[0]),
+ (int) hdr->type, (int) hdr->version, (int) afi,
+ (int) htons(hdr->packet_size),
+ (int) realsize);
+ goto drop;
+ }
+ pp.if_ad = &((struct nhrp_interface *)ifp->info)->afi[afi];
+
+ extoff = htons(hdr->extension_offset);
+ if (extoff) {
+ if (extoff >= realsize) {
+ info = "extoff larger than packet";
+ goto drop;
+ }
+ paylen = extoff - (zb->head - zb->buf);
+ } 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[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 */
+ }
+ }
+ 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 %s: error: %s",
+ sockunion2str(&vc->remote.nbma, buf[0], sizeof buf[0]),
+ 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..a4bc9fa
--- /dev/null
+++ b/nhrpd/nhrp_protocol.h
@@ -0,0 +1,128 @@
+/* nhrp_protocol.h - NHRP protocol definitions
+ *
+ * Copyright (c) 2007-2012 Timo Teräs <***@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
+
+/* 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..cc6b5fa
--- /dev/null
+++ b/nhrpd/nhrp_route.c
@@ -0,0 +1,345 @@
+/* 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.
+ */
+
+#include "nhrpd.h"
+#include "table.h"
+#include "memory.h"
+#include "stream.h"
+#include "log.h"
+#include "zclient.h"
+
+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 void nhrp_zebra_connected(struct zclient *zclient)
+{
+ /* No real VRF support yet -- bind only to the default vrf */
+ zclient_send_requests (zclient, VRF_DEFAULT);
+}
+
+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_family(&ri->via) == AF_UNSPEC) {
+ XFREE(MTYPE_NHRP_ROUTE, rn->info);
+ rn->info = NULL;
+ 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_family(nexthop) != AF_UNSPEC) || ifp);
+ if (rn) {
+ ri = rn->info;
+ ri->via = *nexthop;
+ ri->ifp = ifp;
+ nhrp_route_update_put(rn);
+ }
+}
+
+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 in_addr *nexthop_ipv4;
+ int flags = 0;
+
+ if (zclient->sock < 0)
+ return;
+
+ switch (type) {
+ case NHRP_CACHE_NEGATIVE:
+ SET_FLAG(flags, ZEBRA_FLAG_REJECT);
+ 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(flags, ZEBRA_FLAG_FIB_OVERRIDE);
+ break;
+ }
+ SET_FLAG(flags, ZEBRA_FLAG_INTERNAL);
+
+ if (p->family == AF_INET) {
+ struct zapi_ipv4 api;
+
+ memset(&api, 0, sizeof(api));
+ api.flags = flags;
+ api.type = ZEBRA_ROUTE_NHRP;
+ api.safi = SAFI_UNICAST;
+
+ SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP);
+ if (nexthop) {
+ SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP);
+ nexthop_ipv4 = (struct in_addr *) sockunion_get_addr(nexthop);
+ api.nexthop_num = 1;
+ api.nexthop = &nexthop_ipv4;
+ }
+ if (ifp) {
+ SET_FLAG(api.message, ZAPI_MESSAGE_IFINDEX);
+ api.ifindex_num = 1;
+ api.ifindex = &ifp->ifindex;
+ }
+ if (mtu) {
+ SET_FLAG(api.message, ZAPI_MESSAGE_MTU);
+ api.mtu = mtu;
+ }
+
+ if (unlikely(debug_flags & NHRP_DEBUG_ROUTE)) {
+ char buf[2][INET_ADDRSTRLEN];
+ zlog_debug("Zebra send: IPv4 route %s %s/%d nexthop %s metric %u"
+ " count %d dev %s",
+ add ? "add" : "del",
+ inet_ntop(AF_INET, &p->u.prefix4, buf[0], sizeof(buf[0])),
+ p->prefixlen,
+ nexthop ? inet_ntop(AF_INET, api.nexthop[0], buf[1], sizeof(buf[1])) : "<onlink>",
+ api.metric, api.nexthop_num, ifp->name);
+ }
+
+ zapi_ipv4_route(
+ add ? ZEBRA_IPV4_ROUTE_ADD : ZEBRA_IPV4_ROUTE_DELETE,
+ zclient, (struct prefix_ipv4 *) p, &api);
+ }
+}
+
+int nhrp_route_read(int cmd, struct zclient *zclient, zebra_size_t length, vrf_id_t vrf_id)
+{
+ struct stream *s;
+ struct interface *ifp = NULL;
+ struct prefix prefix;
+ union sockunion nexthop_addr;
+ unsigned char message, nexthop_num, ifindex_num;
+ unsigned ifindex;
+ char buf[2][PREFIX_STRLEN];
+ int i, afaddrlen, added;
+
+ s = zclient->ibuf;
+ memset(&prefix, 0, sizeof(prefix));
+ sockunion_family(&nexthop_addr) = AF_UNSPEC;
+
+ /* Type, flags, message. */
+ /*type =*/ stream_getc(s);
+ /*flags =*/ stream_getc(s);
+ message = stream_getc(s);
+
+ /* Prefix */
+ switch (cmd) {
+ case ZEBRA_IPV4_ROUTE_ADD:
+ case ZEBRA_IPV4_ROUTE_DELETE:
+ prefix.family = AF_INET;
+ break;
+ case ZEBRA_IPV6_ROUTE_ADD:
+ case ZEBRA_IPV6_ROUTE_DELETE:
+ prefix.family = AF_INET6;
+ break;
+ default:
+ return -1;
+ }
+ afaddrlen = family2addrsize(prefix.family);
+ prefix.prefixlen = stream_getc(s);
+ stream_get(&prefix.u.val, s, PSIZE(prefix.prefixlen));
+
+ /* Nexthop, ifindex, distance, metric. */
+ if (CHECK_FLAG(message, ZAPI_MESSAGE_NEXTHOP|ZAPI_MESSAGE_IFINDEX)) {
+ nexthop_num = stream_getc(s);
+ for (i = 0; i < nexthop_num; i++) {
+ stream_get(buf[0], s, afaddrlen);
+ if (i == 0) sockunion_set(&nexthop_addr, prefix.family, (u_char*) buf[0], afaddrlen);
+ }
+ ifindex_num = stream_getc(s);
+ for (i = 0; i < ifindex_num; i++) {
+ ifindex = stream_getl(s);
+ if (i == 0 && ifindex != IFINDEX_INTERNAL)
+ ifp = if_lookup_by_index(ifindex);
+ }
+ }
+ if (CHECK_FLAG(message, ZAPI_MESSAGE_DISTANCE))
+ /*distance =*/ stream_getc(s);
+ if (CHECK_FLAG(message, ZAPI_MESSAGE_METRIC))
+ /*metric =*/ stream_getl(s);
+
+ added = (cmd == ZEBRA_IPV4_ROUTE_ADD || cmd == ZEBRA_IPV6_ROUTE_ADD);
+ debugf(NHRP_DEBUG_ROUTE, "if-route-%s: %s via %s dev %s",
+ added ? "add" : "del",
+ prefix2str(&prefix, buf[0], sizeof buf[0]),
+ sockunion2str(&nexthop_addr, buf[1], sizeof buf[1]),
+ ifp ? ifp->name : "(none)");
+
+ nhrp_route_update_zebra(&prefix, &nexthop_addr, ifp);
+ nhrp_shortcut_prefix_change(&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));
+ char buf[PREFIX_STRLEN];
+
+ 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 %s: nhrp_if=%s",
+ prefix2str(&lookup, buf, sizeof buf),
+ ri->nhrp_ifp->name);
+
+ if (via) sockunion_family(via) = AF_UNSPEC;
+ if (ifp) *ifp = ri->nhrp_ifp;
+ } else {
+ debugf(NHRP_DEBUG_ROUTE, "lookup %s: zebra route dev %s",
+ prefix2str(&lookup, buf, sizeof buf),
+ 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;
+}
+
+void nhrp_zebra_init(void)
+{
+ zebra_rib[AFI_IP] = route_table_init();
+ zebra_rib[AFI_IP6] = route_table_init();
+
+ zclient = zclient_new(master);
+ zclient->zebra_connected = nhrp_zebra_connected;
+ zclient->interface_add = nhrp_interface_add;
+ zclient->interface_delete = nhrp_interface_delete;
+ zclient->interface_up = nhrp_interface_up;
+ zclient->interface_down = nhrp_interface_down;
+ zclient->interface_address_add = nhrp_interface_address_add;
+ zclient->interface_address_delete = nhrp_interface_address_delete;
+ zclient->ipv4_route_add = nhrp_route_read;
+ zclient->ipv4_route_delete = nhrp_route_read;
+ zclient->ipv6_route_add = nhrp_route_read;
+ zclient->ipv6_route_delete = nhrp_route_read;
+
+ zclient_init(zclient, ZEBRA_ROUTE_NHRP);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_KERNEL, VRF_DEFAULT);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_CONNECT, VRF_DEFAULT);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_STATIC, VRF_DEFAULT);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_RIP, VRF_DEFAULT);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_OSPF, VRF_DEFAULT);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_ISIS, VRF_DEFAULT);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_BGP, VRF_DEFAULT);
+}
+
+void nhrp_zebra_terminate(void)
+{
+ zclient_stop(zclient);
+ route_table_finish(zebra_rib[AFI_IP]);
+ route_table_finish(zebra_rib[AFI_IP6]);
+}
+
diff --git a/nhrpd/nhrp_shortcut.c b/nhrpd/nhrp_shortcut.c
new file mode 100644
index 0000000..421f288
--- /dev/null
+++ b/nhrpd/nhrp_shortcut.c
@@ -0,0 +1,402 @@
+/* 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.
+ */
+
+#include "nhrpd.h"
+#include "table.h"
+#include "memory.h"
+#include "thread.h"
+#include "log.h"
+#include "nhrp_protocol.h"
+
+static struct route_table *shortcut_rib[AFI_MAX];
+
+static int 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)
+{
+ char buf[PREFIX_STRLEN];
+
+ if (s->expiring && s->cache && s->cache->used) {
+ debugf(NHRP_DEBUG_ROUTE, "Shortcut %s used and expiring",
+ prefix2str(s->p, buf, sizeof buf));
+ nhrp_shortcut_send_resolution_req(s);
+ }
+}
+
+static int nhrp_shortcut_do_expire(struct thread *t)
+{
+ struct nhrp_shortcut *s = THREAD_ARG(t);
+
+ s->t_timer = NULL;
+ THREAD_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, s->holding_time/3);
+ s->expiring = 1;
+ nhrp_shortcut_check_use(s);
+
+ return 0;
+}
+
+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);
+
+ switch (cmd) {
+ case NOTIFY_CACHE_UP:
+ if (!s->route_installed) {
+ nhrp_route_announce(1, s->type, s->p, NULL, &s->cache->remote_addr, 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;
+ nhrp_shortcut_cache_notify(&s->cache_notifier, NOTIFY_CACHE_UP);
+ }
+ }
+ if (!s->cache || !s->cache->route_installed)
+ 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_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_expire, s, 2*holding_time/3);
+ }
+}
+
+static void nhrp_shortcut_delete(struct nhrp_shortcut *s)
+{
+ struct route_node *rn;
+ afi_t afi = family2afi(PREFIX_FAMILY(s->p));
+ char buf[PREFIX_STRLEN];
+
+ THREAD_OFF(s->t_timer);
+ nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid);
+
+ debugf(NHRP_DEBUG_ROUTE, "Shortcut %s purged",
+ prefix2str(s->p, buf, sizeof buf));
+
+ 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 int nhrp_shortcut_do_purge(struct thread *t)
+{
+ struct nhrp_shortcut *s = THREAD_ARG(t);
+ s->t_timer = NULL;
+ nhrp_shortcut_delete(s);
+ return 0;
+}
+
+static struct nhrp_shortcut *nhrp_shortcut_get(struct prefix *p)
+{
+ struct nhrp_shortcut *s;
+ struct route_node *rn;
+ char buf[PREFIX_STRLEN];
+ 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 %s created",
+ prefix2str(s->p, buf, sizeof buf));
+ } 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 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;
+ union sockunion *proto, cie_proto, *nbma, *nbma_natoa, cie_nbma, nat_nbma;
+ struct prefix prefix, route_prefix;
+ struct zbuf extpl;
+ char bufp[PREFIX_STRLEN], buf[3][SU_ADDRSTRLEN];
+ int holding_time = pp->if_ad->holdtime;
+
+ nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid);
+ THREAD_OFF(s->t_timer);
+ THREAD_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 1);
+
+ 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;
+ }
+
+ /* 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:
+ nhrp_cie_pull(&extpl, pp->hdr, &nat_nbma, &cie_proto);
+ break;
+ }
+ }
+
+ /* 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 %s to %s",
+ sockunion2str(&cie_proto, buf[0], sizeof buf[0]),
+ sockunion2str(&pp->dst_proto, buf[1], sizeof buf[1]));
+ }
+
+ /* 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 = 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;
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "Shortcut: %s is at proto %s cie-nbma %s nat-nbma %s cie-holdtime %d",
+ prefix2str(&prefix, bufp, sizeof bufp),
+ sockunion2str(proto, buf[0], sizeof buf[0]),
+ sockunion2str(&cie_nbma, buf[1], sizeof buf[1]),
+ sockunion2str(&nat_nbma, buf[2], sizeof buf[2]),
+ htons(cie->holding_time));
+
+ /* Update cache entry for the protocol to nbma binding */
+ if (sockunion_family(&nat_nbma) != AF_UNSPEC) {
+ nbma = &nat_nbma;
+ nbma_natoa = &cie_nbma;
+ } else {
+ nbma = &cie_nbma;
+ nbma_natoa = NULL;
+ }
+ if (sockunion_family(nbma)) {
+ c = nhrp_cache_get(pp->ifp, proto, 1);
+ if (c) {
+ nhrp_cache_update_binding(
+ c, NHRP_CACHE_CACHED, holding_time,
+ nhrp_peer_get(pp->ifp, nbma),
+ htons(cie->mtu), nbma_natoa);
+ }
+ }
+
+ /* Update shortcut entry for subnet to protocol gw binding */
+ if (c && !sockunion_same(proto, &pp->dst_proto)) {
+ ps = nhrp_shortcut_get(&prefix);
+ if (ps) {
+ ps->addr = s->addr;
+ nhrp_shortcut_update_binding(ps, NHRP_CACHE_CACHED, c, holding_time);
+ }
+ }
+
+ 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_peer *peer;
+
+ 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: Send holding time, and MTU */
+
+ nhrp_ext_request(zb, hdr, ifp);
+
+ /* Cisco NAT detection extension */
+ hdr->flags |= htons(NHRP_FLAG_RESOLUTION_NAT);
+ nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS);
+
+ 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;
+
+ sockunion2hostprefix(addr, &p);
+ s = nhrp_shortcut_get(&p);
+ if (s && s->type != NHRP_CACHE_INCOMPLETE) {
+ s->addr = *addr;
+ THREAD_OFF(s->t_timer);
+ THREAD_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 30);
+ 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_TIMER_MSEC_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 5);
+ } 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_TIMER_MSEC_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 3000);
+ 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..f9e1ee0
--- /dev/null
+++ b/nhrpd/nhrp_vc.c
@@ -0,0 +1,217 @@
+/* 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"
+
+struct child_sa {
+ uint32_t id;
+ struct nhrp_vc *vc;
+ struct list_head childlist_entry;
+};
+
+static struct hash *nhrp_vc_hash;
+static struct list_head childlist_head[512];
+
+static unsigned int nhrp_vc_key(void *peer_data)
+{
+ struct nhrp_vc *vc = peer_data;
+ return jhash_2words(
+ sockunion_hash(&vc->local.nbma),
+ sockunion_hash(&vc->remote.nbma),
+ 0);
+}
+
+static int 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));
+ if (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)
+{
+ char buf[2][SU_ADDRSTRLEN];
+ struct child_sa *sa = NULL, *lsa;
+ uint32_t child_hash = child_id % ZEBRA_NUM_OF(childlist_head);
+ int abort_migration = 0;
+
+ list_for_each_entry(lsa, &childlist_head[child_hash], childlist_entry) {
+ if (lsa->id == child_id) {
+ sa = lsa;
+ break;
+ }
+ }
+
+ if (!sa) {
+ if (!vc) return 0;
+
+ sa = XMALLOC(MTYPE_NHRP_VC, sizeof(struct child_sa));
+ if (!sa) return 0;
+
+ *sa = (struct child_sa) {
+ .id = child_id,
+ .childlist_entry = LIST_INITIALIZER(sa->childlist_entry),
+ .vc = NULL,
+ };
+ list_add_tail(&sa->childlist_entry, &childlist_head[child_hash]);
+ }
+
+ 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 %s to %s",
+ sockunion2str(&sa->vc->remote.nbma, buf[0], sizeof buf[0]),
+ sockunion2str(&vc->remote.nbma, buf[1], sizeof buf[1]));
+ 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) {
+ list_del(&sa->childlist_entry);
+ 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);
+ 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_backet *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);
+ for (i = 0; i < ZEBRA_NUM_OF(childlist_head); i++)
+ list_init(&childlist_head[i]);
+}
+
+void nhrp_vc_reset(void)
+{
+ struct child_sa *sa, *n;
+ size_t i;
+
+ for (i = 0; i < ZEBRA_NUM_OF(childlist_head); i++) {
+ list_for_each_entry_safe(sa, n, &childlist_head[i], childlist_entry)
+ 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..9b1c69d
--- /dev/null
+++ b/nhrpd/nhrp_vty.c
@@ -0,0 +1,928 @@
+/* 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 "nhrpd.h"
+#include "netlink.h"
+
+static struct cmd_node zebra_node = {
+ .node = ZEBRA_NODE,
+ .prompt = "%s(config-router)# ",
+ .vtysh = 1,
+};
+
+static struct cmd_node nhrp_interface_node = {
+ .node = INTERFACE_NODE,
+ .prompt = "%s(config-if)# ",
+ .vtysh = 1,
+};
+
+#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, NULL },
+};
+
+static const struct message interface_flags_desc[] = {
+ { NHRP_IFF_SHORTCUT, "shortcut" },
+ { NHRP_IFF_REDIRECT, "redirect" },
+ { NHRP_IFF_REG_NO_UNIQUE, "registration no-unique" },
+ { 0, NULL },
+};
+
+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 <= (int)ZEBRA_NUM_OF(errmsgs))
+ if (errmsgs[ret])
+ str = errmsgs[ret];
+
+ if (!str) {
+ str = buf;
+ snprintf(buf, sizeof(buf), "Unknown error %d", ret);
+ }
+
+ vty_out (vty, "%% %s%s", str, VTY_NEWLINE);
+
+ return CMD_WARNING;
+}
+
+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%s", name, VTY_NEWLINE);
+ return CMD_WARNING;
+}
+
+#ifndef NO_DEBUG
+
+DEFUN(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:%s", VTY_NEWLINE);
+
+ 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%s",
+ debug_flags_desc[i].str, VTY_NEWLINE);
+ }
+
+ 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[0], 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[0], 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%s", VTY_NEWLINE);
+ } 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%s", debug_flags_desc[i].str, VTY_NEWLINE);
+ }
+ }
+ vty_out(vty, "!%s", VTY_NEWLINE);
+#endif /* NO_DEBUG */
+
+ if (nhrp_event_socket_path) {
+ vty_out(vty, "nhrp event socket %s%s",
+ nhrp_event_socket_path, VTY_NEWLINE);
+ }
+ if (netlink_nflog_group) {
+ vty_out(vty, "nhrp nflog-group %d%s",
+ netlink_nflog_group, VTY_NEWLINE);
+ }
+
+ 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 char *cmd)
+{
+ return strncmp(cmd, "ipv6", 4) == 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")
+{
+ evmgr_set_socket(argv[0]);
+ 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")
+{
+ 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;
+
+ VTY_GET_INTEGER_RANGE("nflog-group", nfgroup, argv[0], 1, 65535);
+ 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(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")
+{
+ struct interface *ifp = vty->index;
+
+ nhrp_interface_set_protection(ifp, argv[0], argv[1]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_tunnel_protection, no_tunnel_protection_cmd,
+ "no tunnel protection",
+ NO_STR
+ "NHRP/GRE integration\n"
+ "IPsec protection\n")
+{
+ struct interface *ifp = vty->index;
+
+ 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")
+{
+ struct interface *ifp = vty->index;
+ nhrp_interface_set_source(ifp, argv[0]);
+ 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")
+{
+ struct interface *ifp = vty->index;
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ VTY_GET_INTEGER_RANGE("network-id", nifp->afi[afi].network_id, argv[1], 1, 4294967295);
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ return toggle_flag(vty, interface_flags_desc, argv[1], 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ return toggle_flag(vty, interface_flags_desc, argv[1], 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+ char name[256];
+ snprintf(name, sizeof(name), "registration %s", argv[1]);
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+ char name[256];
+ snprintf(name, sizeof(name), "registration %s", argv[1]);
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ VTY_GET_INTEGER_RANGE("holdtime", nifp->afi[afi].holdtime, argv[1], 1, 65000);
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+
+ if (argv[0][0] == 'o') {
+ nifp->afi[AFI_IP].configured_mtu = -1;
+ } else {
+ VTY_GET_INTEGER_RANGE("mtu", nifp->afi[AFI_IP].configured_mtu, argv[0], 576, 1500);
+ }
+ 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")
+{
+ struct interface *ifp = vty->index;
+ 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")
+{
+ struct interface *ifp = vty->index;
+ afi_t afi = cmd_to_afi(argv[0]);
+ union sockunion proto_addr, nbma_addr;
+ struct nhrp_cache *c;
+
+ if (str2sockunion(argv[1], &proto_addr) < 0 ||
+ afi2family(afi) != sockunion_family(&proto_addr))
+ return nhrp_vty_return(vty, NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH);
+
+ c = nhrp_cache_get(ifp, &proto_addr, 1);
+ if (!c)
+ return nhrp_vty_return(vty, NHRP_ERR_FAIL);
+
+ c->map = 1;
+ if (strcmp(argv[2], "local") == 0) {
+ nhrp_cache_update_binding(c, NHRP_CACHE_LOCAL, 0, NULL, 0, NULL);
+ } else{
+ if (str2sockunion(argv[2], &nbma_addr) < 0)
+ return nhrp_vty_return(vty, NHRP_ERR_FAIL);
+ nhrp_cache_update_binding(c, NHRP_CACHE_STATIC, 0,
+ nhrp_peer_get(ifp, &nbma_addr), 0, NULL);
+ }
+
+ return CMD_SUCCESS;
+}
+
+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"
+ "IPv4 NBMA address\n"
+ "Fully qualified domain name for NBMA address(es)\n")
+{
+ struct interface *ifp = vty->index;
+ afi_t afi = cmd_to_afi(argv[0]);
+ union sockunion proto_addr;
+ int ret;
+
+ if (str2sockunion(argv[1], &proto_addr) < 0)
+ sockunion_family(&proto_addr) = AF_UNSPEC;
+
+ ret = nhrp_nhs_add(ifp, afi, &proto_addr, argv[2]);
+ 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"
+ "IPv4 NBMA address\n"
+ "Fully qualified domain name for NBMA address(es)\n")
+{
+ struct interface *ifp = vty->index;
+ afi_t afi = cmd_to_afi(argv[0]);
+ union sockunion proto_addr;
+ int ret;
+
+ if (str2sockunion(argv[1], &proto_addr) < 0)
+ sockunion_family(&proto_addr) = AF_UNSPEC;
+
+ ret = nhrp_nhs_del(ifp, afi, &proto_addr, argv[2]);
+ return nhrp_vty_return(vty, ret);
+}
+
+struct info_ctx {
+ struct vty *vty;
+ afi_t afi;
+ int count;
+};
+
+static void show_ip_nhrp_cache(struct nhrp_cache *c, void *pctx)
+{
+ struct info_ctx *ctx = pctx;
+ struct vty *vty = ctx->vty;
+ char buf[2][SU_ADDRSTRLEN];
+
+ if (ctx->afi != family2afi(sockunion_family(&c->remote_addr)))
+ return;
+
+ if (!ctx->count) {
+ vty_out(vty, "%-8s %-8s %-24s %-24s %-6s %s%s",
+ "Iface",
+ "Type",
+ "Protocol",
+ "NBMA",
+ "Flags",
+ "Identity",
+ VTY_NEWLINE);
+ }
+ ctx->count++;
+
+ vty_out(ctx->vty, "%-8s %-8s %-24s %-24s %c%c%c %s%s",
+ c->ifp->name,
+ nhrp_cache_type_str[c->cur.type],
+ sockunion2str(&c->remote_addr, buf[0], sizeof buf[0]),
+ c->cur.peer ? sockunion2str(&c->cur.peer->vc->remote.nbma, buf[1], sizeof buf[1]) : "-",
+ c->used ? 'U' : ' ',
+ c->t_timeout ? 'T' : ' ',
+ c->t_auth ? 'A' : ' ',
+ c->cur.peer ? c->cur.peer->vc->remote.id : "-",
+ VTY_NEWLINE);
+}
+
+static void show_ip_opennhrp_cache(struct nhrp_cache *c, void *pctx)
+{
+ struct info_ctx *ctx = pctx;
+ struct vty *vty = ctx->vty;
+ char buf[SU_ADDRSTRLEN];
+
+ if (ctx->afi != family2afi(sockunion_family(&c->remote_addr)))
+ return;
+
+ vty_out(ctx->vty,
+ "Type: %s%s"
+ "Flags:%s%s%s"
+ "Protocol-Address: %s/%zu%s",
+ nhrp_cache_type_str[c->cur.type],
+ VTY_NEWLINE,
+ (c->cur.peer && c->cur.peer->online) ? " up": "",
+ c->used ? " used": "",
+ VTY_NEWLINE,
+ sockunion2str(&c->remote_addr, buf, sizeof buf),
+ 8 * family2addrsize(sockunion_family(&c->remote_addr)),
+ VTY_NEWLINE);
+
+ if (c->cur.peer) {
+ vty_out(ctx->vty,
+ "NBMA-Address: %s%s",
+ sockunion2str(&c->cur.peer->vc->remote.nbma, buf, sizeof buf),
+ VTY_NEWLINE);
+ }
+
+ if (sockunion_family(&c->cur.remote_nbma_natoa) != AF_UNSPEC) {
+ vty_out(ctx->vty,
+ "NBMA-NAT-OA-Address: %s%s",
+ sockunion2str(&c->cur.remote_nbma_natoa, buf, sizeof buf),
+ VTY_NEWLINE);
+ }
+
+ vty_out(ctx->vty, "%s", VTY_NEWLINE);
+}
+
+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];
+
+ if (!ctx->count) {
+ vty_out(vty, "%-8s %-24s %-24s %s%s",
+ "Type",
+ "Prefix",
+ "Via",
+ "Identity",
+ VTY_NEWLINE);
+ }
+ ctx->count++;
+
+ c = s->cache;
+ vty_out(ctx->vty, "%-8s %-24s %-24s %s%s",
+ nhrp_cache_type_str[s->type],
+ prefix2str(s->p, buf1, sizeof buf1),
+ c ? sockunion2str(&c->remote_addr, buf2, sizeof buf2) : "",
+ (c && c->cur.peer) ? c->cur.peer->vc->remote.id : "",
+ VTY_NEWLINE);
+}
+
+DEFUN(show_ip_nhrp, show_ip_nhrp_cmd,
+ "show " AFI_CMD " nhrp (cache|shortcut|opennhrp|)",
+ SHOW_STR
+ AFI_STR
+ "NHRP information\n"
+ "Forwarding cache information\n"
+ "Shortcut information\n"
+ "opennhrpctl style cache dump\n")
+{
+ struct listnode *node;
+ struct interface *ifp;
+ struct info_ctx ctx = {
+ .vty = vty,
+ .afi = cmd_to_afi(argv[0]),
+ };
+
+ if (!argv[1] || argv[1][0] == 'c') {
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp))
+ nhrp_cache_foreach(ifp, show_ip_nhrp_cache, &ctx);
+ } else if (argv[1][0] == 'o') {
+ vty_out(vty, "Status: ok%s%s", VTY_NEWLINE, VTY_NEWLINE);
+ ctx.count++;
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp))
+ nhrp_cache_foreach(ifp, show_ip_opennhrp_cache, &ctx);
+ } else {
+ nhrp_shortcut_foreach(ctx.afi, show_ip_nhrp_shortcut, &ctx);
+ }
+
+ if (!ctx.count) {
+ vty_out(vty, "%% No entries%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+static void show_dmvpn_entry(struct nhrp_vc *vc, void *ctx)
+{
+ struct vty *vty = ctx;
+ char buf[2][SU_ADDRSTRLEN];
+
+ vty_out(vty, "%-24s %-24s %c %-4d %-24s%s",
+ sockunion2str(&vc->local.nbma, buf[0], sizeof buf[0]),
+ sockunion2str(&vc->remote.nbma, buf[1], sizeof buf[1]),
+ notifier_active(&vc->notifier_list) ? 'n' : ' ',
+ vc->ipsec,
+ vc->remote.id,
+ VTY_NEWLINE);
+}
+
+DEFUN(show_dmvpn, show_dmvpn_cmd,
+ "show dmvpn",
+ SHOW_STR
+ "DMVPN information\n")
+{
+ vty_out(vty, "%-24s %-24s %-6s %-4s %-24s%s",
+ "Src",
+ "Dst",
+ "Flags",
+ "SAs",
+ "Identity",
+ VTY_NEWLINE);
+
+ nhrp_vc_foreach(show_dmvpn_entry, vty);
+
+ 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_CACHED) {
+ nhrp_cache_update_binding(c, c->cur.type, -1, NULL, 0, NULL);
+ 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 listnode *node;
+ struct interface *ifp;
+ struct info_ctx ctx = {
+ .vty = vty,
+ .afi = cmd_to_afi(argv[0]),
+ .count = 0,
+ };
+
+ if (!argv[1] || argv[1][0] == 'c') {
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp))
+ nhrp_cache_foreach(ifp, clear_nhrp_cache, &ctx);
+ } else {
+ nhrp_shortcut_foreach(ctx.afi, clear_nhrp_shortcut, &ctx);
+ }
+
+ if (!ctx.count) {
+ vty_out(vty, "%% No entries%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vty_out(vty, "%% %d entries cleared%s", ctx.count, VTY_NEWLINE);
+ 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 *c, void *data)
+{
+ struct write_map_ctx *ctx = data;
+ struct vty *vty = ctx->vty;
+ char buf[2][SU_ADDRSTRLEN];
+
+ if (!c->map) return;
+ if (sockunion_family(&c->remote_addr) != ctx->family) return;
+
+ vty_out(vty, " %s nhrp map %s %s%s",
+ ctx->aficmd,
+ sockunion2str(&c->remote_addr, buf[0], sizeof buf[0]),
+ c->cur.type == NHRP_CACHE_LOCAL ? "local" :
+ sockunion2str(&c->cur.peer->vc->remote.nbma, buf[1], sizeof buf[1]),
+ VTY_NEWLINE);
+}
+
+static int interface_config_write(struct vty *vty)
+{
+ struct write_map_ctx mapctx;
+ struct listnode *node;
+ struct interface *ifp;
+ struct nhrp_interface *nifp;
+ struct nhrp_nhs *nhs;
+ const char *aficmd;
+ afi_t afi;
+ char buf[SU_ADDRSTRLEN];
+ int i;
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+ vty_out(vty, "interface %s%s", ifp->name, VTY_NEWLINE);
+ if (ifp->desc)
+ vty_out(vty, " description %s%s", ifp->desc, VTY_NEWLINE);
+
+ 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, "%s", VTY_NEWLINE);
+ }
+ if (nifp->source)
+ vty_out(vty, " tunnel source %s%s",
+ nifp->source, VTY_NEWLINE);
+
+ 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%s",
+ aficmd, ad->network_id,
+ VTY_NEWLINE);
+
+ if (ad->holdtime != NHRPD_DEFAULT_HOLDTIME)
+ vty_out(vty, " %s nhrp holdtime %u%s",
+ aficmd, ad->holdtime,
+ VTY_NEWLINE);
+
+ if (ad->configured_mtu < 0)
+ vty_out(vty, " %s nhrp mtu opennhrp%s",
+ aficmd, VTY_NEWLINE);
+ else if (ad->configured_mtu)
+ vty_out(vty, " %s nhrp mtu %u%s",
+ aficmd, ad->configured_mtu,
+ VTY_NEWLINE);
+
+ 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%s",
+ aficmd, interface_flags_desc[i].str, VTY_NEWLINE);
+ }
+
+ mapctx = (struct write_map_ctx) {
+ .vty = vty,
+ .family = afi2family(afi),
+ .aficmd = aficmd,
+ };
+ nhrp_cache_foreach(ifp, interface_config_write_nhrp_map, &mapctx);
+
+ list_for_each_entry(nhs, &ad->nhslist_head, nhslist_entry) {
+ vty_out(vty, " %s nhrp nhs %s nbma %s%s",
+ aficmd,
+ sockunion_family(&nhs->proto_addr) == AF_UNSPEC ? "dynamic" : sockunion2str(&nhs->proto_addr, buf, sizeof buf),
+ nhs->nbma_fqdn,
+ VTY_NEWLINE);
+ }
+ }
+
+ vty_out (vty, "!%s", VTY_NEWLINE);
+ }
+
+ return 0;
+}
+
+void nhrp_config_init(void)
+{
+ install_node(&zebra_node, nhrp_config_write);
+ install_default(ZEBRA_NODE);
+
+ /* global commands */
+ install_element(VIEW_NODE, &show_debugging_nhrp_cmd);
+ install_element(VIEW_NODE, &show_ip_nhrp_cmd);
+ install_element(VIEW_NODE, &show_dmvpn_cmd);
+ install_element(ENABLE_NODE, &show_debugging_nhrp_cmd);
+ install_element(ENABLE_NODE, &show_ip_nhrp_cmd);
+ install_element(ENABLE_NODE, &show_dmvpn_cmd);
+ install_element(ENABLE_NODE, &clear_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);
+
+ /* interface specific commands */
+ install_node(&nhrp_interface_node, interface_config_write);
+ install_default(INTERFACE_NODE);
+
+ install_element(CONFIG_NODE, &interface_cmd);
+ install_element(CONFIG_NODE, &no_interface_cmd);
+ install_element(INTERFACE_NODE, &interface_cmd);
+ install_element(INTERFACE_NODE, &no_interface_cmd);
+ 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_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..c8f8816
--- /dev/null
+++ b/nhrpd/nhrpd.h
@@ -0,0 +1,440 @@
+/* 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 "list.h"
+
+#include "zebra.h"
+#include "zbuf.h"
+#include "zclient.h"
+#include "log.h"
+
+#define NHRPD_DEFAULT_HOLDTIME 7200
+
+#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)
+
+#define NHRP_VTY_PORT 2612
+#define NHRP_DEFAULT_CONFIG "nhrpd.conf"
+
+#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
+
+extern unsigned int debug_flags;
+extern struct thread_master *master;
+
+#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
+
+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,
+};
+
+struct notifier_block;
+
+typedef void (*notifier_fn_t)(struct notifier_block *, unsigned long);
+
+struct notifier_block {
+ struct list_head notifier_entry;
+ notifier_fn_t action;
+};
+
+struct notifier_list {
+ struct list_head notifier_head;
+};
+
+#define NOTIFIER_LIST_INITIALIZER(l) \
+ { .notifier_head = LIST_INITIALIZER((l)->notifier_head) }
+
+static inline void notifier_init(struct notifier_list *l)
+{
+ list_init(&l->notifier_head);
+}
+
+static inline void notifier_add(struct notifier_block *n, struct notifier_list *l, notifier_fn_t action)
+{
+ n->action = action;
+ list_add_tail(&n->notifier_entry, &l->notifier_head);
+}
+
+static inline void notifier_del(struct notifier_block *n)
+{
+ list_del(&n->notifier_entry);
+}
+
+static inline void notifier_call(struct notifier_list *l, int cmd)
+{
+ struct notifier_block *n, *nn;
+ list_for_each_entry_safe(n, nn, &l->notifier_head, notifier_entry)
+ n->action(n, cmd);
+}
+
+static inline int notifier_active(struct notifier_list *l)
+{
+ return !list_empty(&l->notifier_head);
+}
+
+struct resolver_query {
+ void (*callback)(struct resolver_query *, int n, union sockunion *);
+};
+
+void resolver_init(void);
+void resolver_resolve(struct resolver_query *query, int af, const char *hostname, void (*cb)(struct resolver_query *, int, union sockunion *));
+
+void nhrp_zebra_init(void);
+void nhrp_zebra_terminate(void);
+
+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_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;
+ uint8_t ipsec;
+ 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 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 {
+ 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;
+ struct nhrp_peer *peer;
+ time_t expires;
+ uint32_t mtu;
+ } 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;
+};
+
+struct nhrp_nhs {
+ struct interface *ifp;
+ struct list_head 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 list_head reglist_head;
+};
+
+#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 linkidx;
+ uint32_t grekey;
+
+ struct hash *peer_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 list_head nhslist_head;
+ } afi[AFI_MAX];
+};
+
+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);
+
+int nhrp_interface_add(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id);
+int nhrp_interface_delete(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id);
+int nhrp_interface_up(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id);
+int nhrp_interface_down(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id);
+int nhrp_interface_address_add(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id);
+int nhrp_interface_address_delete(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id);
+
+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);
+
+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_nhs *nhs);
+void nhrp_nhs_terminate(void);
+
+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(int command, struct zclient *zclient, zebra_size_t length, vrf_id_t vrf_id);
+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);
+
+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_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);
+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_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);
+
+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 *);
+
+#endif
diff --git a/nhrpd/os.h b/nhrpd/os.h
new file mode 100644
index 0000000..0fbe8b0
--- /dev/null
+++ b/nhrpd/os.h
@@ -0,0 +1,5 @@
+
+int os_socket(void);
+int os_sendmsg(const uint8_t *buf, size_t len, int ifindex, const uint8_t *addr, size_t addrlen);
+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..24b3199
--- /dev/null
+++ b/nhrpd/reqid.c
@@ -0,0 +1,49 @@
+#include "zebra.h"
+#include "hash.h"
+#include "nhrpd.h"
+
+static unsigned int nhrp_reqid_key(void *data)
+{
+ struct nhrp_reqid *r = data;
+ return r->request_id;
+}
+
+static int 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);
+ 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;
+ 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/resolver.c b/nhrpd/resolver.c
new file mode 100644
index 0000000..07bdb73
--- /dev/null
+++ b/nhrpd/resolver.c
@@ -0,0 +1,190 @@
+/* C-Ares integration to Quagga mainloop
+ * 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 <ares.h>
+#include <ares_version.h>
+
+#include "vector.h"
+#include "thread.h"
+#include "nhrpd.h"
+
+struct resolver_state {
+ ares_channel channel;
+ struct thread *timeout;
+ vector read_threads, write_threads;
+};
+
+static struct resolver_state state;
+
+#define THREAD_RUNNING ((struct thread *)-1)
+
+static void resolver_update_timeouts(struct resolver_state *r);
+
+static int resolver_cb_timeout(struct thread *t)
+{
+ struct resolver_state *r = THREAD_ARG(t);
+
+ r->timeout = THREAD_RUNNING;
+ ares_process(r->channel, NULL, NULL);
+ r->timeout = NULL;
+ resolver_update_timeouts(r);
+
+ return 0;
+}
+
+static int resolver_cb_socket_readable(struct thread *t)
+{
+ struct resolver_state *r = THREAD_ARG(t);
+ int fd = THREAD_FD(t);
+
+ vector_set_index(r->read_threads, fd, THREAD_RUNNING);
+ ares_process_fd(r->channel, fd, ARES_SOCKET_BAD);
+ if (vector_lookup(r->read_threads, fd) == THREAD_RUNNING) {
+ t = NULL;
+ THREAD_READ_ON(master, t, resolver_cb_socket_readable, r, fd);
+ vector_set_index(r->read_threads, fd, t);
+ }
+ resolver_update_timeouts(r);
+
+ return 0;
+}
+
+static int resolver_cb_socket_writable(struct thread *t)
+{
+ struct resolver_state *r = THREAD_ARG(t);
+ int fd = THREAD_FD(t);
+
+ vector_set_index(r->write_threads, fd, THREAD_RUNNING);
+ ares_process_fd(r->channel, ARES_SOCKET_BAD, fd);
+ if (vector_lookup(r->write_threads, fd) == THREAD_RUNNING) {
+ t = NULL;
+ THREAD_WRITE_ON(master, t, resolver_cb_socket_writable, r, fd);
+ vector_set_index(r->write_threads, fd, t);
+ }
+ resolver_update_timeouts(r);
+
+ return 0;
+}
+
+static void resolver_update_timeouts(struct resolver_state *r)
+{
+ struct timeval *tv, tvbuf;
+
+ if (r->timeout == THREAD_RUNNING) return;
+
+ THREAD_OFF(r->timeout);
+ tv = ares_timeout(r->channel, NULL, &tvbuf);
+ if (tv) {
+ unsigned int timeoutms = tv->tv_sec * 1000 + tv->tv_usec / 1000;
+ THREAD_TIMER_MSEC_ON(master, r->timeout, resolver_cb_timeout, r, timeoutms);
+ }
+}
+
+static void ares_socket_cb(void *data, ares_socket_t fd, int readable, int writable)
+{
+ struct resolver_state *r = (struct resolver_state *) data;
+ struct thread *t;
+
+ if (readable) {
+ t = vector_lookup_ensure(r->read_threads, fd);
+ if (!t) {
+ THREAD_READ_ON(master, t, resolver_cb_socket_readable, r, fd);
+ vector_set_index(r->read_threads, fd, t);
+ }
+ } else {
+ t = vector_lookup(r->read_threads, fd);
+ if (t) {
+ if (t != THREAD_RUNNING) {
+ THREAD_OFF(t);
+ }
+ vector_unset(r->read_threads, fd);
+ }
+ }
+
+ if (writable) {
+ t = vector_lookup_ensure(r->write_threads, fd);
+ if (!t) {
+ THREAD_READ_ON(master, t, resolver_cb_socket_writable, r, fd);
+ vector_set_index(r->write_threads, fd, t);
+ }
+ } else {
+ t = vector_lookup(r->write_threads, fd);
+ if (t) {
+ if (t != THREAD_RUNNING) {
+ THREAD_OFF(t);
+ }
+ vector_unset(r->write_threads, fd);
+ }
+ }
+}
+
+void resolver_init(void)
+{
+ struct ares_options ares_opts;
+
+ state.read_threads = vector_init(1);
+ state.write_threads = vector_init(1);
+
+ ares_opts = (struct ares_options) {
+ .sock_state_cb = &ares_socket_cb,
+ .sock_state_cb_data = &state,
+ .timeout = 2,
+ .tries = 3,
+ };
+
+ ares_init_options(&state.channel, &ares_opts,
+ ARES_OPT_SOCK_STATE_CB | ARES_OPT_TIMEOUT |
+ ARES_OPT_TRIES);
+}
+
+
+static void ares_address_cb(void *arg, int status, int timeouts, struct hostent *he)
+{
+ struct resolver_query *query = (struct resolver_query *) arg;
+ union sockunion addr[16];
+ size_t i;
+
+ if (status != ARES_SUCCESS) {
+ debugf(NHRP_DEBUG_COMMON, "[%p] Resolving failed", query);
+ query->callback(query, -1, NULL);
+ query->callback = NULL;
+ return;
+ }
+
+ for (i = 0; he->h_addr_list[i] != NULL && i < ZEBRA_NUM_OF(addr); i++) {
+ memset(&addr[i], 0, sizeof(addr[i]));
+ addr[i].sa.sa_family = he->h_addrtype;
+ switch (he->h_addrtype) {
+ case AF_INET:
+ memcpy(&addr[i].sin.sin_addr, (uint8_t *) he->h_addr_list[i], he->h_length);
+ break;
+ case AF_INET6:
+ memcpy(&addr[i].sin6.sin6_addr, (uint8_t *) he->h_addr_list[i], he->h_length);
+ break;
+ }
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "[%p] Resolved with %d results", query, (int) i);
+ query->callback(query, i, &addr[0]);
+ query->callback = NULL;
+}
+
+void resolver_resolve(struct resolver_query *query, int af, const char *hostname, void (*callback)(struct resolver_query *, int, union sockunion *))
+{
+ if (query->callback != NULL) {
+ zlog_err("Trying to resolve '%s', but previous query was not finished yet", hostname);
+ return;
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "[%p] Resolving '%s'", query, hostname);
+
+ query->callback = callback;
+ ares_gethostbyname(state.channel, hostname, af, ares_address_cb, query);
+ resolver_update_timeouts(&state);
+}
diff --git a/nhrpd/vici.c b/nhrpd/vici.c
new file mode 100644
index 0000000..507dd14
--- /dev/null
+++ b/nhrpd/vici.c
@@ -0,0 +1,482 @@
+/* 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.
+ */
+
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "thread.h"
+#include "zbuf.h"
+#include "log.h"
+#include "nhrpd.h"
+
+#include "vici.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->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->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 int 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_TIMER_ON(master, vici->t_reconnect, vici_reconnect, vici, 2);
+}
+
+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;
+ struct blob val;
+
+ 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);
+ }
+ } else {
+ nhrp_vc_ipsec_updown(sactx->child_uniqueid, 0);
+ }
+ }
+ break;
+ default:
+ switch (key->ptr[0]) {
+ case 'l':
+ if (blob_equal(key, "local-host") && ctx->nsections == 1) {
+ if (blob2buf(val, buf, sizeof(buf)))
+ str2sockunion(buf, &sactx->local.host);
+ } 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)))
+ str2sockunion(buf, &sactx->remote.host);
+ } 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 vici_recv_sa(struct vici_conn *vici, struct zbuf *msg, int event)
+{
+ char buf[32];
+ struct handle_sa_ctx ctx = {
+ .event = event,
+ };
+
+ 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;
+
+ 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_EVENT_UNKNOWN:
+ zlog_err("VICI: StrongSwan does not support mandatory events (unpatched?)");
+ break;
+ case VICI_EVENT_CONFIRM:
+ case VICI_CMD_RESPONSE:
+ break;
+ default:
+ zlog_notice("VICI: Unrecognized message type %d", msgtype);
+ break;
+ }
+}
+
+static int vici_read(struct thread *t)
+{
+ struct vici_conn *vici = THREAD_ARG(t);
+ struct zbuf *ibuf = &vici->ibuf;
+ struct zbuf pktbuf;
+
+ vici->t_read = NULL;
+ if (zbuf_read(ibuf, vici->fd, (size_t) -1) < 0) {
+ vici_connection_error(vici);
+ return 0;
+ }
+
+ /* 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_READ_ON(master, vici->t_read, vici_read, vici, vici->fd);
+ return 0;
+}
+
+static int vici_write(struct thread *t)
+{
+ struct vici_conn *vici = THREAD_ARG(t);
+ int r;
+
+ vici->t_write = NULL;
+ r = zbufq_write(&vici->obuf, vici->fd);
+ if (r > 0) {
+ THREAD_WRITE_ON(master, vici->t_write, vici_write, vici, vici->fd);
+ } else if (r < 0) {
+ vici_connection_error(vici);
+ }
+
+ return 0;
+}
+
+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_WRITE_ON(master, vici->t_write, vici_write, vici, vici->fd);
+}
+
+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;
+ case VICI_END:
+ 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 int vici_reconnect(struct thread *t)
+{
+ struct vici_conn *vici = THREAD_ARG(t);
+ int fd;
+
+ vici->t_reconnect = NULL;
+ if (vici->fd >= 0) return 0;
+
+ fd = sock_open_unix("/var/run/charon.vici");
+ if (fd < 0) {
+ zlog_warn("%s: failure connecting VICI socket: %s",
+ __PRETTY_FUNCTION__, strerror(errno));
+ THREAD_TIMER_ON(master, vici->t_reconnect, vici_reconnect, vici, 2);
+ return 0;
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "VICI: Connected");
+ vici->fd = fd;
+ THREAD_READ_ON(master, vici->t_read, vici_read, vici, vici->fd);
+
+ /* 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);
+
+ return 0;
+}
+
+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_TIMER_MSEC_ON(master, vici->t_reconnect, vici_reconnect, vici, 10);
+}
+
+void vici_terminate(void)
+{
+}
+
+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", 2, "-1",
+ VICI_KEY_VALUE, "async", 1, "1",
+ VICI_KEY_VALUE, "init-limits", 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 (struct sockaddr_un));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, path, strlen (path));
+
+ ret = connect(fd, (struct sockaddr *) &addr, sizeof(addr.sun_family) + strlen(addr.sun_path));
+ if (ret < 0) {
+ close(fd);
+ return -1;
+ }
+
+ fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
+
+ return fd;
+}
diff --git a/nhrpd/vici.h b/nhrpd/vici.h
new file mode 100644
index 0000000..24b900b
--- /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..ead7cfd
--- /dev/null
+++ b/nhrpd/zbuf.c
@@ -0,0 +1,219 @@
+/* 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.
+ */
+
+#define _GNU_SOURCE
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include "zassert.h"
+#include "zbuf.h"
+#include "memory.h"
+#include "memtypes.h"
+#include "nhrpd.h"
+
+#define ERRNO_IO_RETRY(EN) (((EN) == EAGAIN) || ((EN) == EWOULDBLOCK) || ((EN) == EINTR))
+
+struct zbuf *zbuf_alloc(size_t size)
+{
+ struct zbuf *zb;
+
+ zb = XMALLOC(MTYPE_STREAM_DATA, sizeof(*zb) + size);
+ if (!zb)
+ return NULL;
+
+ 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_STREAM_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)
+{
+ zassert((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 (r < 0 && 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 (r < 0 && 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 (r < 0 && 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 = LIST_INITIALIZER(zbq->queue_head),
+ };
+}
+
+void zbufq_reset(struct zbuf_queue *zbq)
+{
+ struct zbuf *buf, *bufn;
+
+ list_for_each_entry_safe(buf, bufn, &zbq->queue_head, queue_list) {
+ list_del(&buf->queue_list);
+ zbuf_free(buf);
+ }
+}
+
+void zbufq_queue(struct zbuf_queue *zbq, struct zbuf *zb)
+{
+ list_add_tail(&zb->queue_list, &zbq->queue_head);
+}
+
+int zbufq_write(struct zbuf_queue *zbq, int fd)
+{
+ struct iovec iov[16];
+ struct zbuf *zb, *zbn;
+ ssize_t r;
+ size_t iovcnt = 0;
+
+ list_for_each_entry_safe(zb, zbn, &zbq->queue_head, queue_list) {
+ iov[iovcnt++] = (struct iovec) {
+ .iov_base = zb->head,
+ .iov_len = zbuf_used(zb),
+ };
+ if (iovcnt >= ZEBRA_NUM_OF(iov))
+ break;
+ }
+
+ r = writev(fd, iov, iovcnt);
+ if (r < 0)
+ return r;
+
+ list_for_each_entry_safe(zb, zbn, &zbq->queue_head, queue_list) {
+ if (r < (ssize_t)zbuf_used(zb)) {
+ zb->head += r;
+ return 1;
+ }
+
+ r -= zbuf_used(zb);
+ list_del(&zb->queue_list);
+ 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);
+}
diff --git a/nhrpd/zbuf.h b/nhrpd/zbuf.h
new file mode 100644
index 0000000..73d7073
--- /dev/null
+++ b/nhrpd/zbuf.h
@@ -0,0 +1,189 @@
+/* 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 "zassert.h"
+#include "list.h"
+
+struct zbuf {
+ struct list_head queue_list;
+ unsigned allocated : 1;
+ unsigned error : 1;
+ uint8_t *buf, *end;
+ uint8_t *head, *tail;
+};
+
+struct zbuf_queue {
+ struct list_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) ((void *)__zbuf_pull(zb, sz, 1))
+#define zbuf_may_pull(zb, type) ((type *)__zbuf_pull(zb, sizeof(type), 0))
+#define zbuf_may_pulln(zb, sz) ((void *)__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) ((void *)__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) ((void *)__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 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..2216d97
--- /dev/null
+++ b/nhrpd/znl.c
@@ -0,0 +1,160 @@
+/* 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 <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;
+
+ fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+ 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..2cd630b
--- /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);
+
diff --git a/vtysh/Makefile.am b/vtysh/Makefile.am
index e44cd49..983103f 100644
--- a/vtysh/Makefile.am
+++ b/vtysh/Makefile.am
@@ -24,6 +24,7 @@ vtysh_cmd_FILES = $(top_srcdir)/bgpd/*.c $(top_srcdir)/isisd/*.c \
$(top_srcdir)/ospfd/*.c $(top_srcdir)/ospf6d/*.c \
$(top_srcdir)/ripd/*.c $(top_srcdir)/ripngd/*.c \
$(top_srcdir)/pimd/pim_cmd.c \
+ $(top_srcdir)/nhrpd/nhrp_vty.c \
$(top_srcdir)/lib/keychain.c $(top_srcdir)/lib/routemap.c \
$(top_srcdir)/lib/filter.c $(top_srcdir)/lib/plist.c \
$(top_srcdir)/lib/distribute.c $(top_srcdir)/lib/if_rmap.c \
diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c
index 2e2203d..1ee8582 100644
--- a/vtysh/vtysh.c
+++ b/vtysh/vtysh.c
@@ -60,6 +60,7 @@ struct vtysh_client
{ .fd = -1, .name = "bgpd", .flag = VTYSH_BGPD, .path = BGP_VTYSH_PATH},
{ .fd = -1, .name = "isisd", .flag = VTYSH_ISISD, .path = ISIS_VTYSH_PATH},
{ .fd = -1, .name = "pimd", .flag = VTYSH_PIMD, .path = PIM_VTYSH_PATH},
+ { .fd = -1, .name = "nhrpd", .flag = VTYSH_NHRPD, .path = NHRP_VTYSH_PATH},
};


diff --git a/vtysh/vtysh.h b/vtysh/vtysh.h
index 1681a71..02ff7fd 100644
--- a/vtysh/vtysh.h
+++ b/vtysh/vtysh.h
@@ -31,9 +31,10 @@
#define VTYSH_ISISD 0x40
#define VTYSH_BABELD 0x80
#define VTYSH_PIMD 0x100
-#define VTYSH_ALL VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_ISISD|VTYSH_BABELD|VTYSH_PIMD
+#define VTYSH_NHRPD 0x200
+#define VTYSH_ALL VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_ISISD|VTYSH_BABELD|VTYSH_PIMD|VTYSH_NHRPD
#define VTYSH_RMAP VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_BABELD
-#define VTYSH_INTERFACE VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_ISISD|VTYSH_BABELD|VTYSH_PIMD
+#define VTYSH_INTERFACE VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_ISISD|VTYSH_BABELD|VTYSH_PIMD|VTYSH_NHRPD

/* vtysh local configuration file. */
#define VTYSH_DEFAULT_CONFIG "vtysh.conf"
diff --git a/zebra/client_main.c b/zebra/client_main.c
index 43ab299..75c8867 100644
--- a/zebra/client_main.c
+++ b/zebra/client_main.c
@@ -120,6 +120,7 @@ struct zebra_info
{ "ospf", ZEBRA_ROUTE_OSPF },
{ "ospf6", ZEBRA_ROUTE_OSPF6 },
{ "bgp", ZEBRA_ROUTE_BGP },
+ { "nhrp", ZEBRA_ROUTE_NHRP },
{ NULL, 0 }
};

diff --git a/zebra/zebra_rib.c b/zebra/zebra_rib.c
index abb9560..b03ccc2 100644
--- a/zebra/zebra_rib.c
+++ b/zebra/zebra_rib.c
@@ -72,6 +72,7 @@ static const struct
[ZEBRA_ROUTE_ISIS] = {ZEBRA_ROUTE_ISIS, 115},
[ZEBRA_ROUTE_BGP] = {ZEBRA_ROUTE_BGP, 20 /* IBGP is 200. */},
[ZEBRA_ROUTE_BABEL] = {ZEBRA_ROUTE_BABEL, 95},
+ [ZEBRA_ROUTE_NHRP] = {ZEBRA_ROUTE_NHRP, 10},
/* no entry/default: 150 */
};

@@ -1423,6 +1424,7 @@ static const u_char meta_queue_map[ZEBRA_ROUTE_MAX] = {
[ZEBRA_ROUTE_BGP] = 3,
[ZEBRA_ROUTE_HSLS] = 4,
[ZEBRA_ROUTE_BABEL] = 2,
+ [ZEBRA_ROUTE_NHRP] = 2,
};

/* Look into the RN and queue it into one or more priority queues,
diff --git a/zebra/zebra_rnh.c b/zebra/zebra_rnh.c
index 5762d3f..98ca6d0 100644
--- a/zebra/zebra_rnh.c
+++ b/zebra/zebra_rnh.c
@@ -223,15 +223,24 @@ zebra_evaluate_rnh_table (vrf_id_t vrfid, int family)
{
if (CHECK_FLAG (rib->status, RIB_ENTRY_REMOVED))
continue;
- if (CHECK_FLAG (rib->status, RIB_ENTRY_SELECTED_FIB))
+ if (! CHECK_FLAG (rib->status, RIB_ENTRY_SELECTED_FIB))
+ continue;
+
+ if (CHECK_FLAG(rnh->flags, ZEBRA_NHT_CONNECTED))
{
- if (CHECK_FLAG(rnh->flags, ZEBRA_NHT_CONNECTED))
+ if (rib->type == ZEBRA_ROUTE_CONNECT)
+ break;
+
+ if (rib->type == ZEBRA_ROUTE_NHRP)
{
- if (rib->type == ZEBRA_ROUTE_CONNECT)
+ struct nexthop *nexthop;
+ for (nexthop = rib->nexthop; nexthop; nexthop = nexthop->next)
+ if (nexthop->type == NEXTHOP_TYPE_IFINDEX ||
+ nexthop->type == NEXTHOP_TYPE_IFNAME)
+ break;
+ if (nexthop)
break;
}
- else
- break;
}
}
}
diff --git a/zebra/zebra_vty.c b/zebra/zebra_vty.c
index 38f61e9..c73896b 100644
--- a/zebra/zebra_vty.c
+++ b/zebra/zebra_vty.c
@@ -2121,6 +2121,7 @@ vty_show_ip_route_detail (struct vty *vty, struct route_node *rn, int mcast)
|| rib->type == ZEBRA_ROUTE_OSPF6
|| rib->type == ZEBRA_ROUTE_BABEL
|| rib->type == ZEBRA_ROUTE_ISIS
+ || rib->type == ZEBRA_ROUTE_NHRP
|| rib->type == ZEBRA_ROUTE_BGP)
{
time_t uptime;
@@ -2341,6 +2342,7 @@ vty_show_ip_route (struct vty *vty, struct route_node *rn, struct rib *rib)
|| rib->type == ZEBRA_ROUTE_OSPF6
|| rib->type == ZEBRA_ROUTE_BABEL
|| rib->type == ZEBRA_ROUTE_ISIS
+ || rib->type == ZEBRA_ROUTE_NHRP
|| rib->type == ZEBRA_ROUTE_BGP)
{
time_t uptime;
--
2.10.1
c***@netdef.org
2016-10-19 15:00:10 UTC
Permalink
Continous Integration Result: FAILED

See below for issues.
This is an EXPERIMENTAL automated CI system.
For questions and feedback, feel free to email
Martin Winter <***@opensourcerouting.org>.

Patches applied :
Patchwork 2151: http://patchwork.quagga.net/patch/2151
[quagga-dev,16311,1/5] zebra: use FIB state for nexthop tracking
Patchwork 2152: http://patchwork.quagga.net/patch/2152
[quagga-dev,16312,2/5] zebra: fix nht validity checking to be same as when it"s resolved
Patchwork 2149: http://patchwork.quagga.net/patch/2149
[quagga-dev,16314,3/5] bgpd: honor disable-connected-check option with next hop tracking
Patchwork 2153: http://patchwork.quagga.net/patch/2153
[quagga-dev,16315,4/5] bgpd: simplify ebgp-multihop and ttl-security handling
Patchwork 2154: http://patchwork.quagga.net/patch/2154
[quagga-dev,16316,5/5] nhrpd: implement next hop resolution protocol
Tested on top of Git : cfb1fae (as of 20161017.164136 UTC)
CI System Testrun URL: https://ci1.netdef.org/browse/QUAGGA-QPWORK-375/


Get source and apply patch from patchwork: Successful
----------------

Building Stage: Failed
----------------
NetBSD7 amd64 build: Successful
OmniOS amd64 build: Successful
Openbsd58 amd64 build: Successful
Ubuntu1204 amd64 build: Successful
FreeBSD10 amd64 build: Successful
FreeBSD9 amd64 build: Successful
NetBSD6 amd64 build: Successful
Debian8 amd64 build: Successful
FreeBSD8 amd64 build: Successful

Ubuntu1404 amd64 build: Unknown Log <log_configure.txt>
URL: https://ci1.netdef.org/browse/QUAGGA-QPWORK-375/artifact/CI001BUILD/ErrorLog/log_configure.txt
Ubuntu1404 amd64 build: No useful log found
Ubuntu1604 amd64 build: Unknown Log <log_configure.txt>
URL: https://ci1.netdef.org/browse/QUAGGA-QPWORK-375/artifact/CI014BUILD/ErrorLog/log_configure.txt
Ubuntu1604 amd64 build: No useful log found
Package building failed for CentOS7 amd64 build: (see full log in attachment centos7_amd64_pkgbuild.log)
^
<command-line>:0:0: note: this is the location of the previous definition
zbuf.c:15:18: fatal error: zbuf.h: No such file or directory
#include "zbuf.h"
^
compilation terminated.
make[2]: Leaving directory `/home/ci/cibuild.375/rpmbuild/BUILD/quagga-1.0.20160315-ci.NetDEF.org-20161019.144004-git.cfb1fae/nhrpd'
make[2]: *** [zbuf.o] Error 1
make[1]: Leaving directory `/home/ci/cibuild.375/rpmbuild/BUILD/quagga-1.0.20160315-ci.NetDEF.org-20161019.144004-git.cfb1fae'
Package building failed for CentOS6 amd64 build: (see full log in attachment centos6_amd64_pkgbuild.log)
zbuf.c:10:1: warning: "_GNU_SOURCE" redefined
<command-line>: warning: this is the location of the previous definition
zbuf.c:15:18: error: zbuf.h: No such file or directory
zbuf.c:18:19: error: nhrpd.h: No such file or directory
zbuf.c:26: error: dereferencing pointer to incomplete type
zbuf.c:30: warning: implicit declaration of function 'zbuf_init'
zbuf.c:30: error: invalid use of undefined type 'struct zbuf'
zbuf.c:31: error: dereferencing pointer to incomplete type
Regards,
NetDEF/OpenSourceRouting Continous Integration (CI) System

---
OpenSourceRouting.org is a project of the Network Device Education Foundation,
For more information, see www.netdef.org and www.opensourcerouting.org
For questions in regards to this CI System, contact Martin Winter, ***@netdef.org
Timo Teräs
2016-11-07 16:58:20 UTC
Permalink
This provides DMVPN support and integrates to strongSwan. Please read
README.nhrpd and README.kernel for more details.
---
Supercedes the earlier nhrp commit. Only change is fixing the hunk for
zebra/zebra_rnh.c to properly track non-connected routes (it was missing
one match condition).

.gitignore | 2 +
Makefile.am | 4 +-
SERVICES | 1 +
configure.ac | 27 +-
lib/log.c | 5 +
lib/log.h | 3 +-
lib/memtypes.c | 14 +
lib/route_types.txt | 2 +
nhrpd/Makefile.am | 34 ++
nhrpd/README.kernel | 145 ++++++++
nhrpd/README.nhrpd | 137 ++++++++
nhrpd/linux.c | 153 ++++++++
nhrpd/list.h | 191 ++++++++++
nhrpd/netlink.c | 398 +++++++++++++++++++++
nhrpd/netlink.h | 21 ++
nhrpd/nhrp_cache.c | 341 ++++++++++++++++++
nhrpd/nhrp_event.c | 280 +++++++++++++++
nhrpd/nhrp_interface.c | 404 +++++++++++++++++++++
nhrpd/nhrp_main.c | 246 +++++++++++++
nhrpd/nhrp_nhs.c | 369 ++++++++++++++++++++
nhrpd/nhrp_packet.c | 312 +++++++++++++++++
nhrpd/nhrp_peer.c | 860 +++++++++++++++++++++++++++++++++++++++++++++
nhrpd/nhrp_protocol.h | 128 +++++++
nhrpd/nhrp_route.c | 345 ++++++++++++++++++
nhrpd/nhrp_shortcut.c | 402 +++++++++++++++++++++
nhrpd/nhrp_vc.c | 217 ++++++++++++
nhrpd/nhrp_vty.c | 928 +++++++++++++++++++++++++++++++++++++++++++++++++
nhrpd/nhrpd.h | 440 +++++++++++++++++++++++
nhrpd/os.h | 5 +
nhrpd/reqid.c | 49 +++
nhrpd/resolver.c | 190 ++++++++++
nhrpd/vici.c | 482 +++++++++++++++++++++++++
nhrpd/vici.h | 24 ++
nhrpd/zbuf.c | 219 ++++++++++++
nhrpd/zbuf.h | 189 ++++++++++
nhrpd/znl.c | 160 +++++++++
nhrpd/znl.h | 29 ++
vtysh/Makefile.am | 1 +
vtysh/vtysh.c | 1 +
vtysh/vtysh.h | 5 +-
zebra/client_main.c | 1 +
zebra/zebra_rib.c | 2 +
zebra/zebra_rnh.c | 21 +-
zebra/zebra_vty.c | 2 +
44 files changed, 7778 insertions(+), 11 deletions(-)
create mode 100644 nhrpd/Makefile.am
create mode 100644 nhrpd/README.kernel
create mode 100644 nhrpd/README.nhrpd
create mode 100644 nhrpd/linux.c
create mode 100644 nhrpd/list.h
create mode 100644 nhrpd/netlink.c
create mode 100644 nhrpd/netlink.h
create mode 100644 nhrpd/nhrp_cache.c
create mode 100644 nhrpd/nhrp_event.c
create mode 100644 nhrpd/nhrp_interface.c
create mode 100644 nhrpd/nhrp_main.c
create mode 100644 nhrpd/nhrp_nhs.c
create mode 100644 nhrpd/nhrp_packet.c
create mode 100644 nhrpd/nhrp_peer.c
create mode 100644 nhrpd/nhrp_protocol.h
create mode 100644 nhrpd/nhrp_route.c
create mode 100644 nhrpd/nhrp_shortcut.c
create mode 100644 nhrpd/nhrp_vc.c
create mode 100644 nhrpd/nhrp_vty.c
create mode 100644 nhrpd/nhrpd.h
create mode 100644 nhrpd/os.h
create mode 100644 nhrpd/reqid.c
create mode 100644 nhrpd/resolver.c
create mode 100644 nhrpd/vici.c
create mode 100644 nhrpd/vici.h
create mode 100644 nhrpd/zbuf.c
create mode 100644 nhrpd/zbuf.h
create mode 100644 nhrpd/znl.c
create mode 100644 nhrpd/znl.h

diff --git a/.gitignore b/.gitignore
index a281555..a8da62f 100644
--- a/.gitignore
+++ b/.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
diff --git a/Makefile.am b/Makefile.am
index 0cd75be..3dea489 100644
--- a/Makefile.am
+++ b/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

diff --git a/SERVICES b/SERVICES
index c69d0c1..0322d45 100644
--- a/SERVICES
+++ b/SERVICES
@@ -18,3 +18,4 @@ ospf6d 2606/tcp
ospfapi 2607/tcp
isisd 2608/tcp
pimd 2611/tcp
+nhrpd 2612/tcp
diff --git a/configure.ac b/configure.ac
index f027762..dc41418 100755
--- a/configure.ac
+++ b/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,
@@ -1186,6 +1189,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
@@ -1241,6 +1255,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)
@@ -1276,6 +1291,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
@@ -1551,6 +1574,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)
@@ -1561,6 +1585,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)
@@ -1588,7 +1613,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
diff --git a/lib/log.c b/lib/log.c
index 42b7717..d437066 100644
--- a/lib/log.c
+++ b/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;
}
diff --git a/lib/log.h b/lib/log.h
index 7aa0896..c172009 100644
--- a/lib/log.h
+++ b/lib/log.h
@@ -54,7 +54,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
diff --git a/lib/memtypes.c b/lib/memtypes.c
index 8abe99d..ba2bacf 100644
--- a/lib/memtypes.c
+++ b/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},
};
diff --git a/lib/route_types.txt b/lib/route_types.txt
index 1b85607..811d24e 100644
--- a/lib/route_types.txt
+++ b/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)"
diff --git a/nhrpd/Makefile.am b/nhrpd/Makefile.am
new file mode 100644
index 0000000..d7e9e3a
--- /dev/null
+++ b/nhrpd/Makefile.am
@@ -0,0 +1,34 @@
+## 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.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
diff --git a/nhrpd/README.kernel b/nhrpd/README.kernel
new file mode 100644
index 0000000..5831316
--- /dev/null
+++ b/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.
+
diff --git a/nhrpd/README.nhrpd b/nhrpd/README.nhrpd
new file mode 100644
index 0000000..569b3f4
--- /dev/null
+++ b/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
+
diff --git a/nhrpd/linux.c b/nhrpd/linux.c
new file mode 100644
index 0000000..1e9c69e
--- /dev/null
+++ b/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;
+}
diff --git a/nhrpd/list.h b/nhrpd/list.h
new file mode 100644
index 0000000..32f21ed
--- /dev/null
+++ b/nhrpd/list.h
@@ -0,0 +1,191 @@
+/* Linux kernel style list handling function
+ *
+ * Written from scratch by Timo Teräs <***@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
diff --git a/nhrpd/netlink.c b/nhrpd/netlink.c
new file mode 100644
index 0000000..7fe2e01
--- /dev/null
+++ b/nhrpd/netlink.c
@@ -0,0 +1,398 @@
+/* NHRP netlink/neighbor table arpd code
+ * 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 <net/if.h>
+#include <netinet/ip.h>
+#include <netinet/if_ether.h>
+#include <linux/netlink.h>
+#include <linux/neighbour.h>
+#include <linux/netfilter/nfnetlink_log.h>
+#include <linux/if_tunnel.h>
+
+#include "thread.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;
+static int netlink_req_fd = -1;
+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;
+}
+
+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);
+}
diff --git a/nhrpd/netlink.h b/nhrpd/netlink.h
new file mode 100644
index 0000000..2285093
--- /dev/null
+++ b/nhrpd/netlink.h
@@ -0,0 +1,21 @@
+/* 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.
+ */
+
+union sockunion;
+struct interface;
+
+extern int netlink_nflog_group;
+
+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);
diff --git a/nhrpd/nhrp_cache.c b/nhrpd/nhrp_cache.c
new file mode 100644
index 0000000..447a814
--- /dev/null
+++ b/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);
+}
diff --git a/nhrpd/nhrp_event.c b/nhrpd/nhrp_event.c
new file mode 100644
index 0000000..aab9ec6
--- /dev/null
+++ b/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);
+}
+
diff --git a/nhrpd/nhrp_interface.c b/nhrpd/nhrp_interface.c
new file mode 100644
index 0000000..8118927
--- /dev/null
+++ b/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);
+}
diff --git a/nhrpd/nhrp_main.c b/nhrpd/nhrp_main.c
new file mode 100644
index 0000000..29349a0
--- /dev/null
+++ b/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;
+}
diff --git a/nhrpd/nhrp_nhs.c b/nhrpd/nhrp_nhs.c
new file mode 100644
index 0000000..d463e06
--- /dev/null
+++ b/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;
+
+ if (sockunion_family(proto_addr) != AF_UNSPEC &&
+ sockunion_family(proto_addr) != afi2family(afi))
+ return NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH;
+
+ list_for_each_entry(nhs, &nifp->afi[afi].nhslist_head, nhslist_entry) {
+ 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));
+ if (!nhs) return NHRP_ERR_NO_MEMORY;
+
+ *nhs = (struct nhrp_nhs) {
+ .afi = afi,
+ .ifp = ifp,
+ .proto_addr = *proto_addr,
+ .nbma_fqdn = strdup(nbma_fqdn),
+ .reglist_head = LIST_INITIALIZER(nhs->reglist_head),
+ };
+ list_add_tail(&nhs->nhslist_entry, &nifp->afi[afi].nhslist_head);
+ THREAD_TIMER_MSEC_ON(master, nhs->t_resolve, nhrp_nhs_resolve, nhs, 1000);
+
+ 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, *nnhs;
+ 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;
+
+ list_for_each_entry_safe(nhs, nnhs, &nifp->afi[afi].nhslist_head, nhslist_entry) {
+ if (!sockunion_same(&nhs->proto_addr, proto_addr))
+ continue;
+ if (strcmp(nhs->nbma_fqdn, nbma_fqdn) != 0)
+ continue;
+
+ nhrp_nhs_free(nhs);
+ ret = NHRP_OK;
+ }
+
+ return ret;
+}
+
+int nhrp_nhs_free(struct nhrp_nhs *nhs)
+{
+ struct nhrp_registration *r, *rn;
+
+ list_for_each_entry_safe(r, rn, &nhs->reglist_head, reglist_entry)
+ nhrp_reg_delete(r);
+ THREAD_OFF(nhs->t_resolve);
+ list_del(&nhs->nhslist_entry);
+ free((void*) nhs->nbma_fqdn);
+ XFREE(MTYPE_NHRP_NHS, nhs);
+ return 0;
+}
+
+void nhrp_nhs_terminate(void)
+{
+ struct interface *ifp;
+ struct nhrp_interface *nifp;
+ struct nhrp_nhs *nhs, *tmp;
+ struct listnode *node;
+ afi_t afi;
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+ nifp = ifp->info;
+ for (afi = 0; afi < AFI_MAX; afi++) {
+ list_for_each_entry_safe(nhs, tmp, &nifp->afi[afi].nhslist_head, nhslist_entry)
+ nhrp_nhs_free(nhs);
+ }
+ }
+}
diff --git a/nhrpd/nhrp_packet.c b/nhrpd/nhrp_packet.c
new file mode 100644
index 0000000..5d2866a
--- /dev/null
+++ b/nhrpd/nhrp_packet.c
@@ -0,0 +1,312 @@
+/* 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.
+ */
+
+#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) {
+ 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->holding_time = htons(ad->holdtime);
+ break;
+ default:
+ if (type & NHRP_EXTENSION_FLAG_COMPULSORY)
+ goto err;
+ 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 int 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);
+
+ zb = zbuf_alloc(1500);
+ if (!zb) return 0;
+
+ 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);
+ 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 0;
+
+err:
+ zbuf_free(zb);
+ return 0;
+}
+
+int nhrp_packet_init(void)
+{
+ thread_add_read(master, nhrp_packet_recvraw, 0, os_socket());
+ return 0;
+}
diff --git a/nhrpd/nhrp_peer.c b/nhrpd/nhrp_peer.c
new file mode 100644
index 0000000..45bfd7d
--- /dev/null
+++ b/nhrpd/nhrp_peer.c
@@ -0,0 +1,860 @@
+/* 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.
+ */
+
+#include <netinet/if_ether.h>
+
+#include "zebra.h"
+#include "memory.h"
+#include "thread.h"
+#include "hash.h"
+
+#include "nhrpd.h"
+#include "nhrp_protocol.h"
+#include "os.h"
+
+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;
+
+ THREAD_OFF(p->t_fallback);
+ 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 int 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);
+ }
+
+ return 0;
+}
+
+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_TIMER_MSEC_ON(
+ master, p->t_fallback,
+ nhrp_peer_notify_up, p, 50);
+ } 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);
+ }
+ /* Fall-through to post config update */
+ case NOTIFY_INTERFACE_ADDRESS_CHANGED:
+ 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(void *peer_data)
+{
+ struct nhrp_peer *p = peer_data;
+ return sockunion_hash(&p->vc->remote.nbma);
+}
+
+static int 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));
+ if (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;
+}
+
+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);
+ 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 int 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;
+
+ p->t_fallback = NULL;
+
+ if (p->online)
+ return 0;
+
+ 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_TIMER_ON(master, p->t_fallback, nhrp_peer_request_timeout, p, 30);
+ } else {
+ p->requested = p->fallback_requested = 0;
+ }
+
+ return 0;
+}
+
+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 (sockunion_family(&vc->local.nbma) == AF_UNSPEC)
+ return 0;
+
+ p->prio = establish > 1;
+ p->requested = 1;
+ vici_request_vc(nifp->ipsec_profile, &vc->local.nbma, &vc->remote.nbma, p->prio);
+ THREAD_TIMER_ON(master, p->t_fallback, nhrp_peer_request_timeout, p,
+ (nifp->ipsec_fallback_profile && !p->prio) ? 15 : 30);
+
+ 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);
+ nhrp_peer_check_delete(p);
+}
+
+void nhrp_peer_send(struct nhrp_peer *p, struct zbuf *zb)
+{
+ char buf[2][256];
+
+ nhrp_packet_debug(zb, "Send");
+
+ if (!p->online)
+ return;
+
+ debugf(NHRP_DEBUG_KERNEL, "PACKET: Send %s -> %s",
+ sockunion2str(&p->vc->local.nbma, buf[0], sizeof buf[0]),
+ sockunion2str(&p->vc->remote.nbma, buf[1], sizeof buf[1]));
+
+ os_sendmsg(zb->head, zbuf_used(zb),
+ p->ifp->ifindex,
+ sockunion_get_addr(&p->vc->remote.nbma),
+ sockunion_get_addrlen(&p->vc->remote.nbma));
+ zbuf_reset(zb);
+}
+
+static void nhrp_handle_resolution_req(struct nhrp_packet_parser *p)
+{
+ struct zbuf *zb, payload;
+ struct nhrp_packet_header *hdr;
+ struct nhrp_cie_header *cie;
+ struct nhrp_extension_header *ext;
+ struct nhrp_interface *nifp;
+ struct nhrp_peer *peer;
+
+ if (!(p->if_ad->flags & NHRP_IFF_SHORTCUT)) {
+ debugf(NHRP_DEBUG_COMMON, "Shortcuts disabled");
+ /* FIXME: Send error indication? */
+ return;
+ }
+
+ if (p->if_ad->network_id &&
+ p->route_type == NHRP_ROUTE_OFF_NBMA &&
+ p->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(p->ifp, &p->src_proto, NULL, &peer) != NHRP_ROUTE_NBMA_NEXTHOP)
+ return;
+
+#if 0
+ /* FIXME: Update requestors binding if CIE specifies holding time */
+ nhrp_cache_update_binding(
+ NHRP_CACHE_CACHED, &p->src_proto,
+ nhrp_peer_get(p->ifp, &p->src_nbma),
+ htons(cie->holding_time));
+#endif
+
+ nifp = peer->ifp->info;
+
+ /* Create reply */
+ zb = zbuf_alloc(1500);
+ hdr = nhrp_packet_push(zb, NHRP_PACKET_RESOLUTION_REPLY, &p->src_nbma, &p->src_proto, &p->dst_proto);
+
+ /* Copied information from request */
+ hdr->flags = p->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 = p->hdr->u.request_id;
+
+ /* CIE payload */
+ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nbma, &p->if_ad->addr);
+ cie->holding_time = htons(p->if_ad->holdtime);
+ cie->mtu = htons(p->if_ad->mtu);
+ if (p->if_ad->network_id && p->route_type == NHRP_ROUTE_OFF_NBMA)
+ cie->prefix_length = p->route_prefix.prefixlen;
+ else
+ cie->prefix_length = 8 * sockunion_get_addrlen(&p->if_ad->addr);
+
+ /* Handle extensions */
+ while ((ext = nhrp_ext_pull(&p->extensions, &payload)) != NULL) {
+ switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) {
+ case NHRP_EXTENSION_NAT_ADDRESS:
+ if (sockunion_family(&nifp->nat_nbma) == AF_UNSPEC)
+ break;
+ ext = nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS);
+ if (!ext) goto err;
+ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nat_nbma, &p->if_ad->addr);
+ if (!cie) goto err;
+ nhrp_ext_complete(zb, ext);
+ break;
+ default:
+ if (nhrp_ext_reply(zb, hdr, p->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, natted = 0;
+ size_t paylen;
+ void *pay;
+
+ debugf(NHRP_DEBUG_COMMON, "Parsing and replying to Registration Req");
+
+ 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) {
+ if (cie->prefix_length != 0xff && !(p->hdr->flags & htons(NHRP_FLAG_REGISTRATION_UNIQUE))) {
+ cie->code = NHRP_CODE_BINDING_NON_UNIQUE;
+ continue;
+ }
+
+ /* We currently support only unique prefix registrations */
+ if (cie->prefix_length != 0xff) {
+ 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 = nbma_addr;
+ 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)) {
+ 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) {
+ nhrp_cie_push(zb, NHRP_CODE_SUCCESS,
+ &p->peer->vc->remote.nbma,
+ &p->src_proto);
+ }
+ 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;
+ char buf[2][SU_ADDRSTRLEN];
+
+ 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 %s about packet to %s ignored",
+ sockunion2str(&p->vc->remote.nbma, buf[0], sizeof buf[0]),
+ sockunion2str(&dst, buf[1], sizeof buf[1]));
+ return;
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "Send Traffic Indication to %s (online=%d) about packet to %s",
+ sockunion2str(&p->vc->remote.nbma, buf[0], sizeof buf[0]),
+ p->online,
+ sockunion2str(&dst, buf[1], sizeof buf[1]));
+
+ /* Create reply */
+ zb = zbuf_alloc(1500);
+ hdr = nhrp_packet_push(zb, NHRP_PACKET_TRAFFIC_INDICATION, &nifp->nbma, &if_ad->addr, &dst);
+ hdr->hop_count = 0;
+
+ /* 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;
+ char buf[2][SU_ADDRSTRLEN];
+
+ hdr = nhrp_packet_pull(&origmsg, &src_nbma, &src_proto, &dst_proto);
+ if (!hdr) return;
+
+ debugf(NHRP_DEBUG_COMMON, "Error Indication from %s about packet to %s ignored",
+ sockunion2str(&pp->src_proto, buf[0], sizeof buf[0]),
+ sockunion2str(&dst_proto, buf[1], sizeof buf[1]));
+
+ 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;
+ char buf[2][SU_ADDRSTRLEN];
+
+ if (!parse_ether_packet(&p->payload, htons(p->hdr->protocol_type), NULL, &dst))
+ return;
+
+ debugf(NHRP_DEBUG_COMMON, "Traffic Indication from %s about packet to %s: %s",
+ sockunion2str(&p->src_proto, buf[0], sizeof buf[0]),
+ sockunion2str(&dst, buf[1], sizeof buf[1]),
+ (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[] = {
+ [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, 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;
+ uint16_t type, len;
+
+ if (pp->hdr->hop_count == 0)
+ return;
+
+ /* Create forward packet - copy header */
+ zb = 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(zb, &pp->payload, zbuf_used(&pp->payload));
+
+ /* 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 ((cie = 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->holding_time = htons(if_ad->holdtime);
+ }
+ 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;
+ 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);
+ return;
+err:
+ nhrp_packet_debug(pp->pkt, "FWD-FAIL");
+ zbuf_free(zb);
+}
+
+static void nhrp_packet_debug(struct zbuf *zb, const char *dir)
+{
+ char buf[2][SU_ADDRSTRLEN];
+ 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);
+
+ sockunion2str(&src_proto, buf[0], sizeof buf[0]);
+ sockunion2str(&dst_proto, buf[1], sizeof buf[1]);
+
+ reply = packet_types[hdr->type].type == PACKET_REPLY;
+ debugf(NHRP_DEBUG_COMMON, "%s %s(%d) %s -> %s",
+ dir,
+ packet_types[hdr->type].name ? : "Unknown",
+ hdr->type,
+ reply ? buf[1] : buf[0],
+ reply ? buf[0] : buf[1]);
+}
+
+struct nhrp_route_info {
+ int local;
+ struct interface *ifp;
+ struct nhrp_vc *vc;
+};
+
+void nhrp_peer_recv(struct nhrp_peer *p, struct zbuf *zb)
+{
+ char buf[2][SU_ADDRSTRLEN];
+ 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 afi;
+
+ debugf(NHRP_DEBUG_KERNEL, "PACKET: Recv %s -> %s",
+ sockunion2str(&vc->remote.nbma, buf[0], sizeof buf[0]),
+ sockunion2str(&vc->local.nbma, buf[1], sizeof buf[1]));
+
+ 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;
+
+ afi = htons(hdr->afnum);
+ if (hdr->type > ZEBRA_NUM_OF(packet_types) ||
+ hdr->version != NHRP_VERSION_RFC2332 ||
+ afi >= AFI_MAX ||
+ packet_types[hdr->type].type == PACKET_UNKNOWN ||
+ htons(hdr->packet_size) > realsize) {
+ zlog_info("From %s: error: packet type %d, version %d, AFI %d, size %d (real size %d)",
+ sockunion2str(&vc->remote.nbma, buf[0], sizeof buf[0]),
+ (int) hdr->type, (int) hdr->version, (int) afi,
+ (int) htons(hdr->packet_size),
+ (int) realsize);
+ goto drop;
+ }
+ pp.if_ad = &((struct nhrp_interface *)ifp->info)->afi[afi];
+
+ extoff = htons(hdr->extension_offset);
+ if (extoff) {
+ if (extoff >= realsize) {
+ info = "extoff larger than packet";
+ goto drop;
+ }
+ paylen = extoff - (zb->head - zb->buf);
+ } 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[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 */
+ }
+ }
+ 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 %s: error: %s",
+ sockunion2str(&vc->remote.nbma, buf[0], sizeof buf[0]),
+ 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..a4bc9fa
--- /dev/null
+++ b/nhrpd/nhrp_protocol.h
@@ -0,0 +1,128 @@
+/* nhrp_protocol.h - NHRP protocol definitions
+ *
+ * Copyright (c) 2007-2012 Timo Teräs <***@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
+
+/* 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..cc6b5fa
--- /dev/null
+++ b/nhrpd/nhrp_route.c
@@ -0,0 +1,345 @@
+/* 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.
+ */
+
+#include "nhrpd.h"
+#include "table.h"
+#include "memory.h"
+#include "stream.h"
+#include "log.h"
+#include "zclient.h"
+
+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 void nhrp_zebra_connected(struct zclient *zclient)
+{
+ /* No real VRF support yet -- bind only to the default vrf */
+ zclient_send_requests (zclient, VRF_DEFAULT);
+}
+
+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_family(&ri->via) == AF_UNSPEC) {
+ XFREE(MTYPE_NHRP_ROUTE, rn->info);
+ rn->info = NULL;
+ 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_family(nexthop) != AF_UNSPEC) || ifp);
+ if (rn) {
+ ri = rn->info;
+ ri->via = *nexthop;
+ ri->ifp = ifp;
+ nhrp_route_update_put(rn);
+ }
+}
+
+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 in_addr *nexthop_ipv4;
+ int flags = 0;
+
+ if (zclient->sock < 0)
+ return;
+
+ switch (type) {
+ case NHRP_CACHE_NEGATIVE:
+ SET_FLAG(flags, ZEBRA_FLAG_REJECT);
+ 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(flags, ZEBRA_FLAG_FIB_OVERRIDE);
+ break;
+ }
+ SET_FLAG(flags, ZEBRA_FLAG_INTERNAL);
+
+ if (p->family == AF_INET) {
+ struct zapi_ipv4 api;
+
+ memset(&api, 0, sizeof(api));
+ api.flags = flags;
+ api.type = ZEBRA_ROUTE_NHRP;
+ api.safi = SAFI_UNICAST;
+
+ SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP);
+ if (nexthop) {
+ SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP);
+ nexthop_ipv4 = (struct in_addr *) sockunion_get_addr(nexthop);
+ api.nexthop_num = 1;
+ api.nexthop = &nexthop_ipv4;
+ }
+ if (ifp) {
+ SET_FLAG(api.message, ZAPI_MESSAGE_IFINDEX);
+ api.ifindex_num = 1;
+ api.ifindex = &ifp->ifindex;
+ }
+ if (mtu) {
+ SET_FLAG(api.message, ZAPI_MESSAGE_MTU);
+ api.mtu = mtu;
+ }
+
+ if (unlikely(debug_flags & NHRP_DEBUG_ROUTE)) {
+ char buf[2][INET_ADDRSTRLEN];
+ zlog_debug("Zebra send: IPv4 route %s %s/%d nexthop %s metric %u"
+ " count %d dev %s",
+ add ? "add" : "del",
+ inet_ntop(AF_INET, &p->u.prefix4, buf[0], sizeof(buf[0])),
+ p->prefixlen,
+ nexthop ? inet_ntop(AF_INET, api.nexthop[0], buf[1], sizeof(buf[1])) : "<onlink>",
+ api.metric, api.nexthop_num, ifp->name);
+ }
+
+ zapi_ipv4_route(
+ add ? ZEBRA_IPV4_ROUTE_ADD : ZEBRA_IPV4_ROUTE_DELETE,
+ zclient, (struct prefix_ipv4 *) p, &api);
+ }
+}
+
+int nhrp_route_read(int cmd, struct zclient *zclient, zebra_size_t length, vrf_id_t vrf_id)
+{
+ struct stream *s;
+ struct interface *ifp = NULL;
+ struct prefix prefix;
+ union sockunion nexthop_addr;
+ unsigned char message, nexthop_num, ifindex_num;
+ unsigned ifindex;
+ char buf[2][PREFIX_STRLEN];
+ int i, afaddrlen, added;
+
+ s = zclient->ibuf;
+ memset(&prefix, 0, sizeof(prefix));
+ sockunion_family(&nexthop_addr) = AF_UNSPEC;
+
+ /* Type, flags, message. */
+ /*type =*/ stream_getc(s);
+ /*flags =*/ stream_getc(s);
+ message = stream_getc(s);
+
+ /* Prefix */
+ switch (cmd) {
+ case ZEBRA_IPV4_ROUTE_ADD:
+ case ZEBRA_IPV4_ROUTE_DELETE:
+ prefix.family = AF_INET;
+ break;
+ case ZEBRA_IPV6_ROUTE_ADD:
+ case ZEBRA_IPV6_ROUTE_DELETE:
+ prefix.family = AF_INET6;
+ break;
+ default:
+ return -1;
+ }
+ afaddrlen = family2addrsize(prefix.family);
+ prefix.prefixlen = stream_getc(s);
+ stream_get(&prefix.u.val, s, PSIZE(prefix.prefixlen));
+
+ /* Nexthop, ifindex, distance, metric. */
+ if (CHECK_FLAG(message, ZAPI_MESSAGE_NEXTHOP|ZAPI_MESSAGE_IFINDEX)) {
+ nexthop_num = stream_getc(s);
+ for (i = 0; i < nexthop_num; i++) {
+ stream_get(buf[0], s, afaddrlen);
+ if (i == 0) sockunion_set(&nexthop_addr, prefix.family, (u_char*) buf[0], afaddrlen);
+ }
+ ifindex_num = stream_getc(s);
+ for (i = 0; i < ifindex_num; i++) {
+ ifindex = stream_getl(s);
+ if (i == 0 && ifindex != IFINDEX_INTERNAL)
+ ifp = if_lookup_by_index(ifindex);
+ }
+ }
+ if (CHECK_FLAG(message, ZAPI_MESSAGE_DISTANCE))
+ /*distance =*/ stream_getc(s);
+ if (CHECK_FLAG(message, ZAPI_MESSAGE_METRIC))
+ /*metric =*/ stream_getl(s);
+
+ added = (cmd == ZEBRA_IPV4_ROUTE_ADD || cmd == ZEBRA_IPV6_ROUTE_ADD);
+ debugf(NHRP_DEBUG_ROUTE, "if-route-%s: %s via %s dev %s",
+ added ? "add" : "del",
+ prefix2str(&prefix, buf[0], sizeof buf[0]),
+ sockunion2str(&nexthop_addr, buf[1], sizeof buf[1]),
+ ifp ? ifp->name : "(none)");
+
+ nhrp_route_update_zebra(&prefix, &nexthop_addr, ifp);
+ nhrp_shortcut_prefix_change(&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));
+ char buf[PREFIX_STRLEN];
+
+ 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 %s: nhrp_if=%s",
+ prefix2str(&lookup, buf, sizeof buf),
+ ri->nhrp_ifp->name);
+
+ if (via) sockunion_family(via) = AF_UNSPEC;
+ if (ifp) *ifp = ri->nhrp_ifp;
+ } else {
+ debugf(NHRP_DEBUG_ROUTE, "lookup %s: zebra route dev %s",
+ prefix2str(&lookup, buf, sizeof buf),
+ 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;
+}
+
+void nhrp_zebra_init(void)
+{
+ zebra_rib[AFI_IP] = route_table_init();
+ zebra_rib[AFI_IP6] = route_table_init();
+
+ zclient = zclient_new(master);
+ zclient->zebra_connected = nhrp_zebra_connected;
+ zclient->interface_add = nhrp_interface_add;
+ zclient->interface_delete = nhrp_interface_delete;
+ zclient->interface_up = nhrp_interface_up;
+ zclient->interface_down = nhrp_interface_down;
+ zclient->interface_address_add = nhrp_interface_address_add;
+ zclient->interface_address_delete = nhrp_interface_address_delete;
+ zclient->ipv4_route_add = nhrp_route_read;
+ zclient->ipv4_route_delete = nhrp_route_read;
+ zclient->ipv6_route_add = nhrp_route_read;
+ zclient->ipv6_route_delete = nhrp_route_read;
+
+ zclient_init(zclient, ZEBRA_ROUTE_NHRP);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_KERNEL, VRF_DEFAULT);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_CONNECT, VRF_DEFAULT);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_STATIC, VRF_DEFAULT);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_RIP, VRF_DEFAULT);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_OSPF, VRF_DEFAULT);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_ISIS, VRF_DEFAULT);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_BGP, VRF_DEFAULT);
+}
+
+void nhrp_zebra_terminate(void)
+{
+ zclient_stop(zclient);
+ route_table_finish(zebra_rib[AFI_IP]);
+ route_table_finish(zebra_rib[AFI_IP6]);
+}
+
diff --git a/nhrpd/nhrp_shortcut.c b/nhrpd/nhrp_shortcut.c
new file mode 100644
index 0000000..421f288
--- /dev/null
+++ b/nhrpd/nhrp_shortcut.c
@@ -0,0 +1,402 @@
+/* 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.
+ */
+
+#include "nhrpd.h"
+#include "table.h"
+#include "memory.h"
+#include "thread.h"
+#include "log.h"
+#include "nhrp_protocol.h"
+
+static struct route_table *shortcut_rib[AFI_MAX];
+
+static int 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)
+{
+ char buf[PREFIX_STRLEN];
+
+ if (s->expiring && s->cache && s->cache->used) {
+ debugf(NHRP_DEBUG_ROUTE, "Shortcut %s used and expiring",
+ prefix2str(s->p, buf, sizeof buf));
+ nhrp_shortcut_send_resolution_req(s);
+ }
+}
+
+static int nhrp_shortcut_do_expire(struct thread *t)
+{
+ struct nhrp_shortcut *s = THREAD_ARG(t);
+
+ s->t_timer = NULL;
+ THREAD_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, s->holding_time/3);
+ s->expiring = 1;
+ nhrp_shortcut_check_use(s);
+
+ return 0;
+}
+
+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);
+
+ switch (cmd) {
+ case NOTIFY_CACHE_UP:
+ if (!s->route_installed) {
+ nhrp_route_announce(1, s->type, s->p, NULL, &s->cache->remote_addr, 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;
+ nhrp_shortcut_cache_notify(&s->cache_notifier, NOTIFY_CACHE_UP);
+ }
+ }
+ if (!s->cache || !s->cache->route_installed)
+ 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_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_expire, s, 2*holding_time/3);
+ }
+}
+
+static void nhrp_shortcut_delete(struct nhrp_shortcut *s)
+{
+ struct route_node *rn;
+ afi_t afi = family2afi(PREFIX_FAMILY(s->p));
+ char buf[PREFIX_STRLEN];
+
+ THREAD_OFF(s->t_timer);
+ nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid);
+
+ debugf(NHRP_DEBUG_ROUTE, "Shortcut %s purged",
+ prefix2str(s->p, buf, sizeof buf));
+
+ 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 int nhrp_shortcut_do_purge(struct thread *t)
+{
+ struct nhrp_shortcut *s = THREAD_ARG(t);
+ s->t_timer = NULL;
+ nhrp_shortcut_delete(s);
+ return 0;
+}
+
+static struct nhrp_shortcut *nhrp_shortcut_get(struct prefix *p)
+{
+ struct nhrp_shortcut *s;
+ struct route_node *rn;
+ char buf[PREFIX_STRLEN];
+ 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 %s created",
+ prefix2str(s->p, buf, sizeof buf));
+ } 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 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;
+ union sockunion *proto, cie_proto, *nbma, *nbma_natoa, cie_nbma, nat_nbma;
+ struct prefix prefix, route_prefix;
+ struct zbuf extpl;
+ char bufp[PREFIX_STRLEN], buf[3][SU_ADDRSTRLEN];
+ int holding_time = pp->if_ad->holdtime;
+
+ nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid);
+ THREAD_OFF(s->t_timer);
+ THREAD_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 1);
+
+ 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;
+ }
+
+ /* 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:
+ nhrp_cie_pull(&extpl, pp->hdr, &nat_nbma, &cie_proto);
+ break;
+ }
+ }
+
+ /* 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 %s to %s",
+ sockunion2str(&cie_proto, buf[0], sizeof buf[0]),
+ sockunion2str(&pp->dst_proto, buf[1], sizeof buf[1]));
+ }
+
+ /* 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 = 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;
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "Shortcut: %s is at proto %s cie-nbma %s nat-nbma %s cie-holdtime %d",
+ prefix2str(&prefix, bufp, sizeof bufp),
+ sockunion2str(proto, buf[0], sizeof buf[0]),
+ sockunion2str(&cie_nbma, buf[1], sizeof buf[1]),
+ sockunion2str(&nat_nbma, buf[2], sizeof buf[2]),
+ htons(cie->holding_time));
+
+ /* Update cache entry for the protocol to nbma binding */
+ if (sockunion_family(&nat_nbma) != AF_UNSPEC) {
+ nbma = &nat_nbma;
+ nbma_natoa = &cie_nbma;
+ } else {
+ nbma = &cie_nbma;
+ nbma_natoa = NULL;
+ }
+ if (sockunion_family(nbma)) {
+ c = nhrp_cache_get(pp->ifp, proto, 1);
+ if (c) {
+ nhrp_cache_update_binding(
+ c, NHRP_CACHE_CACHED, holding_time,
+ nhrp_peer_get(pp->ifp, nbma),
+ htons(cie->mtu), nbma_natoa);
+ }
+ }
+
+ /* Update shortcut entry for subnet to protocol gw binding */
+ if (c && !sockunion_same(proto, &pp->dst_proto)) {
+ ps = nhrp_shortcut_get(&prefix);
+ if (ps) {
+ ps->addr = s->addr;
+ nhrp_shortcut_update_binding(ps, NHRP_CACHE_CACHED, c, holding_time);
+ }
+ }
+
+ 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_peer *peer;
+
+ 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: Send holding time, and MTU */
+
+ nhrp_ext_request(zb, hdr, ifp);
+
+ /* Cisco NAT detection extension */
+ hdr->flags |= htons(NHRP_FLAG_RESOLUTION_NAT);
+ nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS);
+
+ 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;
+
+ sockunion2hostprefix(addr, &p);
+ s = nhrp_shortcut_get(&p);
+ if (s && s->type != NHRP_CACHE_INCOMPLETE) {
+ s->addr = *addr;
+ THREAD_OFF(s->t_timer);
+ THREAD_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 30);
+ 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_TIMER_MSEC_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 5);
+ } 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_TIMER_MSEC_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 3000);
+ 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..f9e1ee0
--- /dev/null
+++ b/nhrpd/nhrp_vc.c
@@ -0,0 +1,217 @@
+/* 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"
+
+struct child_sa {
+ uint32_t id;
+ struct nhrp_vc *vc;
+ struct list_head childlist_entry;
+};
+
+static struct hash *nhrp_vc_hash;
+static struct list_head childlist_head[512];
+
+static unsigned int nhrp_vc_key(void *peer_data)
+{
+ struct nhrp_vc *vc = peer_data;
+ return jhash_2words(
+ sockunion_hash(&vc->local.nbma),
+ sockunion_hash(&vc->remote.nbma),
+ 0);
+}
+
+static int 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));
+ if (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)
+{
+ char buf[2][SU_ADDRSTRLEN];
+ struct child_sa *sa = NULL, *lsa;
+ uint32_t child_hash = child_id % ZEBRA_NUM_OF(childlist_head);
+ int abort_migration = 0;
+
+ list_for_each_entry(lsa, &childlist_head[child_hash], childlist_entry) {
+ if (lsa->id == child_id) {
+ sa = lsa;
+ break;
+ }
+ }
+
+ if (!sa) {
+ if (!vc) return 0;
+
+ sa = XMALLOC(MTYPE_NHRP_VC, sizeof(struct child_sa));
+ if (!sa) return 0;
+
+ *sa = (struct child_sa) {
+ .id = child_id,
+ .childlist_entry = LIST_INITIALIZER(sa->childlist_entry),
+ .vc = NULL,
+ };
+ list_add_tail(&sa->childlist_entry, &childlist_head[child_hash]);
+ }
+
+ 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 %s to %s",
+ sockunion2str(&sa->vc->remote.nbma, buf[0], sizeof buf[0]),
+ sockunion2str(&vc->remote.nbma, buf[1], sizeof buf[1]));
+ 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) {
+ list_del(&sa->childlist_entry);
+ 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);
+ 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_backet *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);
+ for (i = 0; i < ZEBRA_NUM_OF(childlist_head); i++)
+ list_init(&childlist_head[i]);
+}
+
+void nhrp_vc_reset(void)
+{
+ struct child_sa *sa, *n;
+ size_t i;
+
+ for (i = 0; i < ZEBRA_NUM_OF(childlist_head); i++) {
+ list_for_each_entry_safe(sa, n, &childlist_head[i], childlist_entry)
+ 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..9b1c69d
--- /dev/null
+++ b/nhrpd/nhrp_vty.c
@@ -0,0 +1,928 @@
+/* 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 "nhrpd.h"
+#include "netlink.h"
+
+static struct cmd_node zebra_node = {
+ .node = ZEBRA_NODE,
+ .prompt = "%s(config-router)# ",
+ .vtysh = 1,
+};
+
+static struct cmd_node nhrp_interface_node = {
+ .node = INTERFACE_NODE,
+ .prompt = "%s(config-if)# ",
+ .vtysh = 1,
+};
+
+#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, NULL },
+};
+
+static const struct message interface_flags_desc[] = {
+ { NHRP_IFF_SHORTCUT, "shortcut" },
+ { NHRP_IFF_REDIRECT, "redirect" },
+ { NHRP_IFF_REG_NO_UNIQUE, "registration no-unique" },
+ { 0, NULL },
+};
+
+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 <= (int)ZEBRA_NUM_OF(errmsgs))
+ if (errmsgs[ret])
+ str = errmsgs[ret];
+
+ if (!str) {
+ str = buf;
+ snprintf(buf, sizeof(buf), "Unknown error %d", ret);
+ }
+
+ vty_out (vty, "%% %s%s", str, VTY_NEWLINE);
+
+ return CMD_WARNING;
+}
+
+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%s", name, VTY_NEWLINE);
+ return CMD_WARNING;
+}
+
+#ifndef NO_DEBUG
+
+DEFUN(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:%s", VTY_NEWLINE);
+
+ 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%s",
+ debug_flags_desc[i].str, VTY_NEWLINE);
+ }
+
+ 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[0], 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[0], 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%s", VTY_NEWLINE);
+ } 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%s", debug_flags_desc[i].str, VTY_NEWLINE);
+ }
+ }
+ vty_out(vty, "!%s", VTY_NEWLINE);
+#endif /* NO_DEBUG */
+
+ if (nhrp_event_socket_path) {
+ vty_out(vty, "nhrp event socket %s%s",
+ nhrp_event_socket_path, VTY_NEWLINE);
+ }
+ if (netlink_nflog_group) {
+ vty_out(vty, "nhrp nflog-group %d%s",
+ netlink_nflog_group, VTY_NEWLINE);
+ }
+
+ 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 char *cmd)
+{
+ return strncmp(cmd, "ipv6", 4) == 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")
+{
+ evmgr_set_socket(argv[0]);
+ 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")
+{
+ 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;
+
+ VTY_GET_INTEGER_RANGE("nflog-group", nfgroup, argv[0], 1, 65535);
+ 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(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")
+{
+ struct interface *ifp = vty->index;
+
+ nhrp_interface_set_protection(ifp, argv[0], argv[1]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_tunnel_protection, no_tunnel_protection_cmd,
+ "no tunnel protection",
+ NO_STR
+ "NHRP/GRE integration\n"
+ "IPsec protection\n")
+{
+ struct interface *ifp = vty->index;
+
+ 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")
+{
+ struct interface *ifp = vty->index;
+ nhrp_interface_set_source(ifp, argv[0]);
+ 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")
+{
+ struct interface *ifp = vty->index;
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ VTY_GET_INTEGER_RANGE("network-id", nifp->afi[afi].network_id, argv[1], 1, 4294967295);
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ return toggle_flag(vty, interface_flags_desc, argv[1], 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ return toggle_flag(vty, interface_flags_desc, argv[1], 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+ char name[256];
+ snprintf(name, sizeof(name), "registration %s", argv[1]);
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+ char name[256];
+ snprintf(name, sizeof(name), "registration %s", argv[1]);
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ VTY_GET_INTEGER_RANGE("holdtime", nifp->afi[afi].holdtime, argv[1], 1, 65000);
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+
+ if (argv[0][0] == 'o') {
+ nifp->afi[AFI_IP].configured_mtu = -1;
+ } else {
+ VTY_GET_INTEGER_RANGE("mtu", nifp->afi[AFI_IP].configured_mtu, argv[0], 576, 1500);
+ }
+ 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")
+{
+ struct interface *ifp = vty->index;
+ 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")
+{
+ struct interface *ifp = vty->index;
+ afi_t afi = cmd_to_afi(argv[0]);
+ union sockunion proto_addr, nbma_addr;
+ struct nhrp_cache *c;
+
+ if (str2sockunion(argv[1], &proto_addr) < 0 ||
+ afi2family(afi) != sockunion_family(&proto_addr))
+ return nhrp_vty_return(vty, NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH);
+
+ c = nhrp_cache_get(ifp, &proto_addr, 1);
+ if (!c)
+ return nhrp_vty_return(vty, NHRP_ERR_FAIL);
+
+ c->map = 1;
+ if (strcmp(argv[2], "local") == 0) {
+ nhrp_cache_update_binding(c, NHRP_CACHE_LOCAL, 0, NULL, 0, NULL);
+ } else{
+ if (str2sockunion(argv[2], &nbma_addr) < 0)
+ return nhrp_vty_return(vty, NHRP_ERR_FAIL);
+ nhrp_cache_update_binding(c, NHRP_CACHE_STATIC, 0,
+ nhrp_peer_get(ifp, &nbma_addr), 0, NULL);
+ }
+
+ return CMD_SUCCESS;
+}
+
+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"
+ "IPv4 NBMA address\n"
+ "Fully qualified domain name for NBMA address(es)\n")
+{
+ struct interface *ifp = vty->index;
+ afi_t afi = cmd_to_afi(argv[0]);
+ union sockunion proto_addr;
+ int ret;
+
+ if (str2sockunion(argv[1], &proto_addr) < 0)
+ sockunion_family(&proto_addr) = AF_UNSPEC;
+
+ ret = nhrp_nhs_add(ifp, afi, &proto_addr, argv[2]);
+ 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"
+ "IPv4 NBMA address\n"
+ "Fully qualified domain name for NBMA address(es)\n")
+{
+ struct interface *ifp = vty->index;
+ afi_t afi = cmd_to_afi(argv[0]);
+ union sockunion proto_addr;
+ int ret;
+
+ if (str2sockunion(argv[1], &proto_addr) < 0)
+ sockunion_family(&proto_addr) = AF_UNSPEC;
+
+ ret = nhrp_nhs_del(ifp, afi, &proto_addr, argv[2]);
+ return nhrp_vty_return(vty, ret);
+}
+
+struct info_ctx {
+ struct vty *vty;
+ afi_t afi;
+ int count;
+};
+
+static void show_ip_nhrp_cache(struct nhrp_cache *c, void *pctx)
+{
+ struct info_ctx *ctx = pctx;
+ struct vty *vty = ctx->vty;
+ char buf[2][SU_ADDRSTRLEN];
+
+ if (ctx->afi != family2afi(sockunion_family(&c->remote_addr)))
+ return;
+
+ if (!ctx->count) {
+ vty_out(vty, "%-8s %-8s %-24s %-24s %-6s %s%s",
+ "Iface",
+ "Type",
+ "Protocol",
+ "NBMA",
+ "Flags",
+ "Identity",
+ VTY_NEWLINE);
+ }
+ ctx->count++;
+
+ vty_out(ctx->vty, "%-8s %-8s %-24s %-24s %c%c%c %s%s",
+ c->ifp->name,
+ nhrp_cache_type_str[c->cur.type],
+ sockunion2str(&c->remote_addr, buf[0], sizeof buf[0]),
+ c->cur.peer ? sockunion2str(&c->cur.peer->vc->remote.nbma, buf[1], sizeof buf[1]) : "-",
+ c->used ? 'U' : ' ',
+ c->t_timeout ? 'T' : ' ',
+ c->t_auth ? 'A' : ' ',
+ c->cur.peer ? c->cur.peer->vc->remote.id : "-",
+ VTY_NEWLINE);
+}
+
+static void show_ip_opennhrp_cache(struct nhrp_cache *c, void *pctx)
+{
+ struct info_ctx *ctx = pctx;
+ struct vty *vty = ctx->vty;
+ char buf[SU_ADDRSTRLEN];
+
+ if (ctx->afi != family2afi(sockunion_family(&c->remote_addr)))
+ return;
+
+ vty_out(ctx->vty,
+ "Type: %s%s"
+ "Flags:%s%s%s"
+ "Protocol-Address: %s/%zu%s",
+ nhrp_cache_type_str[c->cur.type],
+ VTY_NEWLINE,
+ (c->cur.peer && c->cur.peer->online) ? " up": "",
+ c->used ? " used": "",
+ VTY_NEWLINE,
+ sockunion2str(&c->remote_addr, buf, sizeof buf),
+ 8 * family2addrsize(sockunion_family(&c->remote_addr)),
+ VTY_NEWLINE);
+
+ if (c->cur.peer) {
+ vty_out(ctx->vty,
+ "NBMA-Address: %s%s",
+ sockunion2str(&c->cur.peer->vc->remote.nbma, buf, sizeof buf),
+ VTY_NEWLINE);
+ }
+
+ if (sockunion_family(&c->cur.remote_nbma_natoa) != AF_UNSPEC) {
+ vty_out(ctx->vty,
+ "NBMA-NAT-OA-Address: %s%s",
+ sockunion2str(&c->cur.remote_nbma_natoa, buf, sizeof buf),
+ VTY_NEWLINE);
+ }
+
+ vty_out(ctx->vty, "%s", VTY_NEWLINE);
+}
+
+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];
+
+ if (!ctx->count) {
+ vty_out(vty, "%-8s %-24s %-24s %s%s",
+ "Type",
+ "Prefix",
+ "Via",
+ "Identity",
+ VTY_NEWLINE);
+ }
+ ctx->count++;
+
+ c = s->cache;
+ vty_out(ctx->vty, "%-8s %-24s %-24s %s%s",
+ nhrp_cache_type_str[s->type],
+ prefix2str(s->p, buf1, sizeof buf1),
+ c ? sockunion2str(&c->remote_addr, buf2, sizeof buf2) : "",
+ (c && c->cur.peer) ? c->cur.peer->vc->remote.id : "",
+ VTY_NEWLINE);
+}
+
+DEFUN(show_ip_nhrp, show_ip_nhrp_cmd,
+ "show " AFI_CMD " nhrp (cache|shortcut|opennhrp|)",
+ SHOW_STR
+ AFI_STR
+ "NHRP information\n"
+ "Forwarding cache information\n"
+ "Shortcut information\n"
+ "opennhrpctl style cache dump\n")
+{
+ struct listnode *node;
+ struct interface *ifp;
+ struct info_ctx ctx = {
+ .vty = vty,
+ .afi = cmd_to_afi(argv[0]),
+ };
+
+ if (!argv[1] || argv[1][0] == 'c') {
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp))
+ nhrp_cache_foreach(ifp, show_ip_nhrp_cache, &ctx);
+ } else if (argv[1][0] == 'o') {
+ vty_out(vty, "Status: ok%s%s", VTY_NEWLINE, VTY_NEWLINE);
+ ctx.count++;
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp))
+ nhrp_cache_foreach(ifp, show_ip_opennhrp_cache, &ctx);
+ } else {
+ nhrp_shortcut_foreach(ctx.afi, show_ip_nhrp_shortcut, &ctx);
+ }
+
+ if (!ctx.count) {
+ vty_out(vty, "%% No entries%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+static void show_dmvpn_entry(struct nhrp_vc *vc, void *ctx)
+{
+ struct vty *vty = ctx;
+ char buf[2][SU_ADDRSTRLEN];
+
+ vty_out(vty, "%-24s %-24s %c %-4d %-24s%s",
+ sockunion2str(&vc->local.nbma, buf[0], sizeof buf[0]),
+ sockunion2str(&vc->remote.nbma, buf[1], sizeof buf[1]),
+ notifier_active(&vc->notifier_list) ? 'n' : ' ',
+ vc->ipsec,
+ vc->remote.id,
+ VTY_NEWLINE);
+}
+
+DEFUN(show_dmvpn, show_dmvpn_cmd,
+ "show dmvpn",
+ SHOW_STR
+ "DMVPN information\n")
+{
+ vty_out(vty, "%-24s %-24s %-6s %-4s %-24s%s",
+ "Src",
+ "Dst",
+ "Flags",
+ "SAs",
+ "Identity",
+ VTY_NEWLINE);
+
+ nhrp_vc_foreach(show_dmvpn_entry, vty);
+
+ 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_CACHED) {
+ nhrp_cache_update_binding(c, c->cur.type, -1, NULL, 0, NULL);
+ 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 listnode *node;
+ struct interface *ifp;
+ struct info_ctx ctx = {
+ .vty = vty,
+ .afi = cmd_to_afi(argv[0]),
+ .count = 0,
+ };
+
+ if (!argv[1] || argv[1][0] == 'c') {
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp))
+ nhrp_cache_foreach(ifp, clear_nhrp_cache, &ctx);
+ } else {
+ nhrp_shortcut_foreach(ctx.afi, clear_nhrp_shortcut, &ctx);
+ }
+
+ if (!ctx.count) {
+ vty_out(vty, "%% No entries%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vty_out(vty, "%% %d entries cleared%s", ctx.count, VTY_NEWLINE);
+ 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 *c, void *data)
+{
+ struct write_map_ctx *ctx = data;
+ struct vty *vty = ctx->vty;
+ char buf[2][SU_ADDRSTRLEN];
+
+ if (!c->map) return;
+ if (sockunion_family(&c->remote_addr) != ctx->family) return;
+
+ vty_out(vty, " %s nhrp map %s %s%s",
+ ctx->aficmd,
+ sockunion2str(&c->remote_addr, buf[0], sizeof buf[0]),
+ c->cur.type == NHRP_CACHE_LOCAL ? "local" :
+ sockunion2str(&c->cur.peer->vc->remote.nbma, buf[1], sizeof buf[1]),
+ VTY_NEWLINE);
+}
+
+static int interface_config_write(struct vty *vty)
+{
+ struct write_map_ctx mapctx;
+ struct listnode *node;
+ struct interface *ifp;
+ struct nhrp_interface *nifp;
+ struct nhrp_nhs *nhs;
+ const char *aficmd;
+ afi_t afi;
+ char buf[SU_ADDRSTRLEN];
+ int i;
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+ vty_out(vty, "interface %s%s", ifp->name, VTY_NEWLINE);
+ if (ifp->desc)
+ vty_out(vty, " description %s%s", ifp->desc, VTY_NEWLINE);
+
+ 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, "%s", VTY_NEWLINE);
+ }
+ if (nifp->source)
+ vty_out(vty, " tunnel source %s%s",
+ nifp->source, VTY_NEWLINE);
+
+ 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%s",
+ aficmd, ad->network_id,
+ VTY_NEWLINE);
+
+ if (ad->holdtime != NHRPD_DEFAULT_HOLDTIME)
+ vty_out(vty, " %s nhrp holdtime %u%s",
+ aficmd, ad->holdtime,
+ VTY_NEWLINE);
+
+ if (ad->configured_mtu < 0)
+ vty_out(vty, " %s nhrp mtu opennhrp%s",
+ aficmd, VTY_NEWLINE);
+ else if (ad->configured_mtu)
+ vty_out(vty, " %s nhrp mtu %u%s",
+ aficmd, ad->configured_mtu,
+ VTY_NEWLINE);
+
+ 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%s",
+ aficmd, interface_flags_desc[i].str, VTY_NEWLINE);
+ }
+
+ mapctx = (struct write_map_ctx) {
+ .vty = vty,
+ .family = afi2family(afi),
+ .aficmd = aficmd,
+ };
+ nhrp_cache_foreach(ifp, interface_config_write_nhrp_map, &mapctx);
+
+ list_for_each_entry(nhs, &ad->nhslist_head, nhslist_entry) {
+ vty_out(vty, " %s nhrp nhs %s nbma %s%s",
+ aficmd,
+ sockunion_family(&nhs->proto_addr) == AF_UNSPEC ? "dynamic" : sockunion2str(&nhs->proto_addr, buf, sizeof buf),
+ nhs->nbma_fqdn,
+ VTY_NEWLINE);
+ }
+ }
+
+ vty_out (vty, "!%s", VTY_NEWLINE);
+ }
+
+ return 0;
+}
+
+void nhrp_config_init(void)
+{
+ install_node(&zebra_node, nhrp_config_write);
+ install_default(ZEBRA_NODE);
+
+ /* global commands */
+ install_element(VIEW_NODE, &show_debugging_nhrp_cmd);
+ install_element(VIEW_NODE, &show_ip_nhrp_cmd);
+ install_element(VIEW_NODE, &show_dmvpn_cmd);
+ install_element(ENABLE_NODE, &show_debugging_nhrp_cmd);
+ install_element(ENABLE_NODE, &show_ip_nhrp_cmd);
+ install_element(ENABLE_NODE, &show_dmvpn_cmd);
+ install_element(ENABLE_NODE, &clear_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);
+
+ /* interface specific commands */
+ install_node(&nhrp_interface_node, interface_config_write);
+ install_default(INTERFACE_NODE);
+
+ install_element(CONFIG_NODE, &interface_cmd);
+ install_element(CONFIG_NODE, &no_interface_cmd);
+ install_element(INTERFACE_NODE, &interface_cmd);
+ install_element(INTERFACE_NODE, &no_interface_cmd);
+ 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_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..c8f8816
--- /dev/null
+++ b/nhrpd/nhrpd.h
@@ -0,0 +1,440 @@
+/* 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 "list.h"
+
+#include "zebra.h"
+#include "zbuf.h"
+#include "zclient.h"
+#include "log.h"
+
+#define NHRPD_DEFAULT_HOLDTIME 7200
+
+#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)
+
+#define NHRP_VTY_PORT 2612
+#define NHRP_DEFAULT_CONFIG "nhrpd.conf"
+
+#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
+
+extern unsigned int debug_flags;
+extern struct thread_master *master;
+
+#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
+
+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,
+};
+
+struct notifier_block;
+
+typedef void (*notifier_fn_t)(struct notifier_block *, unsigned long);
+
+struct notifier_block {
+ struct list_head notifier_entry;
+ notifier_fn_t action;
+};
+
+struct notifier_list {
+ struct list_head notifier_head;
+};
+
+#define NOTIFIER_LIST_INITIALIZER(l) \
+ { .notifier_head = LIST_INITIALIZER((l)->notifier_head) }
+
+static inline void notifier_init(struct notifier_list *l)
+{
+ list_init(&l->notifier_head);
+}
+
+static inline void notifier_add(struct notifier_block *n, struct notifier_list *l, notifier_fn_t action)
+{
+ n->action = action;
+ list_add_tail(&n->notifier_entry, &l->notifier_head);
+}
+
+static inline void notifier_del(struct notifier_block *n)
+{
+ list_del(&n->notifier_entry);
+}
+
+static inline void notifier_call(struct notifier_list *l, int cmd)
+{
+ struct notifier_block *n, *nn;
+ list_for_each_entry_safe(n, nn, &l->notifier_head, notifier_entry)
+ n->action(n, cmd);
+}
+
+static inline int notifier_active(struct notifier_list *l)
+{
+ return !list_empty(&l->notifier_head);
+}
+
+struct resolver_query {
+ void (*callback)(struct resolver_query *, int n, union sockunion *);
+};
+
+void resolver_init(void);
+void resolver_resolve(struct resolver_query *query, int af, const char *hostname, void (*cb)(struct resolver_query *, int, union sockunion *));
+
+void nhrp_zebra_init(void);
+void nhrp_zebra_terminate(void);
+
+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_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;
+ uint8_t ipsec;
+ 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 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 {
+ 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;
+ struct nhrp_peer *peer;
+ time_t expires;
+ uint32_t mtu;
+ } 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;
+};
+
+struct nhrp_nhs {
+ struct interface *ifp;
+ struct list_head 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 list_head reglist_head;
+};
+
+#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 linkidx;
+ uint32_t grekey;
+
+ struct hash *peer_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 list_head nhslist_head;
+ } afi[AFI_MAX];
+};
+
+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);
+
+int nhrp_interface_add(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id);
+int nhrp_interface_delete(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id);
+int nhrp_interface_up(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id);
+int nhrp_interface_down(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id);
+int nhrp_interface_address_add(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id);
+int nhrp_interface_address_delete(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id);
+
+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);
+
+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_nhs *nhs);
+void nhrp_nhs_terminate(void);
+
+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(int command, struct zclient *zclient, zebra_size_t length, vrf_id_t vrf_id);
+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);
+
+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_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);
+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_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);
+
+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 *);
+
+#endif
diff --git a/nhrpd/os.h b/nhrpd/os.h
new file mode 100644
index 0000000..0fbe8b0
--- /dev/null
+++ b/nhrpd/os.h
@@ -0,0 +1,5 @@
+
+int os_socket(void);
+int os_sendmsg(const uint8_t *buf, size_t len, int ifindex, const uint8_t *addr, size_t addrlen);
+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..24b3199
--- /dev/null
+++ b/nhrpd/reqid.c
@@ -0,0 +1,49 @@
+#include "zebra.h"
+#include "hash.h"
+#include "nhrpd.h"
+
+static unsigned int nhrp_reqid_key(void *data)
+{
+ struct nhrp_reqid *r = data;
+ return r->request_id;
+}
+
+static int 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);
+ 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;
+ 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/resolver.c b/nhrpd/resolver.c
new file mode 100644
index 0000000..07bdb73
--- /dev/null
+++ b/nhrpd/resolver.c
@@ -0,0 +1,190 @@
+/* C-Ares integration to Quagga mainloop
+ * 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 <ares.h>
+#include <ares_version.h>
+
+#include "vector.h"
+#include "thread.h"
+#include "nhrpd.h"
+
+struct resolver_state {
+ ares_channel channel;
+ struct thread *timeout;
+ vector read_threads, write_threads;
+};
+
+static struct resolver_state state;
+
+#define THREAD_RUNNING ((struct thread *)-1)
+
+static void resolver_update_timeouts(struct resolver_state *r);
+
+static int resolver_cb_timeout(struct thread *t)
+{
+ struct resolver_state *r = THREAD_ARG(t);
+
+ r->timeout = THREAD_RUNNING;
+ ares_process(r->channel, NULL, NULL);
+ r->timeout = NULL;
+ resolver_update_timeouts(r);
+
+ return 0;
+}
+
+static int resolver_cb_socket_readable(struct thread *t)
+{
+ struct resolver_state *r = THREAD_ARG(t);
+ int fd = THREAD_FD(t);
+
+ vector_set_index(r->read_threads, fd, THREAD_RUNNING);
+ ares_process_fd(r->channel, fd, ARES_SOCKET_BAD);
+ if (vector_lookup(r->read_threads, fd) == THREAD_RUNNING) {
+ t = NULL;
+ THREAD_READ_ON(master, t, resolver_cb_socket_readable, r, fd);
+ vector_set_index(r->read_threads, fd, t);
+ }
+ resolver_update_timeouts(r);
+
+ return 0;
+}
+
+static int resolver_cb_socket_writable(struct thread *t)
+{
+ struct resolver_state *r = THREAD_ARG(t);
+ int fd = THREAD_FD(t);
+
+ vector_set_index(r->write_threads, fd, THREAD_RUNNING);
+ ares_process_fd(r->channel, ARES_SOCKET_BAD, fd);
+ if (vector_lookup(r->write_threads, fd) == THREAD_RUNNING) {
+ t = NULL;
+ THREAD_WRITE_ON(master, t, resolver_cb_socket_writable, r, fd);
+ vector_set_index(r->write_threads, fd, t);
+ }
+ resolver_update_timeouts(r);
+
+ return 0;
+}
+
+static void resolver_update_timeouts(struct resolver_state *r)
+{
+ struct timeval *tv, tvbuf;
+
+ if (r->timeout == THREAD_RUNNING) return;
+
+ THREAD_OFF(r->timeout);
+ tv = ares_timeout(r->channel, NULL, &tvbuf);
+ if (tv) {
+ unsigned int timeoutms = tv->tv_sec * 1000 + tv->tv_usec / 1000;
+ THREAD_TIMER_MSEC_ON(master, r->timeout, resolver_cb_timeout, r, timeoutms);
+ }
+}
+
+static void ares_socket_cb(void *data, ares_socket_t fd, int readable, int writable)
+{
+ struct resolver_state *r = (struct resolver_state *) data;
+ struct thread *t;
+
+ if (readable) {
+ t = vector_lookup_ensure(r->read_threads, fd);
+ if (!t) {
+ THREAD_READ_ON(master, t, resolver_cb_socket_readable, r, fd);
+ vector_set_index(r->read_threads, fd, t);
+ }
+ } else {
+ t = vector_lookup(r->read_threads, fd);
+ if (t) {
+ if (t != THREAD_RUNNING) {
+ THREAD_OFF(t);
+ }
+ vector_unset(r->read_threads, fd);
+ }
+ }
+
+ if (writable) {
+ t = vector_lookup_ensure(r->write_threads, fd);
+ if (!t) {
+ THREAD_READ_ON(master, t, resolver_cb_socket_writable, r, fd);
+ vector_set_index(r->write_threads, fd, t);
+ }
+ } else {
+ t = vector_lookup(r->write_threads, fd);
+ if (t) {
+ if (t != THREAD_RUNNING) {
+ THREAD_OFF(t);
+ }
+ vector_unset(r->write_threads, fd);
+ }
+ }
+}
+
+void resolver_init(void)
+{
+ struct ares_options ares_opts;
+
+ state.read_threads = vector_init(1);
+ state.write_threads = vector_init(1);
+
+ ares_opts = (struct ares_options) {
+ .sock_state_cb = &ares_socket_cb,
+ .sock_state_cb_data = &state,
+ .timeout = 2,
+ .tries = 3,
+ };
+
+ ares_init_options(&state.channel, &ares_opts,
+ ARES_OPT_SOCK_STATE_CB | ARES_OPT_TIMEOUT |
+ ARES_OPT_TRIES);
+}
+
+
+static void ares_address_cb(void *arg, int status, int timeouts, struct hostent *he)
+{
+ struct resolver_query *query = (struct resolver_query *) arg;
+ union sockunion addr[16];
+ size_t i;
+
+ if (status != ARES_SUCCESS) {
+ debugf(NHRP_DEBUG_COMMON, "[%p] Resolving failed", query);
+ query->callback(query, -1, NULL);
+ query->callback = NULL;
+ return;
+ }
+
+ for (i = 0; he->h_addr_list[i] != NULL && i < ZEBRA_NUM_OF(addr); i++) {
+ memset(&addr[i], 0, sizeof(addr[i]));
+ addr[i].sa.sa_family = he->h_addrtype;
+ switch (he->h_addrtype) {
+ case AF_INET:
+ memcpy(&addr[i].sin.sin_addr, (uint8_t *) he->h_addr_list[i], he->h_length);
+ break;
+ case AF_INET6:
+ memcpy(&addr[i].sin6.sin6_addr, (uint8_t *) he->h_addr_list[i], he->h_length);
+ break;
+ }
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "[%p] Resolved with %d results", query, (int) i);
+ query->callback(query, i, &addr[0]);
+ query->callback = NULL;
+}
+
+void resolver_resolve(struct resolver_query *query, int af, const char *hostname, void (*callback)(struct resolver_query *, int, union sockunion *))
+{
+ if (query->callback != NULL) {
+ zlog_err("Trying to resolve '%s', but previous query was not finished yet", hostname);
+ return;
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "[%p] Resolving '%s'", query, hostname);
+
+ query->callback = callback;
+ ares_gethostbyname(state.channel, hostname, af, ares_address_cb, query);
+ resolver_update_timeouts(&state);
+}
diff --git a/nhrpd/vici.c b/nhrpd/vici.c
new file mode 100644
index 0000000..507dd14
--- /dev/null
+++ b/nhrpd/vici.c
@@ -0,0 +1,482 @@
+/* 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.
+ */
+
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "thread.h"
+#include "zbuf.h"
+#include "log.h"
+#include "nhrpd.h"
+
+#include "vici.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->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->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 int 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_TIMER_ON(master, vici->t_reconnect, vici_reconnect, vici, 2);
+}
+
+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;
+ struct blob val;
+
+ 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);
+ }
+ } else {
+ nhrp_vc_ipsec_updown(sactx->child_uniqueid, 0);
+ }
+ }
+ break;
+ default:
+ switch (key->ptr[0]) {
+ case 'l':
+ if (blob_equal(key, "local-host") && ctx->nsections == 1) {
+ if (blob2buf(val, buf, sizeof(buf)))
+ str2sockunion(buf, &sactx->local.host);
+ } 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)))
+ str2sockunion(buf, &sactx->remote.host);
+ } 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 vici_recv_sa(struct vici_conn *vici, struct zbuf *msg, int event)
+{
+ char buf[32];
+ struct handle_sa_ctx ctx = {
+ .event = event,
+ };
+
+ 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;
+
+ 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_EVENT_UNKNOWN:
+ zlog_err("VICI: StrongSwan does not support mandatory events (unpatched?)");
+ break;
+ case VICI_EVENT_CONFIRM:
+ case VICI_CMD_RESPONSE:
+ break;
+ default:
+ zlog_notice("VICI: Unrecognized message type %d", msgtype);
+ break;
+ }
+}
+
+static int vici_read(struct thread *t)
+{
+ struct vici_conn *vici = THREAD_ARG(t);
+ struct zbuf *ibuf = &vici->ibuf;
+ struct zbuf pktbuf;
+
+ vici->t_read = NULL;
+ if (zbuf_read(ibuf, vici->fd, (size_t) -1) < 0) {
+ vici_connection_error(vici);
+ return 0;
+ }
+
+ /* 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_READ_ON(master, vici->t_read, vici_read, vici, vici->fd);
+ return 0;
+}
+
+static int vici_write(struct thread *t)
+{
+ struct vici_conn *vici = THREAD_ARG(t);
+ int r;
+
+ vici->t_write = NULL;
+ r = zbufq_write(&vici->obuf, vici->fd);
+ if (r > 0) {
+ THREAD_WRITE_ON(master, vici->t_write, vici_write, vici, vici->fd);
+ } else if (r < 0) {
+ vici_connection_error(vici);
+ }
+
+ return 0;
+}
+
+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_WRITE_ON(master, vici->t_write, vici_write, vici, vici->fd);
+}
+
+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;
+ case VICI_END:
+ 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 int vici_reconnect(struct thread *t)
+{
+ struct vici_conn *vici = THREAD_ARG(t);
+ int fd;
+
+ vici->t_reconnect = NULL;
+ if (vici->fd >= 0) return 0;
+
+ fd = sock_open_unix("/var/run/charon.vici");
+ if (fd < 0) {
+ zlog_warn("%s: failure connecting VICI socket: %s",
+ __PRETTY_FUNCTION__, strerror(errno));
+ THREAD_TIMER_ON(master, vici->t_reconnect, vici_reconnect, vici, 2);
+ return 0;
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "VICI: Connected");
+ vici->fd = fd;
+ THREAD_READ_ON(master, vici->t_read, vici_read, vici, vici->fd);
+
+ /* 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);
+
+ return 0;
+}
+
+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_TIMER_MSEC_ON(master, vici->t_reconnect, vici_reconnect, vici, 10);
+}
+
+void vici_terminate(void)
+{
+}
+
+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", 2, "-1",
+ VICI_KEY_VALUE, "async", 1, "1",
+ VICI_KEY_VALUE, "init-limits", 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 (struct sockaddr_un));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, path, strlen (path));
+
+ ret = connect(fd, (struct sockaddr *) &addr, sizeof(addr.sun_family) + strlen(addr.sun_path));
+ if (ret < 0) {
+ close(fd);
+ return -1;
+ }
+
+ fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
+
+ return fd;
+}
diff --git a/nhrpd/vici.h b/nhrpd/vici.h
new file mode 100644
index 0000000..24b900b
--- /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..ead7cfd
--- /dev/null
+++ b/nhrpd/zbuf.c
@@ -0,0 +1,219 @@
+/* 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.
+ */
+
+#define _GNU_SOURCE
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include "zassert.h"
+#include "zbuf.h"
+#include "memory.h"
+#include "memtypes.h"
+#include "nhrpd.h"
+
+#define ERRNO_IO_RETRY(EN) (((EN) == EAGAIN) || ((EN) == EWOULDBLOCK) || ((EN) == EINTR))
+
+struct zbuf *zbuf_alloc(size_t size)
+{
+ struct zbuf *zb;
+
+ zb = XMALLOC(MTYPE_STREAM_DATA, sizeof(*zb) + size);
+ if (!zb)
+ return NULL;
+
+ 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_STREAM_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)
+{
+ zassert((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 (r < 0 && 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 (r < 0 && 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 (r < 0 && 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 = LIST_INITIALIZER(zbq->queue_head),
+ };
+}
+
+void zbufq_reset(struct zbuf_queue *zbq)
+{
+ struct zbuf *buf, *bufn;
+
+ list_for_each_entry_safe(buf, bufn, &zbq->queue_head, queue_list) {
+ list_del(&buf->queue_list);
+ zbuf_free(buf);
+ }
+}
+
+void zbufq_queue(struct zbuf_queue *zbq, struct zbuf *zb)
+{
+ list_add_tail(&zb->queue_list, &zbq->queue_head);
+}
+
+int zbufq_write(struct zbuf_queue *zbq, int fd)
+{
+ struct iovec iov[16];
+ struct zbuf *zb, *zbn;
+ ssize_t r;
+ size_t iovcnt = 0;
+
+ list_for_each_entry_safe(zb, zbn, &zbq->queue_head, queue_list) {
+ iov[iovcnt++] = (struct iovec) {
+ .iov_base = zb->head,
+ .iov_len = zbuf_used(zb),
+ };
+ if (iovcnt >= ZEBRA_NUM_OF(iov))
+ break;
+ }
+
+ r = writev(fd, iov, iovcnt);
+ if (r < 0)
+ return r;
+
+ list_for_each_entry_safe(zb, zbn, &zbq->queue_head, queue_list) {
+ if (r < (ssize_t)zbuf_used(zb)) {
+ zb->head += r;
+ return 1;
+ }
+
+ r -= zbuf_used(zb);
+ list_del(&zb->queue_list);
+ 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);
+}
diff --git a/nhrpd/zbuf.h b/nhrpd/zbuf.h
new file mode 100644
index 0000000..73d7073
--- /dev/null
+++ b/nhrpd/zbuf.h
@@ -0,0 +1,189 @@
+/* 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 "zassert.h"
+#include "list.h"
+
+struct zbuf {
+ struct list_head queue_list;
+ unsigned allocated : 1;
+ unsigned error : 1;
+ uint8_t *buf, *end;
+ uint8_t *head, *tail;
+};
+
+struct zbuf_queue {
+ struct list_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) ((void *)__zbuf_pull(zb, sz, 1))
+#define zbuf_may_pull(zb, type) ((type *)__zbuf_pull(zb, sizeof(type), 0))
+#define zbuf_may_pulln(zb, sz) ((void *)__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) ((void *)__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) ((void *)__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 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..2216d97
--- /dev/null
+++ b/nhrpd/znl.c
@@ -0,0 +1,160 @@
+/* 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 <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;
+
+ fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+ 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..2cd630b
--- /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);
+
diff --git a/vtysh/Makefile.am b/vtysh/Makefile.am
index e44cd49..983103f 100644
--- a/vtysh/Makefile.am
+++ b/vtysh/Makefile.am
@@ -24,6 +24,7 @@ vtysh_cmd_FILES = $(top_srcdir)/bgpd/*.c $(top_srcdir)/isisd/*.c \
$(top_srcdir)/ospfd/*.c $(top_srcdir)/ospf6d/*.c \
$(top_srcdir)/ripd/*.c $(top_srcdir)/ripngd/*.c \
$(top_srcdir)/pimd/pim_cmd.c \
+ $(top_srcdir)/nhrpd/nhrp_vty.c \
$(top_srcdir)/lib/keychain.c $(top_srcdir)/lib/routemap.c \
$(top_srcdir)/lib/filter.c $(top_srcdir)/lib/plist.c \
$(top_srcdir)/lib/distribute.c $(top_srcdir)/lib/if_rmap.c \
diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c
index 2e2203d..1ee8582 100644
--- a/vtysh/vtysh.c
+++ b/vtysh/vtysh.c
@@ -60,6 +60,7 @@ struct vtysh_client
{ .fd = -1, .name = "bgpd", .flag = VTYSH_BGPD, .path = BGP_VTYSH_PATH},
{ .fd = -1, .name = "isisd", .flag = VTYSH_ISISD, .path = ISIS_VTYSH_PATH},
{ .fd = -1, .name = "pimd", .flag = VTYSH_PIMD, .path = PIM_VTYSH_PATH},
+ { .fd = -1, .name = "nhrpd", .flag = VTYSH_NHRPD, .path = NHRP_VTYSH_PATH},
};


diff --git a/vtysh/vtysh.h b/vtysh/vtysh.h
index 1681a71..02ff7fd 100644
--- a/vtysh/vtysh.h
+++ b/vtysh/vtysh.h
@@ -31,9 +31,10 @@
#define VTYSH_ISISD 0x40
#define VTYSH_BABELD 0x80
#define VTYSH_PIMD 0x100
-#define VTYSH_ALL VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_ISISD|VTYSH_BABELD|VTYSH_PIMD
+#define VTYSH_NHRPD 0x200
+#define VTYSH_ALL VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_ISISD|VTYSH_BABELD|VTYSH_PIMD|VTYSH_NHRPD
#define VTYSH_RMAP VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_BABELD
-#define VTYSH_INTERFACE VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_ISISD|VTYSH_BABELD|VTYSH_PIMD
+#define VTYSH_INTERFACE VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_ISISD|VTYSH_BABELD|VTYSH_PIMD|VTYSH_NHRPD

/* vtysh local configuration file. */
#define VTYSH_DEFAULT_CONFIG "vtysh.conf"
diff --git a/zebra/client_main.c b/zebra/client_main.c
index 43ab299..75c8867 100644
--- a/zebra/client_main.c
+++ b/zebra/client_main.c
@@ -120,6 +120,7 @@ struct zebra_info
{ "ospf", ZEBRA_ROUTE_OSPF },
{ "ospf6", ZEBRA_ROUTE_OSPF6 },
{ "bgp", ZEBRA_ROUTE_BGP },
+ { "nhrp", ZEBRA_ROUTE_NHRP },
{ NULL, 0 }
};

diff --git a/zebra/zebra_rib.c b/zebra/zebra_rib.c
index abb9560..b03ccc2 100644
--- a/zebra/zebra_rib.c
+++ b/zebra/zebra_rib.c
@@ -72,6 +72,7 @@ static const struct
[ZEBRA_ROUTE_ISIS] = {ZEBRA_ROUTE_ISIS, 115},
[ZEBRA_ROUTE_BGP] = {ZEBRA_ROUTE_BGP, 20 /* IBGP is 200. */},
[ZEBRA_ROUTE_BABEL] = {ZEBRA_ROUTE_BABEL, 95},
+ [ZEBRA_ROUTE_NHRP] = {ZEBRA_ROUTE_NHRP, 10},
/* no entry/default: 150 */
};

@@ -1423,6 +1424,7 @@ static const u_char meta_queue_map[ZEBRA_ROUTE_MAX] = {
[ZEBRA_ROUTE_BGP] = 3,
[ZEBRA_ROUTE_HSLS] = 4,
[ZEBRA_ROUTE_BABEL] = 2,
+ [ZEBRA_ROUTE_NHRP] = 2,
};

/* Look into the RN and queue it into one or more priority queues,
diff --git a/zebra/zebra_rnh.c b/zebra/zebra_rnh.c
index 5762d3f..859b6d7 100644
--- a/zebra/zebra_rnh.c
+++ b/zebra/zebra_rnh.c
@@ -223,16 +223,27 @@ zebra_evaluate_rnh_table (vrf_id_t vrfid, int family)
{
if (CHECK_FLAG (rib->status, RIB_ENTRY_REMOVED))
continue;
- if (CHECK_FLAG (rib->status, RIB_ENTRY_SELECTED_FIB))
+ if (! CHECK_FLAG (rib->status, RIB_ENTRY_SELECTED_FIB))
+ continue;
+
+ if (CHECK_FLAG(rnh->flags, ZEBRA_NHT_CONNECTED))
{
- if (CHECK_FLAG(rnh->flags, ZEBRA_NHT_CONNECTED))
+ if (rib->type == ZEBRA_ROUTE_CONNECT)
+ break;
+
+ if (rib->type == ZEBRA_ROUTE_NHRP)
{
- if (rib->type == ZEBRA_ROUTE_CONNECT)
+ struct nexthop *nexthop;
+ for (nexthop = rib->nexthop; nexthop; nexthop = nexthop->next)
+ if (nexthop->type == NEXTHOP_TYPE_IFINDEX ||
+ nexthop->type == NEXTHOP_TYPE_IFNAME)
+ break;
+ if (nexthop)
break;
}
- else
- break;
}
+ else
+ break;
}
}

diff --git a/zebra/zebra_vty.c b/zebra/zebra_vty.c
index 38f61e9..c73896b 100644
--- a/zebra/zebra_vty.c
+++ b/zebra/zebra_vty.c
@@ -2121,6 +2121,7 @@ vty_show_ip_route_detail (struct vty *vty, struct route_node *rn, int mcast)
|| rib->type == ZEBRA_ROUTE_OSPF6
|| rib->type == ZEBRA_ROUTE_BABEL
|| rib->type == ZEBRA_ROUTE_ISIS
+ || rib->type == ZEBRA_ROUTE_NHRP
|| rib->type == ZEBRA_ROUTE_BGP)
{
time_t uptime;
@@ -2341,6 +2342,7 @@ vty_show_ip_route (struct vty *vty, struct route_node *rn, struct rib *rib)
|| rib->type == ZEBRA_ROUTE_OSPF6
|| rib->type == ZEBRA_ROUTE_BABEL
|| rib->type == ZEBRA_ROUTE_ISIS
+ || rib->type == ZEBRA_ROUTE_NHRP
|| rib->type == ZEBRA_ROUTE_BGP)
{
time_t uptime;
--
2.10.2
c***@netdef.org
2016-11-07 17:40:03 UTC
Permalink
Continous Integration Result: FAILED

See below for issues.
This is an EXPERIMENTAL automated CI system.
For questions and feedback, feel free to email
Martin Winter <***@opensourcerouting.org>.

Patches applied :
Patchwork 2214: http://patchwork.quagga.net/patch/2214
[quagga-dev,16398,5/5,v2] nhrpd: implement next hop resolution protocol
Tested on top of Git : 258f3da (as of 20161018.130352 UTC)
CI System Testrun URL: https://ci1.netdef.org/browse/QUAGGA-QPWORK-378/


Get source and apply patch from patchwork: Failed
----------------
Applying Patchwork patch 2214
------------------------------------
Git Patch: Using "git apply" for patch 2214
<stdin>:511: new blank line at EOF.
+
<stdin>:654: new blank line at EOF.
+
<stdin>:2074: new blank line at EOF.
+
<stdin>:4780: new blank line at EOF.
+
<stdin>:5188: new blank line at EOF.
+
error: patch failed: zebra/zebra_rnh.c:223
error: zebra/zebra_rnh.c: patch does not apply
Regards,
NetDEF/OpenSourceRouting Continous Integration (CI) System

---
OpenSourceRouting.org is a project of the Network Device Education Foundation,
For more information, see www.netdef.org and www.opensourcerouting.org
For questions in regards to this CI System, contact Martin Winter, ***@netdef.org
Paul Jakma
2016-10-19 13:26:52 UTC
Permalink
Hi Timo,

Thanks. I'll try get this in the next slurpage. Due to travel that might
be after a week on tuesday though.

Aim will be to have the "staging/proposed -> push to master" cycle to 1
to max 2 weeks here-after.

(Modulo the odd cases of people saying "hey, I'm away that week or two
- mind adjusting?", as that crops up + whatever major holidays,
Christmas, Chinese New Year [and there's currently one in India?]).

regards,

Paul
Hi,
Now that we have all the important stuff in master (fib overrides,
nexthop tracking), I'm ready to submit NHRP for inclusion.
The first three commits fix simple bugs. The first two ones probably
are not triggerable without running NHRP. The third commit may have
caused regressions for some use cases.
The fourth patch is not strictly needed. But as I had it in my queue
and it's simplifying things, I'm posting it here too.
Finally the fifth commit implements all of nhrp and plugs it in place.
Thanks,
Timo
zebra: use FIB state for nexthop tracking
zebra: fix nht validity checking to be same as when it's resolved
bgpd: honor disable-connected-check option with next hop tracking
bgpd: simplify ebgp-multihop and ttl-security handling
nhrpd: implement next hop resolution protocol
.gitignore | 2 +
Makefile.am | 4 +-
SERVICES | 1 +
bgpd/bgp_fsm.c | 3 +-
bgpd/bgp_network.c | 58 ++--
bgpd/bgp_network.h | 1 +
bgpd/bgp_route.c | 4 +-
bgpd/bgp_vty.c | 26 +-
bgpd/bgp_zebra.c | 13 +-
bgpd/bgpd.c | 237 +++----------
bgpd/bgpd.h | 7 +-
configure.ac | 27 +-
lib/log.c | 5 +
lib/log.h | 3 +-
lib/memtypes.c | 14 +
lib/route_types.txt | 2 +
nhrpd/Makefile.am | 34 ++
nhrpd/README.kernel | 145 ++++++++
nhrpd/README.nhrpd | 137 ++++++++
nhrpd/linux.c | 153 ++++++++
nhrpd/list.h | 191 ++++++++++
nhrpd/netlink.c | 398 +++++++++++++++++++++
nhrpd/netlink.h | 21 ++
nhrpd/nhrp_cache.c | 341 ++++++++++++++++++
nhrpd/nhrp_event.c | 280 +++++++++++++++
nhrpd/nhrp_interface.c | 404 +++++++++++++++++++++
nhrpd/nhrp_main.c | 246 +++++++++++++
nhrpd/nhrp_nhs.c | 369 ++++++++++++++++++++
nhrpd/nhrp_packet.c | 312 +++++++++++++++++
nhrpd/nhrp_peer.c | 860 +++++++++++++++++++++++++++++++++++++++++++++
nhrpd/nhrp_protocol.h | 128 +++++++
nhrpd/nhrp_route.c | 345 ++++++++++++++++++
nhrpd/nhrp_shortcut.c | 402 +++++++++++++++++++++
nhrpd/nhrp_vc.c | 217 ++++++++++++
nhrpd/nhrp_vty.c | 928 +++++++++++++++++++++++++++++++++++++++++++++++++
nhrpd/nhrpd.h | 440 +++++++++++++++++++++++
nhrpd/os.h | 5 +
nhrpd/reqid.c | 49 +++
nhrpd/resolver.c | 190 ++++++++++
nhrpd/vici.c | 482 +++++++++++++++++++++++++
nhrpd/vici.h | 24 ++
nhrpd/zbuf.c | 219 ++++++++++++
nhrpd/zbuf.h | 189 ++++++++++
nhrpd/znl.c | 160 +++++++++
nhrpd/znl.h | 29 ++
vtysh/Makefile.am | 1 +
vtysh/vtysh.c | 1 +
vtysh/vtysh.h | 5 +-
zebra/client_main.c | 1 +
zebra/zebra_rib.c | 2 +
zebra/zebra_rnh.c | 23 +-
zebra/zebra_vty.c | 2 +
52 files changed, 7874 insertions(+), 266 deletions(-)
create mode 100644 nhrpd/Makefile.am
create mode 100644 nhrpd/README.kernel
create mode 100644 nhrpd/README.nhrpd
create mode 100644 nhrpd/linux.c
create mode 100644 nhrpd/list.h
create mode 100644 nhrpd/netlink.c
create mode 100644 nhrpd/netlink.h
create mode 100644 nhrpd/nhrp_cache.c
create mode 100644 nhrpd/nhrp_event.c
create mode 100644 nhrpd/nhrp_interface.c
create mode 100644 nhrpd/nhrp_main.c
create mode 100644 nhrpd/nhrp_nhs.c
create mode 100644 nhrpd/nhrp_packet.c
create mode 100644 nhrpd/nhrp_peer.c
create mode 100644 nhrpd/nhrp_protocol.h
create mode 100644 nhrpd/nhrp_route.c
create mode 100644 nhrpd/nhrp_shortcut.c
create mode 100644 nhrpd/nhrp_vc.c
create mode 100644 nhrpd/nhrp_vty.c
create mode 100644 nhrpd/nhrpd.h
create mode 100644 nhrpd/os.h
create mode 100644 nhrpd/reqid.c
create mode 100644 nhrpd/resolver.c
create mode 100644 nhrpd/vici.c
create mode 100644 nhrpd/vici.h
create mode 100644 nhrpd/zbuf.c
create mode 100644 nhrpd/zbuf.h
create mode 100644 nhrpd/znl.c
create mode 100644 nhrpd/znl.h
--
Paul Jakma | ***@jakma.org | @pjakma | Key ID: 0xD86BF79464A2FF6A
Fortune:
The Phone Booth Rule:
A lone dime always gets the number nearly right.
Paul Jakma
2017-01-12 13:17:10 UTC
Permalink
Hi Timo,

There's a compile failure here somewhere:

make[2]: Entering directory '/home/paul/code/quagga/nhrpd'
CC zbuf.o
CC znl.o
CC resolver.o
CC linux.o
CC netlink.o
In file included from /usr/include/linux/if_tunnel.h:6:0,
from netlink.c:17:
/usr/include/linux/ip.h:85:8: error: redefinition of ¡struct iphdr¢
struct iphdr {
^~~~~
In file included from netlink.c:12:0:
/usr/include/netinet/ip.h:44:8: note: originally defined here
struct iphdr
^~~~~
Makefile:497: recipe for target 'netlink.o' failed

# rpm -q kernel-headers
kernel-headers-4.8.15-200.fc24.x86_64

regards,
--
Paul Jakma | ***@jakma.org | @pjakma | Key ID: 0xD86BF79464A2FF6A
Fortune:
Laugh and the world laughs with you, snore and you sleep alone.
Timo Teras
2017-01-12 13:54:48 UTC
Permalink
On Thu, 12 Jan 2017 13:17:10 +0000 (GMT)
Post by Paul Jakma
make[2]: Entering directory '/home/paul/code/quagga/nhrpd'
CC zbuf.o
CC znl.o
CC resolver.o
CC linux.o
CC netlink.o
In file included from /usr/include/linux/if_tunnel.h:6:0,
/usr/include/linux/ip.h:85:8: error: redefinition of ‘struct iphdr’
struct iphdr {
^~~~~
/usr/include/netinet/ip.h:44:8: note: originally defined here
struct iphdr
^~~~~
Makefile:497: recipe for target 'netlink.o' failed
# rpm -q kernel-headers
kernel-headers-4.8.15-200.fc24.x86_64
Seems to be caused by the recent kernel commit:
http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=1fe8e0f074c77aa41aaa579345a9e675acbebfa9

It is known issue that some of the c-library and kernel header files
conflict with each other. Slightly sad, that it's getting more overlap.

Fundamentally, it would be good to get lib/quagga.h to not include all
known headers in the universe. This would allow to include quagga
library headers in connection with kernel headers.

Probably the simples fix is to split the netlink.c in two parts. I can
send a v3 nhrpd commit, unless you prefer a follow up commit that fixes
it?

Thanks,
Timo
Paul Jakma
2017-01-12 14:01:34 UTC
Permalink
Post by Timo Teras
http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=1fe8e0f074c77aa41aaa579345a9e675acbebfa9
It is known issue that some of the c-library and kernel header files
conflict with each other. Slightly sad, that it's getting more
overlap.
Ah, oh dear.
Post by Timo Teras
Fundamentally, it would be good to get lib/quagga.h to not include all
known headers in the universe. This would allow to include quagga
library headers in connection with kernel headers.
Agreed.

Beyond that, a larger cleanup of the exported APIs of header files. It'd
be good to define a stable subset, and export it properly under a
"quagga/" include namespace.
Post by Timo Teras
Probably the simples fix is to split the netlink.c in two parts. I can
send a v3 nhrpd commit, unless you prefer a follow up commit that fixes
it?
Whatever is easier for you. If you have a follow up and want to just
send that, that's fine.

regards,
--
Paul Jakma | ***@jakma.org | @pjakma | Key ID: 0xD86BF79464A2FF6A
Fortune:
Fear and loathing, my man, fear and loathing.
-- H.S. Thompson
Timo Teräs
2017-01-12 14:31:48 UTC
Permalink
This provides DMVPN support and integrates to strongSwan. Please read
README.nhrpd and README.kernel for more details.
---
v3: fix compilation error with new linux kernel headers

.gitignore | 2 +
Makefile.am | 4 +-
SERVICES | 1 +
configure.ac | 27 +-
lib/log.c | 5 +
lib/log.h | 4 +-
lib/memtypes.c | 14 +
lib/route_types.txt | 2 +
nhrpd/Makefile.am | 35 ++
nhrpd/README.kernel | 145 ++++++++
nhrpd/README.nhrpd | 137 ++++++++
nhrpd/linux.c | 153 ++++++++
nhrpd/list.h | 191 ++++++++++
nhrpd/netlink.h | 24 ++
nhrpd/nhrp_cache.c | 341 ++++++++++++++++++
nhrpd/nhrp_event.c | 280 +++++++++++++++
nhrpd/nhrp_interface.c | 404 +++++++++++++++++++++
nhrpd/nhrp_main.c | 246 +++++++++++++
nhrpd/nhrp_nhs.c | 369 ++++++++++++++++++++
nhrpd/nhrp_packet.c | 312 +++++++++++++++++
nhrpd/nhrp_peer.c | 860 +++++++++++++++++++++++++++++++++++++++++++++
nhrpd/nhrp_protocol.h | 128 +++++++
nhrpd/nhrp_route.c | 345 ++++++++++++++++++
nhrpd/nhrp_shortcut.c | 402 +++++++++++++++++++++
nhrpd/nhrp_vc.c | 217 ++++++++++++
nhrpd/nhrp_vty.c | 928 +++++++++++++++++++++++++++++++++++++++++++++++++
nhrpd/nhrpd.h | 400 +++++++++++++++++++++
nhrpd/os.h | 5 +
nhrpd/reqid.c | 49 +++
nhrpd/resolver.c | 190 ++++++++++
nhrpd/vici.c | 482 +++++++++++++++++++++++++
nhrpd/vici.h | 24 ++
nhrpd/zbuf.c | 219 ++++++++++++
nhrpd/zbuf.h | 189 ++++++++++
nhrpd/znl.c | 160 +++++++++
nhrpd/znl.h | 29 ++
vtysh/Makefile.am | 1 +
vtysh/vtysh.c | 1 +
vtysh/vtysh.h | 5 +-
zebra/client_main.c | 1 +
zebra/zebra_rib.c | 2 +
zebra/zebra_rnh.c | 21 +-
zebra/zebra_vty.c | 2 +
43 files changed, 7345 insertions(+), 11 deletions(-)
create mode 100644 nhrpd/Makefile.am
create mode 100644 nhrpd/README.kernel
create mode 100644 nhrpd/README.nhrpd
create mode 100644 nhrpd/linux.c
create mode 100644 nhrpd/list.h
create mode 100644 nhrpd/netlink.h
create mode 100644 nhrpd/nhrp_cache.c
create mode 100644 nhrpd/nhrp_event.c
create mode 100644 nhrpd/nhrp_interface.c
create mode 100644 nhrpd/nhrp_main.c
create mode 100644 nhrpd/nhrp_nhs.c
create mode 100644 nhrpd/nhrp_packet.c
create mode 100644 nhrpd/nhrp_peer.c
create mode 100644 nhrpd/nhrp_protocol.h
create mode 100644 nhrpd/nhrp_route.c
create mode 100644 nhrpd/nhrp_shortcut.c
create mode 100644 nhrpd/nhrp_vc.c
create mode 100644 nhrpd/nhrp_vty.c
create mode 100644 nhrpd/nhrpd.h
create mode 100644 nhrpd/os.h
create mode 100644 nhrpd/reqid.c
create mode 100644 nhrpd/resolver.c
create mode 100644 nhrpd/vici.c
create mode 100644 nhrpd/vici.h
create mode 100644 nhrpd/zbuf.c
create mode 100644 nhrpd/zbuf.h
create mode 100644 nhrpd/znl.c
create mode 100644 nhrpd/znl.h

diff --git a/.gitignore b/.gitignore
index a281555e..a8da62f1 100644
--- a/.gitignore
+++ b/.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
diff --git a/Makefile.am b/Makefile.am
index 0cd75be8..3dea4898 100644
--- a/Makefile.am
+++ b/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

diff --git a/SERVICES b/SERVICES
index c69d0c1a..0322d451 100644
--- a/SERVICES
+++ b/SERVICES
@@ -18,3 +18,4 @@ ospf6d 2606/tcp
ospfapi 2607/tcp
isisd 2608/tcp
pimd 2611/tcp
+nhrpd 2612/tcp
diff --git a/configure.ac b/configure.ac
index ce1bb7ea..f8fc7eb9 100755
--- a/configure.ac
+++ b/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,
@@ -1186,6 +1189,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
@@ -1241,6 +1255,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)
@@ -1276,6 +1291,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
@@ -1551,6 +1574,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)
@@ -1561,6 +1585,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)
@@ -1588,7 +1613,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
diff --git a/lib/log.c b/lib/log.c
index 42b7717e..d4370669 100644
--- a/lib/log.c
+++ b/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;
}
diff --git a/lib/log.h b/lib/log.h
index 7aa0896b..59968aee 100644
--- a/lib/log.h
+++ b/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
diff --git a/lib/memtypes.c b/lib/memtypes.c
index 8abe99d2..ba2bacfa 100644
--- a/lib/memtypes.c
+++ b/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},
};
diff --git a/lib/route_types.txt b/lib/route_types.txt
index 1b856079..811d24e0 100644
--- a/lib/route_types.txt
+++ b/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)"
diff --git a/nhrpd/Makefile.am b/nhrpd/Makefile.am
new file mode 100644
index 00000000..00ecc7f3
--- /dev/null
+++ b/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
diff --git a/nhrpd/README.kernel b/nhrpd/README.kernel
new file mode 100644
index 00000000..5831316f
--- /dev/null
+++ b/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.
+
diff --git a/nhrpd/README.nhrpd b/nhrpd/README.nhrpd
new file mode 100644
index 00000000..569b3f44
--- /dev/null
+++ b/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
+
diff --git a/nhrpd/linux.c b/nhrpd/linux.c
new file mode 100644
index 00000000..1e9c69eb
--- /dev/null
+++ b/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;
+}
diff --git a/nhrpd/list.h b/nhrpd/list.h
new file mode 100644
index 00000000..32f21ed5
--- /dev/null
+++ b/nhrpd/list.h
@@ -0,0 +1,191 @@
+/* Linux kernel style list handling function
+ *
+ * Written from scratch by Timo Teräs <***@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
diff --git a/nhrpd/netlink.h b/nhrpd/netlink.h
new file mode 100644
index 00000000..f05596ba
--- /dev/null
+++ b/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);
diff --git a/nhrpd/nhrp_cache.c b/nhrpd/nhrp_cache.c
new file mode 100644
index 00000000..447a814c
--- /dev/null
+++ b/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);
+}
diff --git a/nhrpd/nhrp_event.c b/nhrpd/nhrp_event.c
new file mode 100644
index 00000000..aab9ec64
--- /dev/null
+++ b/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);
+}
+
diff --git a/nhrpd/nhrp_interface.c b/nhrpd/nhrp_interface.c
new file mode 100644
index 00000000..8118927a
--- /dev/null
+++ b/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);
+}
diff --git a/nhrpd/nhrp_main.c b/nhrpd/nhrp_main.c
new file mode 100644
index 00000000..29349a03
--- /dev/null
+++ b/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;
+}
diff --git a/nhrpd/nhrp_nhs.c b/nhrpd/nhrp_nhs.c
new file mode 100644
index 00000000..d463e062
--- /dev/null
+++ b/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;
+
+ if (sockunion_family(proto_addr) != AF_UNSPEC &&
+ sockunion_family(proto_addr) != afi2family(afi))
+ return NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH;
+
+ list_for_each_entry(nhs, &nifp->afi[afi].nhslist_head, nhslist_entry) {
+ 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));
+ if (!nhs) return NHRP_ERR_NO_MEMORY;
+
+ *nhs = (struct nhrp_nhs) {
+ .afi = afi,
+ .ifp = ifp,
+ .proto_addr = *proto_addr,
+ .nbma_fqdn = strdup(nbma_fqdn),
+ .reglist_head = LIST_INITIALIZER(nhs->reglist_head),
+ };
+ list_add_tail(&nhs->nhslist_entry, &nifp->afi[afi].nhslist_head);
+ THREAD_TIMER_MSEC_ON(master, nhs->t_resolve, nhrp_nhs_resolve, nhs, 1000);
+
+ 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, *nnhs;
+ 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;
+
+ list_for_each_entry_safe(nhs, nnhs, &nifp->afi[afi].nhslist_head, nhslist_entry) {
+ if (!sockunion_same(&nhs->proto_addr, proto_addr))
+ continue;
+ if (strcmp(nhs->nbma_fqdn, nbma_fqdn) != 0)
+ continue;
+
+ nhrp_nhs_free(nhs);
+ ret = NHRP_OK;
+ }
+
+ return ret;
+}
+
+int nhrp_nhs_free(struct nhrp_nhs *nhs)
+{
+ struct nhrp_registration *r, *rn;
+
+ list_for_each_entry_safe(r, rn, &nhs->reglist_head, reglist_entry)
+ nhrp_reg_delete(r);
+ THREAD_OFF(nhs->t_resolve);
+ list_del(&nhs->nhslist_entry);
+ free((void*) nhs->nbma_fqdn);
+ XFREE(MTYPE_NHRP_NHS, nhs);
+ return 0;
+}
+
+void nhrp_nhs_terminate(void)
+{
+ struct interface *ifp;
+ struct nhrp_interface *nifp;
+ struct nhrp_nhs *nhs, *tmp;
+ struct listnode *node;
+ afi_t afi;
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+ nifp = ifp->info;
+ for (afi = 0; afi < AFI_MAX; afi++) {
+ list_for_each_entry_safe(nhs, tmp, &nifp->afi[afi].nhslist_head, nhslist_entry)
+ nhrp_nhs_free(nhs);
+ }
+ }
+}
diff --git a/nhrpd/nhrp_packet.c b/nhrpd/nhrp_packet.c
new file mode 100644
index 00000000..5d2866a6
--- /dev/null
+++ b/nhrpd/nhrp_packet.c
@@ -0,0 +1,312 @@
+/* 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.
+ */
+
+#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) {
+ 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->holding_time = htons(ad->holdtime);
+ break;
+ default:
+ if (type & NHRP_EXTENSION_FLAG_COMPULSORY)
+ goto err;
+ 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 int 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);
+
+ zb = zbuf_alloc(1500);
+ if (!zb) return 0;
+
+ 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);
+ 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 0;
+
+err:
+ zbuf_free(zb);
+ return 0;
+}
+
+int nhrp_packet_init(void)
+{
+ thread_add_read(master, nhrp_packet_recvraw, 0, os_socket());
+ return 0;
+}
diff --git a/nhrpd/nhrp_peer.c b/nhrpd/nhrp_peer.c
new file mode 100644
index 00000000..45bfd7de
--- /dev/null
+++ b/nhrpd/nhrp_peer.c
@@ -0,0 +1,860 @@
+/* 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.
+ */
+
+#include <netinet/if_ether.h>
+
+#include "zebra.h"
+#include "memory.h"
+#include "thread.h"
+#include "hash.h"
+
+#include "nhrpd.h"
+#include "nhrp_protocol.h"
+#include "os.h"
+
+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;
+
+ THREAD_OFF(p->t_fallback);
+ 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 int 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);
+ }
+
+ return 0;
+}
+
+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_TIMER_MSEC_ON(
+ master, p->t_fallback,
+ nhrp_peer_notify_up, p, 50);
+ } 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);
+ }
+ /* Fall-through to post config update */
+ case NOTIFY_INTERFACE_ADDRESS_CHANGED:
+ 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(void *peer_data)
+{
+ struct nhrp_peer *p = peer_data;
+ return sockunion_hash(&p->vc->remote.nbma);
+}
+
+static int 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));
+ if (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;
+}
+
+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);
+ 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 int 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;
+
+ p->t_fallback = NULL;
+
+ if (p->online)
+ return 0;
+
+ 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_TIMER_ON(master, p->t_fallback, nhrp_peer_request_timeout, p, 30);
+ } else {
+ p->requested = p->fallback_requested = 0;
+ }
+
+ return 0;
+}
+
+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 (sockunion_family(&vc->local.nbma) == AF_UNSPEC)
+ return 0;
+
+ p->prio = establish > 1;
+ p->requested = 1;
+ vici_request_vc(nifp->ipsec_profile, &vc->local.nbma, &vc->remote.nbma, p->prio);
+ THREAD_TIMER_ON(master, p->t_fallback, nhrp_peer_request_timeout, p,
+ (nifp->ipsec_fallback_profile && !p->prio) ? 15 : 30);
+
+ 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);
+ nhrp_peer_check_delete(p);
+}
+
+void nhrp_peer_send(struct nhrp_peer *p, struct zbuf *zb)
+{
+ char buf[2][256];
+
+ nhrp_packet_debug(zb, "Send");
+
+ if (!p->online)
+ return;
+
+ debugf(NHRP_DEBUG_KERNEL, "PACKET: Send %s -> %s",
+ sockunion2str(&p->vc->local.nbma, buf[0], sizeof buf[0]),
+ sockunion2str(&p->vc->remote.nbma, buf[1], sizeof buf[1]));
+
+ os_sendmsg(zb->head, zbuf_used(zb),
+ p->ifp->ifindex,
+ sockunion_get_addr(&p->vc->remote.nbma),
+ sockunion_get_addrlen(&p->vc->remote.nbma));
+ zbuf_reset(zb);
+}
+
+static void nhrp_handle_resolution_req(struct nhrp_packet_parser *p)
+{
+ struct zbuf *zb, payload;
+ struct nhrp_packet_header *hdr;
+ struct nhrp_cie_header *cie;
+ struct nhrp_extension_header *ext;
+ struct nhrp_interface *nifp;
+ struct nhrp_peer *peer;
+
+ if (!(p->if_ad->flags & NHRP_IFF_SHORTCUT)) {
+ debugf(NHRP_DEBUG_COMMON, "Shortcuts disabled");
+ /* FIXME: Send error indication? */
+ return;
+ }
+
+ if (p->if_ad->network_id &&
+ p->route_type == NHRP_ROUTE_OFF_NBMA &&
+ p->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(p->ifp, &p->src_proto, NULL, &peer) != NHRP_ROUTE_NBMA_NEXTHOP)
+ return;
+
+#if 0
+ /* FIXME: Update requestors binding if CIE specifies holding time */
+ nhrp_cache_update_binding(
+ NHRP_CACHE_CACHED, &p->src_proto,
+ nhrp_peer_get(p->ifp, &p->src_nbma),
+ htons(cie->holding_time));
+#endif
+
+ nifp = peer->ifp->info;
+
+ /* Create reply */
+ zb = zbuf_alloc(1500);
+ hdr = nhrp_packet_push(zb, NHRP_PACKET_RESOLUTION_REPLY, &p->src_nbma, &p->src_proto, &p->dst_proto);
+
+ /* Copied information from request */
+ hdr->flags = p->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 = p->hdr->u.request_id;
+
+ /* CIE payload */
+ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nbma, &p->if_ad->addr);
+ cie->holding_time = htons(p->if_ad->holdtime);
+ cie->mtu = htons(p->if_ad->mtu);
+ if (p->if_ad->network_id && p->route_type == NHRP_ROUTE_OFF_NBMA)
+ cie->prefix_length = p->route_prefix.prefixlen;
+ else
+ cie->prefix_length = 8 * sockunion_get_addrlen(&p->if_ad->addr);
+
+ /* Handle extensions */
+ while ((ext = nhrp_ext_pull(&p->extensions, &payload)) != NULL) {
+ switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) {
+ case NHRP_EXTENSION_NAT_ADDRESS:
+ if (sockunion_family(&nifp->nat_nbma) == AF_UNSPEC)
+ break;
+ ext = nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS);
+ if (!ext) goto err;
+ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nat_nbma, &p->if_ad->addr);
+ if (!cie) goto err;
+ nhrp_ext_complete(zb, ext);
+ break;
+ default:
+ if (nhrp_ext_reply(zb, hdr, p->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, natted = 0;
+ size_t paylen;
+ void *pay;
+
+ debugf(NHRP_DEBUG_COMMON, "Parsing and replying to Registration Req");
+
+ 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) {
+ if (cie->prefix_length != 0xff && !(p->hdr->flags & htons(NHRP_FLAG_REGISTRATION_UNIQUE))) {
+ cie->code = NHRP_CODE_BINDING_NON_UNIQUE;
+ continue;
+ }
+
+ /* We currently support only unique prefix registrations */
+ if (cie->prefix_length != 0xff) {
+ 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 = nbma_addr;
+ 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)) {
+ 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) {
+ nhrp_cie_push(zb, NHRP_CODE_SUCCESS,
+ &p->peer->vc->remote.nbma,
+ &p->src_proto);
+ }
+ 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;
+ char buf[2][SU_ADDRSTRLEN];
+
+ 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 %s about packet to %s ignored",
+ sockunion2str(&p->vc->remote.nbma, buf[0], sizeof buf[0]),
+ sockunion2str(&dst, buf[1], sizeof buf[1]));
+ return;
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "Send Traffic Indication to %s (online=%d) about packet to %s",
+ sockunion2str(&p->vc->remote.nbma, buf[0], sizeof buf[0]),
+ p->online,
+ sockunion2str(&dst, buf[1], sizeof buf[1]));
+
+ /* Create reply */
+ zb = zbuf_alloc(1500);
+ hdr = nhrp_packet_push(zb, NHRP_PACKET_TRAFFIC_INDICATION, &nifp->nbma, &if_ad->addr, &dst);
+ hdr->hop_count = 0;
+
+ /* 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;
+ char buf[2][SU_ADDRSTRLEN];
+
+ hdr = nhrp_packet_pull(&origmsg, &src_nbma, &src_proto, &dst_proto);
+ if (!hdr) return;
+
+ debugf(NHRP_DEBUG_COMMON, "Error Indication from %s about packet to %s ignored",
+ sockunion2str(&pp->src_proto, buf[0], sizeof buf[0]),
+ sockunion2str(&dst_proto, buf[1], sizeof buf[1]));
+
+ 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;
+ char buf[2][SU_ADDRSTRLEN];
+
+ if (!parse_ether_packet(&p->payload, htons(p->hdr->protocol_type), NULL, &dst))
+ return;
+
+ debugf(NHRP_DEBUG_COMMON, "Traffic Indication from %s about packet to %s: %s",
+ sockunion2str(&p->src_proto, buf[0], sizeof buf[0]),
+ sockunion2str(&dst, buf[1], sizeof buf[1]),
+ (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[] = {
+ [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, 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;
+ uint16_t type, len;
+
+ if (pp->hdr->hop_count == 0)
+ return;
+
+ /* Create forward packet - copy header */
+ zb = 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(zb, &pp->payload, zbuf_used(&pp->payload));
+
+ /* 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 ((cie = 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->holding_time = htons(if_ad->holdtime);
+ }
+ 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;
+ 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);
+ return;
+err:
+ nhrp_packet_debug(pp->pkt, "FWD-FAIL");
+ zbuf_free(zb);
+}
+
+static void nhrp_packet_debug(struct zbuf *zb, const char *dir)
+{
+ char buf[2][SU_ADDRSTRLEN];
+ 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);
+
+ sockunion2str(&src_proto, buf[0], sizeof buf[0]);
+ sockunion2str(&dst_proto, buf[1], sizeof buf[1]);
+
+ reply = packet_types[hdr->type].type == PACKET_REPLY;
+ debugf(NHRP_DEBUG_COMMON, "%s %s(%d) %s -> %s",
+ dir,
+ packet_types[hdr->type].name ? : "Unknown",
+ hdr->type,
+ reply ? buf[1] : buf[0],
+ reply ? buf[0] : buf[1]);
+}
+
+struct nhrp_route_info {
+ int local;
+ struct interface *ifp;
+ struct nhrp_vc *vc;
+};
+
+void nhrp_peer_recv(struct nhrp_peer *p, struct zbuf *zb)
+{
+ char buf[2][SU_ADDRSTRLEN];
+ 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 afi;
+
+ debugf(NHRP_DEBUG_KERNEL, "PACKET: Recv %s -> %s",
+ sockunion2str(&vc->remote.nbma, buf[0], sizeof buf[0]),
+ sockunion2str(&vc->local.nbma, buf[1], sizeof buf[1]));
+
+ 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;
+
+ afi = htons(hdr->afnum);
+ if (hdr->type > ZEBRA_NUM_OF(packet_types) ||
+ hdr->version != NHRP_VERSION_RFC2332 ||
+ afi >= AFI_MAX ||
+ packet_types[hdr->type].type == PACKET_UNKNOWN ||
+ htons(hdr->packet_size) > realsize) {
+ zlog_info("From %s: error: packet type %d, version %d, AFI %d, size %d (real size %d)",
+ sockunion2str(&vc->remote.nbma, buf[0], sizeof buf[0]),
+ (int) hdr->type, (int) hdr->version, (int) afi,
+ (int) htons(hdr->packet_size),
+ (int) realsize);
+ goto drop;
+ }
+ pp.if_ad = &((struct nhrp_interface *)ifp->info)->afi[afi];
+
+ extoff = htons(hdr->extension_offset);
+ if (extoff) {
+ if (extoff >= realsize) {
+ info = "extoff larger than packet";
+ goto drop;
+ }
+ paylen = extoff - (zb->head - zb->buf);
+ } 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[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 */
+ }
+ }
+ 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 %s: error: %s",
+ sockunion2str(&vc->remote.nbma, buf[0], sizeof buf[0]),
+ 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 00000000..a4bc9fa6
--- /dev/null
+++ b/nhrpd/nhrp_protocol.h
@@ -0,0 +1,128 @@
+/* nhrp_protocol.h - NHRP protocol definitions
+ *
+ * Copyright (c) 2007-2012 Timo Teräs <***@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
+
+/* 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 00000000..cc6b5fa2
--- /dev/null
+++ b/nhrpd/nhrp_route.c
@@ -0,0 +1,345 @@
+/* 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.
+ */
+
+#include "nhrpd.h"
+#include "table.h"
+#include "memory.h"
+#include "stream.h"
+#include "log.h"
+#include "zclient.h"
+
+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 void nhrp_zebra_connected(struct zclient *zclient)
+{
+ /* No real VRF support yet -- bind only to the default vrf */
+ zclient_send_requests (zclient, VRF_DEFAULT);
+}
+
+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_family(&ri->via) == AF_UNSPEC) {
+ XFREE(MTYPE_NHRP_ROUTE, rn->info);
+ rn->info = NULL;
+ 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_family(nexthop) != AF_UNSPEC) || ifp);
+ if (rn) {
+ ri = rn->info;
+ ri->via = *nexthop;
+ ri->ifp = ifp;
+ nhrp_route_update_put(rn);
+ }
+}
+
+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 in_addr *nexthop_ipv4;
+ int flags = 0;
+
+ if (zclient->sock < 0)
+ return;
+
+ switch (type) {
+ case NHRP_CACHE_NEGATIVE:
+ SET_FLAG(flags, ZEBRA_FLAG_REJECT);
+ 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(flags, ZEBRA_FLAG_FIB_OVERRIDE);
+ break;
+ }
+ SET_FLAG(flags, ZEBRA_FLAG_INTERNAL);
+
+ if (p->family == AF_INET) {
+ struct zapi_ipv4 api;
+
+ memset(&api, 0, sizeof(api));
+ api.flags = flags;
+ api.type = ZEBRA_ROUTE_NHRP;
+ api.safi = SAFI_UNICAST;
+
+ SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP);
+ if (nexthop) {
+ SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP);
+ nexthop_ipv4 = (struct in_addr *) sockunion_get_addr(nexthop);
+ api.nexthop_num = 1;
+ api.nexthop = &nexthop_ipv4;
+ }
+ if (ifp) {
+ SET_FLAG(api.message, ZAPI_MESSAGE_IFINDEX);
+ api.ifindex_num = 1;
+ api.ifindex = &ifp->ifindex;
+ }
+ if (mtu) {
+ SET_FLAG(api.message, ZAPI_MESSAGE_MTU);
+ api.mtu = mtu;
+ }
+
+ if (unlikely(debug_flags & NHRP_DEBUG_ROUTE)) {
+ char buf[2][INET_ADDRSTRLEN];
+ zlog_debug("Zebra send: IPv4 route %s %s/%d nexthop %s metric %u"
+ " count %d dev %s",
+ add ? "add" : "del",
+ inet_ntop(AF_INET, &p->u.prefix4, buf[0], sizeof(buf[0])),
+ p->prefixlen,
+ nexthop ? inet_ntop(AF_INET, api.nexthop[0], buf[1], sizeof(buf[1])) : "<onlink>",
+ api.metric, api.nexthop_num, ifp->name);
+ }
+
+ zapi_ipv4_route(
+ add ? ZEBRA_IPV4_ROUTE_ADD : ZEBRA_IPV4_ROUTE_DELETE,
+ zclient, (struct prefix_ipv4 *) p, &api);
+ }
+}
+
+int nhrp_route_read(int cmd, struct zclient *zclient, zebra_size_t length, vrf_id_t vrf_id)
+{
+ struct stream *s;
+ struct interface *ifp = NULL;
+ struct prefix prefix;
+ union sockunion nexthop_addr;
+ unsigned char message, nexthop_num, ifindex_num;
+ unsigned ifindex;
+ char buf[2][PREFIX_STRLEN];
+ int i, afaddrlen, added;
+
+ s = zclient->ibuf;
+ memset(&prefix, 0, sizeof(prefix));
+ sockunion_family(&nexthop_addr) = AF_UNSPEC;
+
+ /* Type, flags, message. */
+ /*type =*/ stream_getc(s);
+ /*flags =*/ stream_getc(s);
+ message = stream_getc(s);
+
+ /* Prefix */
+ switch (cmd) {
+ case ZEBRA_IPV4_ROUTE_ADD:
+ case ZEBRA_IPV4_ROUTE_DELETE:
+ prefix.family = AF_INET;
+ break;
+ case ZEBRA_IPV6_ROUTE_ADD:
+ case ZEBRA_IPV6_ROUTE_DELETE:
+ prefix.family = AF_INET6;
+ break;
+ default:
+ return -1;
+ }
+ afaddrlen = family2addrsize(prefix.family);
+ prefix.prefixlen = stream_getc(s);
+ stream_get(&prefix.u.val, s, PSIZE(prefix.prefixlen));
+
+ /* Nexthop, ifindex, distance, metric. */
+ if (CHECK_FLAG(message, ZAPI_MESSAGE_NEXTHOP|ZAPI_MESSAGE_IFINDEX)) {
+ nexthop_num = stream_getc(s);
+ for (i = 0; i < nexthop_num; i++) {
+ stream_get(buf[0], s, afaddrlen);
+ if (i == 0) sockunion_set(&nexthop_addr, prefix.family, (u_char*) buf[0], afaddrlen);
+ }
+ ifindex_num = stream_getc(s);
+ for (i = 0; i < ifindex_num; i++) {
+ ifindex = stream_getl(s);
+ if (i == 0 && ifindex != IFINDEX_INTERNAL)
+ ifp = if_lookup_by_index(ifindex);
+ }
+ }
+ if (CHECK_FLAG(message, ZAPI_MESSAGE_DISTANCE))
+ /*distance =*/ stream_getc(s);
+ if (CHECK_FLAG(message, ZAPI_MESSAGE_METRIC))
+ /*metric =*/ stream_getl(s);
+
+ added = (cmd == ZEBRA_IPV4_ROUTE_ADD || cmd == ZEBRA_IPV6_ROUTE_ADD);
+ debugf(NHRP_DEBUG_ROUTE, "if-route-%s: %s via %s dev %s",
+ added ? "add" : "del",
+ prefix2str(&prefix, buf[0], sizeof buf[0]),
+ sockunion2str(&nexthop_addr, buf[1], sizeof buf[1]),
+ ifp ? ifp->name : "(none)");
+
+ nhrp_route_update_zebra(&prefix, &nexthop_addr, ifp);
+ nhrp_shortcut_prefix_change(&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));
+ char buf[PREFIX_STRLEN];
+
+ 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 %s: nhrp_if=%s",
+ prefix2str(&lookup, buf, sizeof buf),
+ ri->nhrp_ifp->name);
+
+ if (via) sockunion_family(via) = AF_UNSPEC;
+ if (ifp) *ifp = ri->nhrp_ifp;
+ } else {
+ debugf(NHRP_DEBUG_ROUTE, "lookup %s: zebra route dev %s",
+ prefix2str(&lookup, buf, sizeof buf),
+ 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;
+}
+
+void nhrp_zebra_init(void)
+{
+ zebra_rib[AFI_IP] = route_table_init();
+ zebra_rib[AFI_IP6] = route_table_init();
+
+ zclient = zclient_new(master);
+ zclient->zebra_connected = nhrp_zebra_connected;
+ zclient->interface_add = nhrp_interface_add;
+ zclient->interface_delete = nhrp_interface_delete;
+ zclient->interface_up = nhrp_interface_up;
+ zclient->interface_down = nhrp_interface_down;
+ zclient->interface_address_add = nhrp_interface_address_add;
+ zclient->interface_address_delete = nhrp_interface_address_delete;
+ zclient->ipv4_route_add = nhrp_route_read;
+ zclient->ipv4_route_delete = nhrp_route_read;
+ zclient->ipv6_route_add = nhrp_route_read;
+ zclient->ipv6_route_delete = nhrp_route_read;
+
+ zclient_init(zclient, ZEBRA_ROUTE_NHRP);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_KERNEL, VRF_DEFAULT);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_CONNECT, VRF_DEFAULT);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_STATIC, VRF_DEFAULT);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_RIP, VRF_DEFAULT);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_OSPF, VRF_DEFAULT);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_ISIS, VRF_DEFAULT);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_BGP, VRF_DEFAULT);
+}
+
+void nhrp_zebra_terminate(void)
+{
+ zclient_stop(zclient);
+ route_table_finish(zebra_rib[AFI_IP]);
+ route_table_finish(zebra_rib[AFI_IP6]);
+}
+
diff --git a/nhrpd/nhrp_shortcut.c b/nhrpd/nhrp_shortcut.c
new file mode 100644
index 00000000..421f2886
--- /dev/null
+++ b/nhrpd/nhrp_shortcut.c
@@ -0,0 +1,402 @@
+/* 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.
+ */
+
+#include "nhrpd.h"
+#include "table.h"
+#include "memory.h"
+#include "thread.h"
+#include "log.h"
+#include "nhrp_protocol.h"
+
+static struct route_table *shortcut_rib[AFI_MAX];
+
+static int 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)
+{
+ char buf[PREFIX_STRLEN];
+
+ if (s->expiring && s->cache && s->cache->used) {
+ debugf(NHRP_DEBUG_ROUTE, "Shortcut %s used and expiring",
+ prefix2str(s->p, buf, sizeof buf));
+ nhrp_shortcut_send_resolution_req(s);
+ }
+}
+
+static int nhrp_shortcut_do_expire(struct thread *t)
+{
+ struct nhrp_shortcut *s = THREAD_ARG(t);
+
+ s->t_timer = NULL;
+ THREAD_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, s->holding_time/3);
+ s->expiring = 1;
+ nhrp_shortcut_check_use(s);
+
+ return 0;
+}
+
+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);
+
+ switch (cmd) {
+ case NOTIFY_CACHE_UP:
+ if (!s->route_installed) {
+ nhrp_route_announce(1, s->type, s->p, NULL, &s->cache->remote_addr, 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;
+ nhrp_shortcut_cache_notify(&s->cache_notifier, NOTIFY_CACHE_UP);
+ }
+ }
+ if (!s->cache || !s->cache->route_installed)
+ 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_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_expire, s, 2*holding_time/3);
+ }
+}
+
+static void nhrp_shortcut_delete(struct nhrp_shortcut *s)
+{
+ struct route_node *rn;
+ afi_t afi = family2afi(PREFIX_FAMILY(s->p));
+ char buf[PREFIX_STRLEN];
+
+ THREAD_OFF(s->t_timer);
+ nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid);
+
+ debugf(NHRP_DEBUG_ROUTE, "Shortcut %s purged",
+ prefix2str(s->p, buf, sizeof buf));
+
+ 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 int nhrp_shortcut_do_purge(struct thread *t)
+{
+ struct nhrp_shortcut *s = THREAD_ARG(t);
+ s->t_timer = NULL;
+ nhrp_shortcut_delete(s);
+ return 0;
+}
+
+static struct nhrp_shortcut *nhrp_shortcut_get(struct prefix *p)
+{
+ struct nhrp_shortcut *s;
+ struct route_node *rn;
+ char buf[PREFIX_STRLEN];
+ 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 %s created",
+ prefix2str(s->p, buf, sizeof buf));
+ } 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 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;
+ union sockunion *proto, cie_proto, *nbma, *nbma_natoa, cie_nbma, nat_nbma;
+ struct prefix prefix, route_prefix;
+ struct zbuf extpl;
+ char bufp[PREFIX_STRLEN], buf[3][SU_ADDRSTRLEN];
+ int holding_time = pp->if_ad->holdtime;
+
+ nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid);
+ THREAD_OFF(s->t_timer);
+ THREAD_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 1);
+
+ 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;
+ }
+
+ /* 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:
+ nhrp_cie_pull(&extpl, pp->hdr, &nat_nbma, &cie_proto);
+ break;
+ }
+ }
+
+ /* 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 %s to %s",
+ sockunion2str(&cie_proto, buf[0], sizeof buf[0]),
+ sockunion2str(&pp->dst_proto, buf[1], sizeof buf[1]));
+ }
+
+ /* 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 = 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;
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "Shortcut: %s is at proto %s cie-nbma %s nat-nbma %s cie-holdtime %d",
+ prefix2str(&prefix, bufp, sizeof bufp),
+ sockunion2str(proto, buf[0], sizeof buf[0]),
+ sockunion2str(&cie_nbma, buf[1], sizeof buf[1]),
+ sockunion2str(&nat_nbma, buf[2], sizeof buf[2]),
+ htons(cie->holding_time));
+
+ /* Update cache entry for the protocol to nbma binding */
+ if (sockunion_family(&nat_nbma) != AF_UNSPEC) {
+ nbma = &nat_nbma;
+ nbma_natoa = &cie_nbma;
+ } else {
+ nbma = &cie_nbma;
+ nbma_natoa = NULL;
+ }
+ if (sockunion_family(nbma)) {
+ c = nhrp_cache_get(pp->ifp, proto, 1);
+ if (c) {
+ nhrp_cache_update_binding(
+ c, NHRP_CACHE_CACHED, holding_time,
+ nhrp_peer_get(pp->ifp, nbma),
+ htons(cie->mtu), nbma_natoa);
+ }
+ }
+
+ /* Update shortcut entry for subnet to protocol gw binding */
+ if (c && !sockunion_same(proto, &pp->dst_proto)) {
+ ps = nhrp_shortcut_get(&prefix);
+ if (ps) {
+ ps->addr = s->addr;
+ nhrp_shortcut_update_binding(ps, NHRP_CACHE_CACHED, c, holding_time);
+ }
+ }
+
+ 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_peer *peer;
+
+ 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: Send holding time, and MTU */
+
+ nhrp_ext_request(zb, hdr, ifp);
+
+ /* Cisco NAT detection extension */
+ hdr->flags |= htons(NHRP_FLAG_RESOLUTION_NAT);
+ nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS);
+
+ 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;
+
+ sockunion2hostprefix(addr, &p);
+ s = nhrp_shortcut_get(&p);
+ if (s && s->type != NHRP_CACHE_INCOMPLETE) {
+ s->addr = *addr;
+ THREAD_OFF(s->t_timer);
+ THREAD_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 30);
+ 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_TIMER_MSEC_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 5);
+ } 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_TIMER_MSEC_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 3000);
+ 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 00000000..f9e1ee06
--- /dev/null
+++ b/nhrpd/nhrp_vc.c
@@ -0,0 +1,217 @@
+/* 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"
+
+struct child_sa {
+ uint32_t id;
+ struct nhrp_vc *vc;
+ struct list_head childlist_entry;
+};
+
+static struct hash *nhrp_vc_hash;
+static struct list_head childlist_head[512];
+
+static unsigned int nhrp_vc_key(void *peer_data)
+{
+ struct nhrp_vc *vc = peer_data;
+ return jhash_2words(
+ sockunion_hash(&vc->local.nbma),
+ sockunion_hash(&vc->remote.nbma),
+ 0);
+}
+
+static int 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));
+ if (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)
+{
+ char buf[2][SU_ADDRSTRLEN];
+ struct child_sa *sa = NULL, *lsa;
+ uint32_t child_hash = child_id % ZEBRA_NUM_OF(childlist_head);
+ int abort_migration = 0;
+
+ list_for_each_entry(lsa, &childlist_head[child_hash], childlist_entry) {
+ if (lsa->id == child_id) {
+ sa = lsa;
+ break;
+ }
+ }
+
+ if (!sa) {
+ if (!vc) return 0;
+
+ sa = XMALLOC(MTYPE_NHRP_VC, sizeof(struct child_sa));
+ if (!sa) return 0;
+
+ *sa = (struct child_sa) {
+ .id = child_id,
+ .childlist_entry = LIST_INITIALIZER(sa->childlist_entry),
+ .vc = NULL,
+ };
+ list_add_tail(&sa->childlist_entry, &childlist_head[child_hash]);
+ }
+
+ 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 %s to %s",
+ sockunion2str(&sa->vc->remote.nbma, buf[0], sizeof buf[0]),
+ sockunion2str(&vc->remote.nbma, buf[1], sizeof buf[1]));
+ 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) {
+ list_del(&sa->childlist_entry);
+ 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);
+ 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_backet *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);
+ for (i = 0; i < ZEBRA_NUM_OF(childlist_head); i++)
+ list_init(&childlist_head[i]);
+}
+
+void nhrp_vc_reset(void)
+{
+ struct child_sa *sa, *n;
+ size_t i;
+
+ for (i = 0; i < ZEBRA_NUM_OF(childlist_head); i++) {
+ list_for_each_entry_safe(sa, n, &childlist_head[i], childlist_entry)
+ 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 00000000..9b1c69de
--- /dev/null
+++ b/nhrpd/nhrp_vty.c
@@ -0,0 +1,928 @@
+/* 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 "nhrpd.h"
+#include "netlink.h"
+
+static struct cmd_node zebra_node = {
+ .node = ZEBRA_NODE,
+ .prompt = "%s(config-router)# ",
+ .vtysh = 1,
+};
+
+static struct cmd_node nhrp_interface_node = {
+ .node = INTERFACE_NODE,
+ .prompt = "%s(config-if)# ",
+ .vtysh = 1,
+};
+
+#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, NULL },
+};
+
+static const struct message interface_flags_desc[] = {
+ { NHRP_IFF_SHORTCUT, "shortcut" },
+ { NHRP_IFF_REDIRECT, "redirect" },
+ { NHRP_IFF_REG_NO_UNIQUE, "registration no-unique" },
+ { 0, NULL },
+};
+
+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 <= (int)ZEBRA_NUM_OF(errmsgs))
+ if (errmsgs[ret])
+ str = errmsgs[ret];
+
+ if (!str) {
+ str = buf;
+ snprintf(buf, sizeof(buf), "Unknown error %d", ret);
+ }
+
+ vty_out (vty, "%% %s%s", str, VTY_NEWLINE);
+
+ return CMD_WARNING;
+}
+
+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%s", name, VTY_NEWLINE);
+ return CMD_WARNING;
+}
+
+#ifndef NO_DEBUG
+
+DEFUN(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:%s", VTY_NEWLINE);
+
+ 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%s",
+ debug_flags_desc[i].str, VTY_NEWLINE);
+ }
+
+ 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[0], 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[0], 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%s", VTY_NEWLINE);
+ } 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%s", debug_flags_desc[i].str, VTY_NEWLINE);
+ }
+ }
+ vty_out(vty, "!%s", VTY_NEWLINE);
+#endif /* NO_DEBUG */
+
+ if (nhrp_event_socket_path) {
+ vty_out(vty, "nhrp event socket %s%s",
+ nhrp_event_socket_path, VTY_NEWLINE);
+ }
+ if (netlink_nflog_group) {
+ vty_out(vty, "nhrp nflog-group %d%s",
+ netlink_nflog_group, VTY_NEWLINE);
+ }
+
+ 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 char *cmd)
+{
+ return strncmp(cmd, "ipv6", 4) == 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")
+{
+ evmgr_set_socket(argv[0]);
+ 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")
+{
+ 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;
+
+ VTY_GET_INTEGER_RANGE("nflog-group", nfgroup, argv[0], 1, 65535);
+ 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(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")
+{
+ struct interface *ifp = vty->index;
+
+ nhrp_interface_set_protection(ifp, argv[0], argv[1]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_tunnel_protection, no_tunnel_protection_cmd,
+ "no tunnel protection",
+ NO_STR
+ "NHRP/GRE integration\n"
+ "IPsec protection\n")
+{
+ struct interface *ifp = vty->index;
+
+ 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")
+{
+ struct interface *ifp = vty->index;
+ nhrp_interface_set_source(ifp, argv[0]);
+ 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")
+{
+ struct interface *ifp = vty->index;
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ VTY_GET_INTEGER_RANGE("network-id", nifp->afi[afi].network_id, argv[1], 1, 4294967295);
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ return toggle_flag(vty, interface_flags_desc, argv[1], 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ return toggle_flag(vty, interface_flags_desc, argv[1], 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+ char name[256];
+ snprintf(name, sizeof(name), "registration %s", argv[1]);
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+ char name[256];
+ snprintf(name, sizeof(name), "registration %s", argv[1]);
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ VTY_GET_INTEGER_RANGE("holdtime", nifp->afi[afi].holdtime, argv[1], 1, 65000);
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+
+ if (argv[0][0] == 'o') {
+ nifp->afi[AFI_IP].configured_mtu = -1;
+ } else {
+ VTY_GET_INTEGER_RANGE("mtu", nifp->afi[AFI_IP].configured_mtu, argv[0], 576, 1500);
+ }
+ 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")
+{
+ struct interface *ifp = vty->index;
+ 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")
+{
+ struct interface *ifp = vty->index;
+ afi_t afi = cmd_to_afi(argv[0]);
+ union sockunion proto_addr, nbma_addr;
+ struct nhrp_cache *c;
+
+ if (str2sockunion(argv[1], &proto_addr) < 0 ||
+ afi2family(afi) != sockunion_family(&proto_addr))
+ return nhrp_vty_return(vty, NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH);
+
+ c = nhrp_cache_get(ifp, &proto_addr, 1);
+ if (!c)
+ return nhrp_vty_return(vty, NHRP_ERR_FAIL);
+
+ c->map = 1;
+ if (strcmp(argv[2], "local") == 0) {
+ nhrp_cache_update_binding(c, NHRP_CACHE_LOCAL, 0, NULL, 0, NULL);
+ } else{
+ if (str2sockunion(argv[2], &nbma_addr) < 0)
+ return nhrp_vty_return(vty, NHRP_ERR_FAIL);
+ nhrp_cache_update_binding(c, NHRP_CACHE_STATIC, 0,
+ nhrp_peer_get(ifp, &nbma_addr), 0, NULL);
+ }
+
+ return CMD_SUCCESS;
+}
+
+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"
+ "IPv4 NBMA address\n"
+ "Fully qualified domain name for NBMA address(es)\n")
+{
+ struct interface *ifp = vty->index;
+ afi_t afi = cmd_to_afi(argv[0]);
+ union sockunion proto_addr;
+ int ret;
+
+ if (str2sockunion(argv[1], &proto_addr) < 0)
+ sockunion_family(&proto_addr) = AF_UNSPEC;
+
+ ret = nhrp_nhs_add(ifp, afi, &proto_addr, argv[2]);
+ 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"
+ "IPv4 NBMA address\n"
+ "Fully qualified domain name for NBMA address(es)\n")
+{
+ struct interface *ifp = vty->index;
+ afi_t afi = cmd_to_afi(argv[0]);
+ union sockunion proto_addr;
+ int ret;
+
+ if (str2sockunion(argv[1], &proto_addr) < 0)
+ sockunion_family(&proto_addr) = AF_UNSPEC;
+
+ ret = nhrp_nhs_del(ifp, afi, &proto_addr, argv[2]);
+ return nhrp_vty_return(vty, ret);
+}
+
+struct info_ctx {
+ struct vty *vty;
+ afi_t afi;
+ int count;
+};
+
+static void show_ip_nhrp_cache(struct nhrp_cache *c, void *pctx)
+{
+ struct info_ctx *ctx = pctx;
+ struct vty *vty = ctx->vty;
+ char buf[2][SU_ADDRSTRLEN];
+
+ if (ctx->afi != family2afi(sockunion_family(&c->remote_addr)))
+ return;
+
+ if (!ctx->count) {
+ vty_out(vty, "%-8s %-8s %-24s %-24s %-6s %s%s",
+ "Iface",
+ "Type",
+ "Protocol",
+ "NBMA",
+ "Flags",
+ "Identity",
+ VTY_NEWLINE);
+ }
+ ctx->count++;
+
+ vty_out(ctx->vty, "%-8s %-8s %-24s %-24s %c%c%c %s%s",
+ c->ifp->name,
+ nhrp_cache_type_str[c->cur.type],
+ sockunion2str(&c->remote_addr, buf[0], sizeof buf[0]),
+ c->cur.peer ? sockunion2str(&c->cur.peer->vc->remote.nbma, buf[1], sizeof buf[1]) : "-",
+ c->used ? 'U' : ' ',
+ c->t_timeout ? 'T' : ' ',
+ c->t_auth ? 'A' : ' ',
+ c->cur.peer ? c->cur.peer->vc->remote.id : "-",
+ VTY_NEWLINE);
+}
+
+static void show_ip_opennhrp_cache(struct nhrp_cache *c, void *pctx)
+{
+ struct info_ctx *ctx = pctx;
+ struct vty *vty = ctx->vty;
+ char buf[SU_ADDRSTRLEN];
+
+ if (ctx->afi != family2afi(sockunion_family(&c->remote_addr)))
+ return;
+
+ vty_out(ctx->vty,
+ "Type: %s%s"
+ "Flags:%s%s%s"
+ "Protocol-Address: %s/%zu%s",
+ nhrp_cache_type_str[c->cur.type],
+ VTY_NEWLINE,
+ (c->cur.peer && c->cur.peer->online) ? " up": "",
+ c->used ? " used": "",
+ VTY_NEWLINE,
+ sockunion2str(&c->remote_addr, buf, sizeof buf),
+ 8 * family2addrsize(sockunion_family(&c->remote_addr)),
+ VTY_NEWLINE);
+
+ if (c->cur.peer) {
+ vty_out(ctx->vty,
+ "NBMA-Address: %s%s",
+ sockunion2str(&c->cur.peer->vc->remote.nbma, buf, sizeof buf),
+ VTY_NEWLINE);
+ }
+
+ if (sockunion_family(&c->cur.remote_nbma_natoa) != AF_UNSPEC) {
+ vty_out(ctx->vty,
+ "NBMA-NAT-OA-Address: %s%s",
+ sockunion2str(&c->cur.remote_nbma_natoa, buf, sizeof buf),
+ VTY_NEWLINE);
+ }
+
+ vty_out(ctx->vty, "%s", VTY_NEWLINE);
+}
+
+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];
+
+ if (!ctx->count) {
+ vty_out(vty, "%-8s %-24s %-24s %s%s",
+ "Type",
+ "Prefix",
+ "Via",
+ "Identity",
+ VTY_NEWLINE);
+ }
+ ctx->count++;
+
+ c = s->cache;
+ vty_out(ctx->vty, "%-8s %-24s %-24s %s%s",
+ nhrp_cache_type_str[s->type],
+ prefix2str(s->p, buf1, sizeof buf1),
+ c ? sockunion2str(&c->remote_addr, buf2, sizeof buf2) : "",
+ (c && c->cur.peer) ? c->cur.peer->vc->remote.id : "",
+ VTY_NEWLINE);
+}
+
+DEFUN(show_ip_nhrp, show_ip_nhrp_cmd,
+ "show " AFI_CMD " nhrp (cache|shortcut|opennhrp|)",
+ SHOW_STR
+ AFI_STR
+ "NHRP information\n"
+ "Forwarding cache information\n"
+ "Shortcut information\n"
+ "opennhrpctl style cache dump\n")
+{
+ struct listnode *node;
+ struct interface *ifp;
+ struct info_ctx ctx = {
+ .vty = vty,
+ .afi = cmd_to_afi(argv[0]),
+ };
+
+ if (!argv[1] || argv[1][0] == 'c') {
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp))
+ nhrp_cache_foreach(ifp, show_ip_nhrp_cache, &ctx);
+ } else if (argv[1][0] == 'o') {
+ vty_out(vty, "Status: ok%s%s", VTY_NEWLINE, VTY_NEWLINE);
+ ctx.count++;
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp))
+ nhrp_cache_foreach(ifp, show_ip_opennhrp_cache, &ctx);
+ } else {
+ nhrp_shortcut_foreach(ctx.afi, show_ip_nhrp_shortcut, &ctx);
+ }
+
+ if (!ctx.count) {
+ vty_out(vty, "%% No entries%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+static void show_dmvpn_entry(struct nhrp_vc *vc, void *ctx)
+{
+ struct vty *vty = ctx;
+ char buf[2][SU_ADDRSTRLEN];
+
+ vty_out(vty, "%-24s %-24s %c %-4d %-24s%s",
+ sockunion2str(&vc->local.nbma, buf[0], sizeof buf[0]),
+ sockunion2str(&vc->remote.nbma, buf[1], sizeof buf[1]),
+ notifier_active(&vc->notifier_list) ? 'n' : ' ',
+ vc->ipsec,
+ vc->remote.id,
+ VTY_NEWLINE);
+}
+
+DEFUN(show_dmvpn, show_dmvpn_cmd,
+ "show dmvpn",
+ SHOW_STR
+ "DMVPN information\n")
+{
+ vty_out(vty, "%-24s %-24s %-6s %-4s %-24s%s",
+ "Src",
+ "Dst",
+ "Flags",
+ "SAs",
+ "Identity",
+ VTY_NEWLINE);
+
+ nhrp_vc_foreach(show_dmvpn_entry, vty);
+
+ 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_CACHED) {
+ nhrp_cache_update_binding(c, c->cur.type, -1, NULL, 0, NULL);
+ 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 listnode *node;
+ struct interface *ifp;
+ struct info_ctx ctx = {
+ .vty = vty,
+ .afi = cmd_to_afi(argv[0]),
+ .count = 0,
+ };
+
+ if (!argv[1] || argv[1][0] == 'c') {
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp))
+ nhrp_cache_foreach(ifp, clear_nhrp_cache, &ctx);
+ } else {
+ nhrp_shortcut_foreach(ctx.afi, clear_nhrp_shortcut, &ctx);
+ }
+
+ if (!ctx.count) {
+ vty_out(vty, "%% No entries%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vty_out(vty, "%% %d entries cleared%s", ctx.count, VTY_NEWLINE);
+ 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 *c, void *data)
+{
+ struct write_map_ctx *ctx = data;
+ struct vty *vty = ctx->vty;
+ char buf[2][SU_ADDRSTRLEN];
+
+ if (!c->map) return;
+ if (sockunion_family(&c->remote_addr) != ctx->family) return;
+
+ vty_out(vty, " %s nhrp map %s %s%s",
+ ctx->aficmd,
+ sockunion2str(&c->remote_addr, buf[0], sizeof buf[0]),
+ c->cur.type == NHRP_CACHE_LOCAL ? "local" :
+ sockunion2str(&c->cur.peer->vc->remote.nbma, buf[1], sizeof buf[1]),
+ VTY_NEWLINE);
+}
+
+static int interface_config_write(struct vty *vty)
+{
+ struct write_map_ctx mapctx;
+ struct listnode *node;
+ struct interface *ifp;
+ struct nhrp_interface *nifp;
+ struct nhrp_nhs *nhs;
+ const char *aficmd;
+ afi_t afi;
+ char buf[SU_ADDRSTRLEN];
+ int i;
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+ vty_out(vty, "interface %s%s", ifp->name, VTY_NEWLINE);
+ if (ifp->desc)
+ vty_out(vty, " description %s%s", ifp->desc, VTY_NEWLINE);
+
+ 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, "%s", VTY_NEWLINE);
+ }
+ if (nifp->source)
+ vty_out(vty, " tunnel source %s%s",
+ nifp->source, VTY_NEWLINE);
+
+ 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%s",
+ aficmd, ad->network_id,
+ VTY_NEWLINE);
+
+ if (ad->holdtime != NHRPD_DEFAULT_HOLDTIME)
+ vty_out(vty, " %s nhrp holdtime %u%s",
+ aficmd, ad->holdtime,
+ VTY_NEWLINE);
+
+ if (ad->configured_mtu < 0)
+ vty_out(vty, " %s nhrp mtu opennhrp%s",
+ aficmd, VTY_NEWLINE);
+ else if (ad->configured_mtu)
+ vty_out(vty, " %s nhrp mtu %u%s",
+ aficmd, ad->configured_mtu,
+ VTY_NEWLINE);
+
+ 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%s",
+ aficmd, interface_flags_desc[i].str, VTY_NEWLINE);
+ }
+
+ mapctx = (struct write_map_ctx) {
+ .vty = vty,
+ .family = afi2family(afi),
+ .aficmd = aficmd,
+ };
+ nhrp_cache_foreach(ifp, interface_config_write_nhrp_map, &mapctx);
+
+ list_for_each_entry(nhs, &ad->nhslist_head, nhslist_entry) {
+ vty_out(vty, " %s nhrp nhs %s nbma %s%s",
+ aficmd,
+ sockunion_family(&nhs->proto_addr) == AF_UNSPEC ? "dynamic" : sockunion2str(&nhs->proto_addr, buf, sizeof buf),
+ nhs->nbma_fqdn,
+ VTY_NEWLINE);
+ }
+ }
+
+ vty_out (vty, "!%s", VTY_NEWLINE);
+ }
+
+ return 0;
+}
+
+void nhrp_config_init(void)
+{
+ install_node(&zebra_node, nhrp_config_write);
+ install_default(ZEBRA_NODE);
+
+ /* global commands */
+ install_element(VIEW_NODE, &show_debugging_nhrp_cmd);
+ install_element(VIEW_NODE, &show_ip_nhrp_cmd);
+ install_element(VIEW_NODE, &show_dmvpn_cmd);
+ install_element(ENABLE_NODE, &show_debugging_nhrp_cmd);
+ install_element(ENABLE_NODE, &show_ip_nhrp_cmd);
+ install_element(ENABLE_NODE, &show_dmvpn_cmd);
+ install_element(ENABLE_NODE, &clear_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);
+
+ /* interface specific commands */
+ install_node(&nhrp_interface_node, interface_config_write);
+ install_default(INTERFACE_NODE);
+
+ install_element(CONFIG_NODE, &interface_cmd);
+ install_element(CONFIG_NODE, &no_interface_cmd);
+ install_element(INTERFACE_NODE, &interface_cmd);
+ install_element(INTERFACE_NODE, &no_interface_cmd);
+ 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_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 00000000..307546e0
--- /dev/null
+++ b/nhrpd/nhrpd.h
@@ -0,0 +1,400 @@
+/* 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 "list.h"
+
+#include "zbuf.h"
+#include "zclient.h"
+#include "debug.h"
+
+#define NHRPD_DEFAULT_HOLDTIME 7200
+
+#define NHRP_VTY_PORT 2612
+#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,
+};
+
+struct notifier_block;
+
+typedef void (*notifier_fn_t)(struct notifier_block *, unsigned long);
+
+struct notifier_block {
+ struct list_head notifier_entry;
+ notifier_fn_t action;
+};
+
+struct notifier_list {
+ struct list_head notifier_head;
+};
+
+#define NOTIFIER_LIST_INITIALIZER(l) \
+ { .notifier_head = LIST_INITIALIZER((l)->notifier_head) }
+
+static inline void notifier_init(struct notifier_list *l)
+{
+ list_init(&l->notifier_head);
+}
+
+static inline void notifier_add(struct notifier_block *n, struct notifier_list *l, notifier_fn_t action)
+{
+ n->action = action;
+ list_add_tail(&n->notifier_entry, &l->notifier_head);
+}
+
+static inline void notifier_del(struct notifier_block *n)
+{
+ list_del(&n->notifier_entry);
+}
+
+static inline void notifier_call(struct notifier_list *l, int cmd)
+{
+ struct notifier_block *n, *nn;
+ list_for_each_entry_safe(n, nn, &l->notifier_head, notifier_entry)
+ n->action(n, cmd);
+}
+
+static inline int notifier_active(struct notifier_list *l)
+{
+ return !list_empty(&l->notifier_head);
+}
+
+struct resolver_query {
+ void (*callback)(struct resolver_query *, int n, union sockunion *);
+};
+
+void resolver_init(void);
+void resolver_resolve(struct resolver_query *query, int af, const char *hostname, void (*cb)(struct resolver_query *, int, union sockunion *));
+
+void nhrp_zebra_init(void);
+void nhrp_zebra_terminate(void);
+
+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_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;
+ uint8_t ipsec;
+ 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 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 {
+ 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;
+ struct nhrp_peer *peer;
+ time_t expires;
+ uint32_t mtu;
+ } 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;
+};
+
+struct nhrp_nhs {
+ struct interface *ifp;
+ struct list_head 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 list_head reglist_head;
+};
+
+#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 linkidx;
+ uint32_t grekey;
+
+ struct hash *peer_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 list_head nhslist_head;
+ } afi[AFI_MAX];
+};
+
+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);
+
+int nhrp_interface_add(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id);
+int nhrp_interface_delete(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id);
+int nhrp_interface_up(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id);
+int nhrp_interface_down(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id);
+int nhrp_interface_address_add(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id);
+int nhrp_interface_address_delete(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id);
+
+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);
+
+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_nhs *nhs);
+void nhrp_nhs_terminate(void);
+
+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(int command, struct zclient *zclient, zebra_size_t length, vrf_id_t vrf_id);
+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);
+
+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_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);
+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_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);
+
+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 *);
+
+#endif
diff --git a/nhrpd/os.h b/nhrpd/os.h
new file mode 100644
index 00000000..0fbe8b00
--- /dev/null
+++ b/nhrpd/os.h
@@ -0,0 +1,5 @@
+
+int os_socket(void);
+int os_sendmsg(const uint8_t *buf, size_t len, int ifindex, const uint8_t *addr, size_t addrlen);
+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 00000000..24b31993
--- /dev/null
+++ b/nhrpd/reqid.c
@@ -0,0 +1,49 @@
+#include "zebra.h"
+#include "hash.h"
+#include "nhrpd.h"
+
+static unsigned int nhrp_reqid_key(void *data)
+{
+ struct nhrp_reqid *r = data;
+ return r->request_id;
+}
+
+static int 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);
+ 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;
+ 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/resolver.c b/nhrpd/resolver.c
new file mode 100644
index 00000000..07bdb735
--- /dev/null
+++ b/nhrpd/resolver.c
@@ -0,0 +1,190 @@
+/* C-Ares integration to Quagga mainloop
+ * 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 <ares.h>
+#include <ares_version.h>
+
+#include "vector.h"
+#include "thread.h"
+#include "nhrpd.h"
+
+struct resolver_state {
+ ares_channel channel;
+ struct thread *timeout;
+ vector read_threads, write_threads;
+};
+
+static struct resolver_state state;
+
+#define THREAD_RUNNING ((struct thread *)-1)
+
+static void resolver_update_timeouts(struct resolver_state *r);
+
+static int resolver_cb_timeout(struct thread *t)
+{
+ struct resolver_state *r = THREAD_ARG(t);
+
+ r->timeout = THREAD_RUNNING;
+ ares_process(r->channel, NULL, NULL);
+ r->timeout = NULL;
+ resolver_update_timeouts(r);
+
+ return 0;
+}
+
+static int resolver_cb_socket_readable(struct thread *t)
+{
+ struct resolver_state *r = THREAD_ARG(t);
+ int fd = THREAD_FD(t);
+
+ vector_set_index(r->read_threads, fd, THREAD_RUNNING);
+ ares_process_fd(r->channel, fd, ARES_SOCKET_BAD);
+ if (vector_lookup(r->read_threads, fd) == THREAD_RUNNING) {
+ t = NULL;
+ THREAD_READ_ON(master, t, resolver_cb_socket_readable, r, fd);
+ vector_set_index(r->read_threads, fd, t);
+ }
+ resolver_update_timeouts(r);
+
+ return 0;
+}
+
+static int resolver_cb_socket_writable(struct thread *t)
+{
+ struct resolver_state *r = THREAD_ARG(t);
+ int fd = THREAD_FD(t);
+
+ vector_set_index(r->write_threads, fd, THREAD_RUNNING);
+ ares_process_fd(r->channel, ARES_SOCKET_BAD, fd);
+ if (vector_lookup(r->write_threads, fd) == THREAD_RUNNING) {
+ t = NULL;
+ THREAD_WRITE_ON(master, t, resolver_cb_socket_writable, r, fd);
+ vector_set_index(r->write_threads, fd, t);
+ }
+ resolver_update_timeouts(r);
+
+ return 0;
+}
+
+static void resolver_update_timeouts(struct resolver_state *r)
+{
+ struct timeval *tv, tvbuf;
+
+ if (r->timeout == THREAD_RUNNING) return;
+
+ THREAD_OFF(r->timeout);
+ tv = ares_timeout(r->channel, NULL, &tvbuf);
+ if (tv) {
+ unsigned int timeoutms = tv->tv_sec * 1000 + tv->tv_usec / 1000;
+ THREAD_TIMER_MSEC_ON(master, r->timeout, resolver_cb_timeout, r, timeoutms);
+ }
+}
+
+static void ares_socket_cb(void *data, ares_socket_t fd, int readable, int writable)
+{
+ struct resolver_state *r = (struct resolver_state *) data;
+ struct thread *t;
+
+ if (readable) {
+ t = vector_lookup_ensure(r->read_threads, fd);
+ if (!t) {
+ THREAD_READ_ON(master, t, resolver_cb_socket_readable, r, fd);
+ vector_set_index(r->read_threads, fd, t);
+ }
+ } else {
+ t = vector_lookup(r->read_threads, fd);
+ if (t) {
+ if (t != THREAD_RUNNING) {
+ THREAD_OFF(t);
+ }
+ vector_unset(r->read_threads, fd);
+ }
+ }
+
+ if (writable) {
+ t = vector_lookup_ensure(r->write_threads, fd);
+ if (!t) {
+ THREAD_READ_ON(master, t, resolver_cb_socket_writable, r, fd);
+ vector_set_index(r->write_threads, fd, t);
+ }
+ } else {
+ t = vector_lookup(r->write_threads, fd);
+ if (t) {
+ if (t != THREAD_RUNNING) {
+ THREAD_OFF(t);
+ }
+ vector_unset(r->write_threads, fd);
+ }
+ }
+}
+
+void resolver_init(void)
+{
+ struct ares_options ares_opts;
+
+ state.read_threads = vector_init(1);
+ state.write_threads = vector_init(1);
+
+ ares_opts = (struct ares_options) {
+ .sock_state_cb = &ares_socket_cb,
+ .sock_state_cb_data = &state,
+ .timeout = 2,
+ .tries = 3,
+ };
+
+ ares_init_options(&state.channel, &ares_opts,
+ ARES_OPT_SOCK_STATE_CB | ARES_OPT_TIMEOUT |
+ ARES_OPT_TRIES);
+}
+
+
+static void ares_address_cb(void *arg, int status, int timeouts, struct hostent *he)
+{
+ struct resolver_query *query = (struct resolver_query *) arg;
+ union sockunion addr[16];
+ size_t i;
+
+ if (status != ARES_SUCCESS) {
+ debugf(NHRP_DEBUG_COMMON, "[%p] Resolving failed", query);
+ query->callback(query, -1, NULL);
+ query->callback = NULL;
+ return;
+ }
+
+ for (i = 0; he->h_addr_list[i] != NULL && i < ZEBRA_NUM_OF(addr); i++) {
+ memset(&addr[i], 0, sizeof(addr[i]));
+ addr[i].sa.sa_family = he->h_addrtype;
+ switch (he->h_addrtype) {
+ case AF_INET:
+ memcpy(&addr[i].sin.sin_addr, (uint8_t *) he->h_addr_list[i], he->h_length);
+ break;
+ case AF_INET6:
+ memcpy(&addr[i].sin6.sin6_addr, (uint8_t *) he->h_addr_list[i], he->h_length);
+ break;
+ }
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "[%p] Resolved with %d results", query, (int) i);
+ query->callback(query, i, &addr[0]);
+ query->callback = NULL;
+}
+
+void resolver_resolve(struct resolver_query *query, int af, const char *hostname, void (*callback)(struct resolver_query *, int, union sockunion *))
+{
+ if (query->callback != NULL) {
+ zlog_err("Trying to resolve '%s', but previous query was not finished yet", hostname);
+ return;
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "[%p] Resolving '%s'", query, hostname);
+
+ query->callback = callback;
+ ares_gethostbyname(state.channel, hostname, af, ares_address_cb, query);
+ resolver_update_timeouts(&state);
+}
diff --git a/nhrpd/vici.c b/nhrpd/vici.c
new file mode 100644
index 00000000..507dd14a
--- /dev/null
+++ b/nhrpd/vici.c
@@ -0,0 +1,482 @@
+/* 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.
+ */
+
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "thread.h"
+#include "zbuf.h"
+#include "log.h"
+#include "nhrpd.h"
+
+#include "vici.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->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->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 int 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_TIMER_ON(master, vici->t_reconnect, vici_reconnect, vici, 2);
+}
+
+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;
+ struct blob val;
+
+ 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);
+ }
+ } else {
+ nhrp_vc_ipsec_updown(sactx->child_uniqueid, 0);
+ }
+ }
+ break;
+ default:
+ switch (key->ptr[0]) {
+ case 'l':
+ if (blob_equal(key, "local-host") && ctx->nsections == 1) {
+ if (blob2buf(val, buf, sizeof(buf)))
+ str2sockunion(buf, &sactx->local.host);
+ } 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)))
+ str2sockunion(buf, &sactx->remote.host);
+ } 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 vici_recv_sa(struct vici_conn *vici, struct zbuf *msg, int event)
+{
+ char buf[32];
+ struct handle_sa_ctx ctx = {
+ .event = event,
+ };
+
+ 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;
+
+ 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_EVENT_UNKNOWN:
+ zlog_err("VICI: StrongSwan does not support mandatory events (unpatched?)");
+ break;
+ case VICI_EVENT_CONFIRM:
+ case VICI_CMD_RESPONSE:
+ break;
+ default:
+ zlog_notice("VICI: Unrecognized message type %d", msgtype);
+ break;
+ }
+}
+
+static int vici_read(struct thread *t)
+{
+ struct vici_conn *vici = THREAD_ARG(t);
+ struct zbuf *ibuf = &vici->ibuf;
+ struct zbuf pktbuf;
+
+ vici->t_read = NULL;
+ if (zbuf_read(ibuf, vici->fd, (size_t) -1) < 0) {
+ vici_connection_error(vici);
+ return 0;
+ }
+
+ /* 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_READ_ON(master, vici->t_read, vici_read, vici, vici->fd);
+ return 0;
+}
+
+static int vici_write(struct thread *t)
+{
+ struct vici_conn *vici = THREAD_ARG(t);
+ int r;
+
+ vici->t_write = NULL;
+ r = zbufq_write(&vici->obuf, vici->fd);
+ if (r > 0) {
+ THREAD_WRITE_ON(master, vici->t_write, vici_write, vici, vici->fd);
+ } else if (r < 0) {
+ vici_connection_error(vici);
+ }
+
+ return 0;
+}
+
+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_WRITE_ON(master, vici->t_write, vici_write, vici, vici->fd);
+}
+
+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;
+ case VICI_END:
+ 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 int vici_reconnect(struct thread *t)
+{
+ struct vici_conn *vici = THREAD_ARG(t);
+ int fd;
+
+ vici->t_reconnect = NULL;
+ if (vici->fd >= 0) return 0;
+
+ fd = sock_open_unix("/var/run/charon.vici");
+ if (fd < 0) {
+ zlog_warn("%s: failure connecting VICI socket: %s",
+ __PRETTY_FUNCTION__, strerror(errno));
+ THREAD_TIMER_ON(master, vici->t_reconnect, vici_reconnect, vici, 2);
+ return 0;
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "VICI: Connected");
+ vici->fd = fd;
+ THREAD_READ_ON(master, vici->t_read, vici_read, vici, vici->fd);
+
+ /* 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);
+
+ return 0;
+}
+
+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_TIMER_MSEC_ON(master, vici->t_reconnect, vici_reconnect, vici, 10);
+}
+
+void vici_terminate(void)
+{
+}
+
+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", 2, "-1",
+ VICI_KEY_VALUE, "async", 1, "1",
+ VICI_KEY_VALUE, "init-limits", 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 (struct sockaddr_un));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, path, strlen (path));
+
+ ret = connect(fd, (struct sockaddr *) &addr, sizeof(addr.sun_family) + strlen(addr.sun_path));
+ if (ret < 0) {
+ close(fd);
+ return -1;
+ }
+
+ fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
+
+ return fd;
+}
diff --git a/nhrpd/vici.h b/nhrpd/vici.h
new file mode 100644
index 00000000..24b900b4
--- /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 00000000..ead7cfd2
--- /dev/null
+++ b/nhrpd/zbuf.c
@@ -0,0 +1,219 @@
+/* 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.
+ */
+
+#define _GNU_SOURCE
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include "zassert.h"
+#include "zbuf.h"
+#include "memory.h"
+#include "memtypes.h"
+#include "nhrpd.h"
+
+#define ERRNO_IO_RETRY(EN) (((EN) == EAGAIN) || ((EN) == EWOULDBLOCK) || ((EN) == EINTR))
+
+struct zbuf *zbuf_alloc(size_t size)
+{
+ struct zbuf *zb;
+
+ zb = XMALLOC(MTYPE_STREAM_DATA, sizeof(*zb) + size);
+ if (!zb)
+ return NULL;
+
+ 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_STREAM_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)
+{
+ zassert((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 (r < 0 && 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 (r < 0 && 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 (r < 0 && 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 = LIST_INITIALIZER(zbq->queue_head),
+ };
+}
+
+void zbufq_reset(struct zbuf_queue *zbq)
+{
+ struct zbuf *buf, *bufn;
+
+ list_for_each_entry_safe(buf, bufn, &zbq->queue_head, queue_list) {
+ list_del(&buf->queue_list);
+ zbuf_free(buf);
+ }
+}
+
+void zbufq_queue(struct zbuf_queue *zbq, struct zbuf *zb)
+{
+ list_add_tail(&zb->queue_list, &zbq->queue_head);
+}
+
+int zbufq_write(struct zbuf_queue *zbq, int fd)
+{
+ struct iovec iov[16];
+ struct zbuf *zb, *zbn;
+ ssize_t r;
+ size_t iovcnt = 0;
+
+ list_for_each_entry_safe(zb, zbn, &zbq->queue_head, queue_list) {
+ iov[iovcnt++] = (struct iovec) {
+ .iov_base = zb->head,
+ .iov_len = zbuf_used(zb),
+ };
+ if (iovcnt >= ZEBRA_NUM_OF(iov))
+ break;
+ }
+
+ r = writev(fd, iov, iovcnt);
+ if (r < 0)
+ return r;
+
+ list_for_each_entry_safe(zb, zbn, &zbq->queue_head, queue_list) {
+ if (r < (ssize_t)zbuf_used(zb)) {
+ zb->head += r;
+ return 1;
+ }
+
+ r -= zbuf_used(zb);
+ list_del(&zb->queue_list);
+ 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);
+}
diff --git a/nhrpd/zbuf.h b/nhrpd/zbuf.h
new file mode 100644
index 00000000..73d70734
--- /dev/null
+++ b/nhrpd/zbuf.h
@@ -0,0 +1,189 @@
+/* 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 "zassert.h"
+#include "list.h"
+
+struct zbuf {
+ struct list_head queue_list;
+ unsigned allocated : 1;
+ unsigned error : 1;
+ uint8_t *buf, *end;
+ uint8_t *head, *tail;
+};
+
+struct zbuf_queue {
+ struct list_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) ((void *)__zbuf_pull(zb, sz, 1))
+#define zbuf_may_pull(zb, type) ((type *)__zbuf_pull(zb, sizeof(type), 0))
+#define zbuf_may_pulln(zb, sz) ((void *)__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) ((void *)__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) ((void *)__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 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 00000000..2216d97e
--- /dev/null
+++ b/nhrpd/znl.c
@@ -0,0 +1,160 @@
+/* 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 <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;
+
+ fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+ 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 00000000..2cd630b5
--- /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);
+
diff --git a/vtysh/Makefile.am b/vtysh/Makefile.am
index e44cd49f..983103ff 100644
--- a/vtysh/Makefile.am
+++ b/vtysh/Makefile.am
@@ -24,6 +24,7 @@ vtysh_cmd_FILES = $(top_srcdir)/bgpd/*.c $(top_srcdir)/isisd/*.c \
$(top_srcdir)/ospfd/*.c $(top_srcdir)/ospf6d/*.c \
$(top_srcdir)/ripd/*.c $(top_srcdir)/ripngd/*.c \
$(top_srcdir)/pimd/pim_cmd.c \
+ $(top_srcdir)/nhrpd/nhrp_vty.c \
$(top_srcdir)/lib/keychain.c $(top_srcdir)/lib/routemap.c \
$(top_srcdir)/lib/filter.c $(top_srcdir)/lib/plist.c \
$(top_srcdir)/lib/distribute.c $(top_srcdir)/lib/if_rmap.c \
diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c
index 2e2203da..1ee8582a 100644
--- a/vtysh/vtysh.c
+++ b/vtysh/vtysh.c
@@ -60,6 +60,7 @@ struct vtysh_client
{ .fd = -1, .name = "bgpd", .flag = VTYSH_BGPD, .path = BGP_VTYSH_PATH},
{ .fd = -1, .name = "isisd", .flag = VTYSH_ISISD, .path = ISIS_VTYSH_PATH},
{ .fd = -1, .name = "pimd", .flag = VTYSH_PIMD, .path = PIM_VTYSH_PATH},
+ { .fd = -1, .name = "nhrpd", .flag = VTYSH_NHRPD, .path = NHRP_VTYSH_PATH},
};


diff --git a/vtysh/vtysh.h b/vtysh/vtysh.h
index 1681a71a..02ff7fd7 100644
--- a/vtysh/vtysh.h
+++ b/vtysh/vtysh.h
@@ -31,9 +31,10 @@
#define VTYSH_ISISD 0x40
#define VTYSH_BABELD 0x80
#define VTYSH_PIMD 0x100
-#define VTYSH_ALL VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_ISISD|VTYSH_BABELD|VTYSH_PIMD
+#define VTYSH_NHRPD 0x200
+#define VTYSH_ALL VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_ISISD|VTYSH_BABELD|VTYSH_PIMD|VTYSH_NHRPD
#define VTYSH_RMAP VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_BABELD
-#define VTYSH_INTERFACE VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_ISISD|VTYSH_BABELD|VTYSH_PIMD
+#define VTYSH_INTERFACE VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_ISISD|VTYSH_BABELD|VTYSH_PIMD|VTYSH_NHRPD

/* vtysh local configuration file. */
#define VTYSH_DEFAULT_CONFIG "vtysh.conf"
diff --git a/zebra/client_main.c b/zebra/client_main.c
index 43ab2997..75c88677 100644
--- a/zebra/client_main.c
+++ b/zebra/client_main.c
@@ -120,6 +120,7 @@ struct zebra_info
{ "ospf", ZEBRA_ROUTE_OSPF },
{ "ospf6", ZEBRA_ROUTE_OSPF6 },
{ "bgp", ZEBRA_ROUTE_BGP },
+ { "nhrp", ZEBRA_ROUTE_NHRP },
{ NULL, 0 }
};

diff --git a/zebra/zebra_rib.c b/zebra/zebra_rib.c
index abb9560a..b03ccc2f 100644
--- a/zebra/zebra_rib.c
+++ b/zebra/zebra_rib.c
@@ -72,6 +72,7 @@ static const struct
[ZEBRA_ROUTE_ISIS] = {ZEBRA_ROUTE_ISIS, 115},
[ZEBRA_ROUTE_BGP] = {ZEBRA_ROUTE_BGP, 20 /* IBGP is 200. */},
[ZEBRA_ROUTE_BABEL] = {ZEBRA_ROUTE_BABEL, 95},
+ [ZEBRA_ROUTE_NHRP] = {ZEBRA_ROUTE_NHRP, 10},
/* no entry/default: 150 */
};

@@ -1423,6 +1424,7 @@ static const u_char meta_queue_map[ZEBRA_ROUTE_MAX] = {
[ZEBRA_ROUTE_BGP] = 3,
[ZEBRA_ROUTE_HSLS] = 4,
[ZEBRA_ROUTE_BABEL] = 2,
+ [ZEBRA_ROUTE_NHRP] = 2,
};

/* Look into the RN and queue it into one or more priority queues,
diff --git a/zebra/zebra_rnh.c b/zebra/zebra_rnh.c
index 5762d3f2..859b6d79 100644
--- a/zebra/zebra_rnh.c
+++ b/zebra/zebra_rnh.c
@@ -223,16 +223,27 @@ zebra_evaluate_rnh_table (vrf_id_t vrfid, int family)
{
if (CHECK_FLAG (rib->status, RIB_ENTRY_REMOVED))
continue;
- if (CHECK_FLAG (rib->status, RIB_ENTRY_SELECTED_FIB))
+ if (! CHECK_FLAG (rib->status, RIB_ENTRY_SELECTED_FIB))
+ continue;
+
+ if (CHECK_FLAG(rnh->flags, ZEBRA_NHT_CONNECTED))
{
- if (CHECK_FLAG(rnh->flags, ZEBRA_NHT_CONNECTED))
+ if (rib->type == ZEBRA_ROUTE_CONNECT)
+ break;
+
+ if (rib->type == ZEBRA_ROUTE_NHRP)
{
- if (rib->type == ZEBRA_ROUTE_CONNECT)
+ struct nexthop *nexthop;
+ for (nexthop = rib->nexthop; nexthop; nexthop = nexthop->next)
+ if (nexthop->type == NEXTHOP_TYPE_IFINDEX ||
+ nexthop->type == NEXTHOP_TYPE_IFNAME)
+ break;
+ if (nexthop)
break;
}
- else
- break;
}
+ else
+ break;
}
}

diff --git a/zebra/zebra_vty.c b/zebra/zebra_vty.c
index 38f61e9d..c73896b3 100644
--- a/zebra/zebra_vty.c
+++ b/zebra/zebra_vty.c
@@ -2121,6 +2121,7 @@ vty_show_ip_route_detail (struct vty *vty, struct route_node *rn, int mcast)
|| rib->type == ZEBRA_ROUTE_OSPF6
|| rib->type == ZEBRA_ROUTE_BABEL
|| rib->type == ZEBRA_ROUTE_ISIS
+ || rib->type == ZEBRA_ROUTE_NHRP
|| rib->type == ZEBRA_ROUTE_BGP)
{
time_t uptime;
@@ -2341,6 +2342,7 @@ vty_show_ip_route (struct vty *vty, struct route_node *rn, struct rib *rib)
|| rib->type == ZEBRA_ROUTE_OSPF6
|| rib->type == ZEBRA_ROUTE_BABEL
|| rib->type == ZEBRA_ROUTE_ISIS
+ || rib->type == ZEBRA_ROUTE_NHRP
|| rib->type == ZEBRA_ROUTE_BGP)
{
time_t uptime;
--
2.11.0
Vincent JARDIN
2017-01-12 15:58:13 UTC
Permalink
Post by Timo Teräs
This provides DMVPN support and integrates to strongSwan. Please read
README.nhrpd and README.kernel for more details.
I do like the value that NHRP brings for DMVPN support. Sorry, I did not
review the code due to lack of time, but since it is quite independent
code, I feel it brings lot of value to start with.
Jafar Al-Gharaibeh
2017-01-20 14:44:23 UTC
Permalink
Post by Vincent JARDIN
Post by Timo Teräs
This provides DMVPN support and integrates to strongSwan. Please read
README.nhrpd and README.kernel for more details.
I do like the value that NHRP brings for DMVPN support. Sorry, I did
not review the code due to lack of time, but since it is quite
independent code, I feel it brings lot of value to start with.
+1

Timo, thanks a lot for this great work. I specifically like the level of
details you went through in the readme files! Kudos!

--Jafar
Timo Teräs
2017-01-19 15:27:01 UTC
Permalink
This provides DMVPN support and integrates to strongSwan. Please read
README.nhrpd and README.kernel for more details.
---
v4: added netlink_*.c and debug.h that are missing from v3
(names changed for the compile fix requiring file split)

.gitignore | 2 +
Makefile.am | 4 +-
SERVICES | 1 +
configure.ac | 27 +-
lib/log.c | 5 +
lib/log.h | 4 +-
lib/memtypes.c | 14 +
lib/route_types.txt | 2 +
nhrpd/Makefile.am | 35 ++
nhrpd/README.kernel | 145 ++++++++
nhrpd/README.nhrpd | 137 ++++++++
nhrpd/debug.h | 42 +++
nhrpd/linux.c | 153 ++++++++
nhrpd/list.h | 191 ++++++++++
nhrpd/netlink.h | 24 ++
nhrpd/netlink_arp.c | 275 +++++++++++++++
nhrpd/netlink_gre.c | 141 ++++++++
nhrpd/nhrp_cache.c | 341 ++++++++++++++++++
nhrpd/nhrp_event.c | 280 +++++++++++++++
nhrpd/nhrp_interface.c | 404 +++++++++++++++++++++
nhrpd/nhrp_main.c | 246 +++++++++++++
nhrpd/nhrp_nhs.c | 369 ++++++++++++++++++++
nhrpd/nhrp_packet.c | 312 +++++++++++++++++
nhrpd/nhrp_peer.c | 860 +++++++++++++++++++++++++++++++++++++++++++++
nhrpd/nhrp_protocol.h | 128 +++++++
nhrpd/nhrp_route.c | 345 ++++++++++++++++++
nhrpd/nhrp_shortcut.c | 402 +++++++++++++++++++++
nhrpd/nhrp_vc.c | 217 ++++++++++++
nhrpd/nhrp_vty.c | 928 +++++++++++++++++++++++++++++++++++++++++++++++++
nhrpd/nhrpd.h | 400 +++++++++++++++++++++
nhrpd/os.h | 5 +
nhrpd/reqid.c | 49 +++
nhrpd/resolver.c | 190 ++++++++++
nhrpd/vici.c | 482 +++++++++++++++++++++++++
nhrpd/vici.h | 24 ++
nhrpd/zbuf.c | 219 ++++++++++++
nhrpd/zbuf.h | 189 ++++++++++
nhrpd/znl.c | 160 +++++++++
nhrpd/znl.h | 29 ++
vtysh/Makefile.am | 1 +
vtysh/vtysh.c | 1 +
vtysh/vtysh.h | 5 +-
zebra/client_main.c | 1 +
zebra/zebra_rib.c | 2 +
zebra/zebra_rnh.c | 21 +-
zebra/zebra_vty.c | 2 +
46 files changed, 7803 insertions(+), 11 deletions(-)
create mode 100644 nhrpd/Makefile.am
create mode 100644 nhrpd/README.kernel
create mode 100644 nhrpd/README.nhrpd
create mode 100644 nhrpd/debug.h
create mode 100644 nhrpd/linux.c
create mode 100644 nhrpd/list.h
create mode 100644 nhrpd/netlink.h
create mode 100644 nhrpd/netlink_arp.c
create mode 100644 nhrpd/netlink_gre.c
create mode 100644 nhrpd/nhrp_cache.c
create mode 100644 nhrpd/nhrp_event.c
create mode 100644 nhrpd/nhrp_interface.c
create mode 100644 nhrpd/nhrp_main.c
create mode 100644 nhrpd/nhrp_nhs.c
create mode 100644 nhrpd/nhrp_packet.c
create mode 100644 nhrpd/nhrp_peer.c
create mode 100644 nhrpd/nhrp_protocol.h
create mode 100644 nhrpd/nhrp_route.c
create mode 100644 nhrpd/nhrp_shortcut.c
create mode 100644 nhrpd/nhrp_vc.c
create mode 100644 nhrpd/nhrp_vty.c
create mode 100644 nhrpd/nhrpd.h
create mode 100644 nhrpd/os.h
create mode 100644 nhrpd/reqid.c
create mode 100644 nhrpd/resolver.c
create mode 100644 nhrpd/vici.c
create mode 100644 nhrpd/vici.h
create mode 100644 nhrpd/zbuf.c
create mode 100644 nhrpd/zbuf.h
create mode 100644 nhrpd/znl.c
create mode 100644 nhrpd/znl.h

diff --git a/.gitignore b/.gitignore
index a281555e..a8da62f1 100644
--- a/.gitignore
+++ b/.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
diff --git a/Makefile.am b/Makefile.am
index 0cd75be8..3dea4898 100644
--- a/Makefile.am
+++ b/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

diff --git a/SERVICES b/SERVICES
index c69d0c1a..0322d451 100644
--- a/SERVICES
+++ b/SERVICES
@@ -18,3 +18,4 @@ ospf6d 2606/tcp
ospfapi 2607/tcp
isisd 2608/tcp
pimd 2611/tcp
+nhrpd 2612/tcp
diff --git a/configure.ac b/configure.ac
index ce1bb7ea..f8fc7eb9 100755
--- a/configure.ac
+++ b/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,
@@ -1186,6 +1189,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
@@ -1241,6 +1255,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)
@@ -1276,6 +1291,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
@@ -1551,6 +1574,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)
@@ -1561,6 +1585,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)
@@ -1588,7 +1613,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
diff --git a/lib/log.c b/lib/log.c
index 42b7717e..d4370669 100644
--- a/lib/log.c
+++ b/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;
}
diff --git a/lib/log.h b/lib/log.h
index 7aa0896b..59968aee 100644
--- a/lib/log.h
+++ b/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
diff --git a/lib/memtypes.c b/lib/memtypes.c
index 8abe99d2..ba2bacfa 100644
--- a/lib/memtypes.c
+++ b/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},
};
diff --git a/lib/route_types.txt b/lib/route_types.txt
index 1b856079..811d24e0 100644
--- a/lib/route_types.txt
+++ b/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)"
diff --git a/nhrpd/Makefile.am b/nhrpd/Makefile.am
new file mode 100644
index 00000000..00ecc7f3
--- /dev/null
+++ b/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
diff --git a/nhrpd/README.kernel b/nhrpd/README.kernel
new file mode 100644
index 00000000..5831316f
--- /dev/null
+++ b/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.
+
diff --git a/nhrpd/README.nhrpd b/nhrpd/README.nhrpd
new file mode 100644
index 00000000..569b3f44
--- /dev/null
+++ b/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
+
diff --git a/nhrpd/debug.h b/nhrpd/debug.h
new file mode 100644
index 00000000..b1f49aa8
--- /dev/null
+++ b/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
+
diff --git a/nhrpd/linux.c b/nhrpd/linux.c
new file mode 100644
index 00000000..1e9c69eb
--- /dev/null
+++ b/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;
+}
diff --git a/nhrpd/list.h b/nhrpd/list.h
new file mode 100644
index 00000000..32f21ed5
--- /dev/null
+++ b/nhrpd/list.h
@@ -0,0 +1,191 @@
+/* Linux kernel style list handling function
+ *
+ * Written from scratch by Timo Teräs <***@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
diff --git a/nhrpd/netlink.h b/nhrpd/netlink.h
new file mode 100644
index 00000000..f05596ba
--- /dev/null
+++ b/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);
diff --git a/nhrpd/netlink_arp.c b/nhrpd/netlink_arp.c
new file mode 100644
index 00000000..a418ecab
--- /dev/null
+++ b/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;
+}
diff --git a/nhrpd/netlink_gre.c b/nhrpd/netlink_gre.c
new file mode 100644
index 00000000..7cd30aa3
--- /dev/null
+++ b/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);
+}
diff --git a/nhrpd/nhrp_cache.c b/nhrpd/nhrp_cache.c
new file mode 100644
index 00000000..447a814c
--- /dev/null
+++ b/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);
+}
diff --git a/nhrpd/nhrp_event.c b/nhrpd/nhrp_event.c
new file mode 100644
index 00000000..aab9ec64
--- /dev/null
+++ b/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);
+}
+
diff --git a/nhrpd/nhrp_interface.c b/nhrpd/nhrp_interface.c
new file mode 100644
index 00000000..8118927a
--- /dev/null
+++ b/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);
+}
diff --git a/nhrpd/nhrp_main.c b/nhrpd/nhrp_main.c
new file mode 100644
index 00000000..29349a03
--- /dev/null
+++ b/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;
+}
diff --git a/nhrpd/nhrp_nhs.c b/nhrpd/nhrp_nhs.c
new file mode 100644
index 00000000..d463e062
--- /dev/null
+++ b/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;
+
+ if (sockunion_family(proto_addr) != AF_UNSPEC &&
+ sockunion_family(proto_addr) != afi2family(afi))
+ return NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH;
+
+ list_for_each_entry(nhs, &nifp->afi[afi].nhslist_head, nhslist_entry) {
+ 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));
+ if (!nhs) return NHRP_ERR_NO_MEMORY;
+
+ *nhs = (struct nhrp_nhs) {
+ .afi = afi,
+ .ifp = ifp,
+ .proto_addr = *proto_addr,
+ .nbma_fqdn = strdup(nbma_fqdn),
+ .reglist_head = LIST_INITIALIZER(nhs->reglist_head),
+ };
+ list_add_tail(&nhs->nhslist_entry, &nifp->afi[afi].nhslist_head);
+ THREAD_TIMER_MSEC_ON(master, nhs->t_resolve, nhrp_nhs_resolve, nhs, 1000);
+
+ 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, *nnhs;
+ 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;
+
+ list_for_each_entry_safe(nhs, nnhs, &nifp->afi[afi].nhslist_head, nhslist_entry) {
+ if (!sockunion_same(&nhs->proto_addr, proto_addr))
+ continue;
+ if (strcmp(nhs->nbma_fqdn, nbma_fqdn) != 0)
+ continue;
+
+ nhrp_nhs_free(nhs);
+ ret = NHRP_OK;
+ }
+
+ return ret;
+}
+
+int nhrp_nhs_free(struct nhrp_nhs *nhs)
+{
+ struct nhrp_registration *r, *rn;
+
+ list_for_each_entry_safe(r, rn, &nhs->reglist_head, reglist_entry)
+ nhrp_reg_delete(r);
+ THREAD_OFF(nhs->t_resolve);
+ list_del(&nhs->nhslist_entry);
+ free((void*) nhs->nbma_fqdn);
+ XFREE(MTYPE_NHRP_NHS, nhs);
+ return 0;
+}
+
+void nhrp_nhs_terminate(void)
+{
+ struct interface *ifp;
+ struct nhrp_interface *nifp;
+ struct nhrp_nhs *nhs, *tmp;
+ struct listnode *node;
+ afi_t afi;
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+ nifp = ifp->info;
+ for (afi = 0; afi < AFI_MAX; afi++) {
+ list_for_each_entry_safe(nhs, tmp, &nifp->afi[afi].nhslist_head, nhslist_entry)
+ nhrp_nhs_free(nhs);
+ }
+ }
+}
diff --git a/nhrpd/nhrp_packet.c b/nhrpd/nhrp_packet.c
new file mode 100644
index 00000000..5d2866a6
--- /dev/null
+++ b/nhrpd/nhrp_packet.c
@@ -0,0 +1,312 @@
+/* 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.
+ */
+
+#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) {
+ 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->holding_time = htons(ad->holdtime);
+ break;
+ default:
+ if (type & NHRP_EXTENSION_FLAG_COMPULSORY)
+ goto err;
+ 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 int 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);
+
+ zb = zbuf_alloc(1500);
+ if (!zb) return 0;
+
+ 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);
+ 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 0;
+
+err:
+ zbuf_free(zb);
+ return 0;
+}
+
+int nhrp_packet_init(void)
+{
+ thread_add_read(master, nhrp_packet_recvraw, 0, os_socket());
+ return 0;
+}
diff --git a/nhrpd/nhrp_peer.c b/nhrpd/nhrp_peer.c
new file mode 100644
index 00000000..45bfd7de
--- /dev/null
+++ b/nhrpd/nhrp_peer.c
@@ -0,0 +1,860 @@
+/* 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.
+ */
+
+#include <netinet/if_ether.h>
+
+#include "zebra.h"
+#include "memory.h"
+#include "thread.h"
+#include "hash.h"
+
+#include "nhrpd.h"
+#include "nhrp_protocol.h"
+#include "os.h"
+
+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;
+
+ THREAD_OFF(p->t_fallback);
+ 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 int 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);
+ }
+
+ return 0;
+}
+
+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_TIMER_MSEC_ON(
+ master, p->t_fallback,
+ nhrp_peer_notify_up, p, 50);
+ } 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);
+ }
+ /* Fall-through to post config update */
+ case NOTIFY_INTERFACE_ADDRESS_CHANGED:
+ 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(void *peer_data)
+{
+ struct nhrp_peer *p = peer_data;
+ return sockunion_hash(&p->vc->remote.nbma);
+}
+
+static int 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));
+ if (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;
+}
+
+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);
+ 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 int 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;
+
+ p->t_fallback = NULL;
+
+ if (p->online)
+ return 0;
+
+ 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_TIMER_ON(master, p->t_fallback, nhrp_peer_request_timeout, p, 30);
+ } else {
+ p->requested = p->fallback_requested = 0;
+ }
+
+ return 0;
+}
+
+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 (sockunion_family(&vc->local.nbma) == AF_UNSPEC)
+ return 0;
+
+ p->prio = establish > 1;
+ p->requested = 1;
+ vici_request_vc(nifp->ipsec_profile, &vc->local.nbma, &vc->remote.nbma, p->prio);
+ THREAD_TIMER_ON(master, p->t_fallback, nhrp_peer_request_timeout, p,
+ (nifp->ipsec_fallback_profile && !p->prio) ? 15 : 30);
+
+ 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);
+ nhrp_peer_check_delete(p);
+}
+
+void nhrp_peer_send(struct nhrp_peer *p, struct zbuf *zb)
+{
+ char buf[2][256];
+
+ nhrp_packet_debug(zb, "Send");
+
+ if (!p->online)
+ return;
+
+ debugf(NHRP_DEBUG_KERNEL, "PACKET: Send %s -> %s",
+ sockunion2str(&p->vc->local.nbma, buf[0], sizeof buf[0]),
+ sockunion2str(&p->vc->remote.nbma, buf[1], sizeof buf[1]));
+
+ os_sendmsg(zb->head, zbuf_used(zb),
+ p->ifp->ifindex,
+ sockunion_get_addr(&p->vc->remote.nbma),
+ sockunion_get_addrlen(&p->vc->remote.nbma));
+ zbuf_reset(zb);
+}
+
+static void nhrp_handle_resolution_req(struct nhrp_packet_parser *p)
+{
+ struct zbuf *zb, payload;
+ struct nhrp_packet_header *hdr;
+ struct nhrp_cie_header *cie;
+ struct nhrp_extension_header *ext;
+ struct nhrp_interface *nifp;
+ struct nhrp_peer *peer;
+
+ if (!(p->if_ad->flags & NHRP_IFF_SHORTCUT)) {
+ debugf(NHRP_DEBUG_COMMON, "Shortcuts disabled");
+ /* FIXME: Send error indication? */
+ return;
+ }
+
+ if (p->if_ad->network_id &&
+ p->route_type == NHRP_ROUTE_OFF_NBMA &&
+ p->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(p->ifp, &p->src_proto, NULL, &peer) != NHRP_ROUTE_NBMA_NEXTHOP)
+ return;
+
+#if 0
+ /* FIXME: Update requestors binding if CIE specifies holding time */
+ nhrp_cache_update_binding(
+ NHRP_CACHE_CACHED, &p->src_proto,
+ nhrp_peer_get(p->ifp, &p->src_nbma),
+ htons(cie->holding_time));
+#endif
+
+ nifp = peer->ifp->info;
+
+ /* Create reply */
+ zb = zbuf_alloc(1500);
+ hdr = nhrp_packet_push(zb, NHRP_PACKET_RESOLUTION_REPLY, &p->src_nbma, &p->src_proto, &p->dst_proto);
+
+ /* Copied information from request */
+ hdr->flags = p->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 = p->hdr->u.request_id;
+
+ /* CIE payload */
+ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nbma, &p->if_ad->addr);
+ cie->holding_time = htons(p->if_ad->holdtime);
+ cie->mtu = htons(p->if_ad->mtu);
+ if (p->if_ad->network_id && p->route_type == NHRP_ROUTE_OFF_NBMA)
+ cie->prefix_length = p->route_prefix.prefixlen;
+ else
+ cie->prefix_length = 8 * sockunion_get_addrlen(&p->if_ad->addr);
+
+ /* Handle extensions */
+ while ((ext = nhrp_ext_pull(&p->extensions, &payload)) != NULL) {
+ switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) {
+ case NHRP_EXTENSION_NAT_ADDRESS:
+ if (sockunion_family(&nifp->nat_nbma) == AF_UNSPEC)
+ break;
+ ext = nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS);
+ if (!ext) goto err;
+ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nat_nbma, &p->if_ad->addr);
+ if (!cie) goto err;
+ nhrp_ext_complete(zb, ext);
+ break;
+ default:
+ if (nhrp_ext_reply(zb, hdr, p->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, natted = 0;
+ size_t paylen;
+ void *pay;
+
+ debugf(NHRP_DEBUG_COMMON, "Parsing and replying to Registration Req");
+
+ 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) {
+ if (cie->prefix_length != 0xff && !(p->hdr->flags & htons(NHRP_FLAG_REGISTRATION_UNIQUE))) {
+ cie->code = NHRP_CODE_BINDING_NON_UNIQUE;
+ continue;
+ }
+
+ /* We currently support only unique prefix registrations */
+ if (cie->prefix_length != 0xff) {
+ 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 = nbma_addr;
+ 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)) {
+ 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) {
+ nhrp_cie_push(zb, NHRP_CODE_SUCCESS,
+ &p->peer->vc->remote.nbma,
+ &p->src_proto);
+ }
+ 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;
+ char buf[2][SU_ADDRSTRLEN];
+
+ 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 %s about packet to %s ignored",
+ sockunion2str(&p->vc->remote.nbma, buf[0], sizeof buf[0]),
+ sockunion2str(&dst, buf[1], sizeof buf[1]));
+ return;
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "Send Traffic Indication to %s (online=%d) about packet to %s",
+ sockunion2str(&p->vc->remote.nbma, buf[0], sizeof buf[0]),
+ p->online,
+ sockunion2str(&dst, buf[1], sizeof buf[1]));
+
+ /* Create reply */
+ zb = zbuf_alloc(1500);
+ hdr = nhrp_packet_push(zb, NHRP_PACKET_TRAFFIC_INDICATION, &nifp->nbma, &if_ad->addr, &dst);
+ hdr->hop_count = 0;
+
+ /* 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;
+ char buf[2][SU_ADDRSTRLEN];
+
+ hdr = nhrp_packet_pull(&origmsg, &src_nbma, &src_proto, &dst_proto);
+ if (!hdr) return;
+
+ debugf(NHRP_DEBUG_COMMON, "Error Indication from %s about packet to %s ignored",
+ sockunion2str(&pp->src_proto, buf[0], sizeof buf[0]),
+ sockunion2str(&dst_proto, buf[1], sizeof buf[1]));
+
+ 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;
+ char buf[2][SU_ADDRSTRLEN];
+
+ if (!parse_ether_packet(&p->payload, htons(p->hdr->protocol_type), NULL, &dst))
+ return;
+
+ debugf(NHRP_DEBUG_COMMON, "Traffic Indication from %s about packet to %s: %s",
+ sockunion2str(&p->src_proto, buf[0], sizeof buf[0]),
+ sockunion2str(&dst, buf[1], sizeof buf[1]),
+ (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[] = {
+ [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, 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;
+ uint16_t type, len;
+
+ if (pp->hdr->hop_count == 0)
+ return;
+
+ /* Create forward packet - copy header */
+ zb = 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(zb, &pp->payload, zbuf_used(&pp->payload));
+
+ /* 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 ((cie = 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->holding_time = htons(if_ad->holdtime);
+ }
+ 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;
+ 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);
+ return;
+err:
+ nhrp_packet_debug(pp->pkt, "FWD-FAIL");
+ zbuf_free(zb);
+}
+
+static void nhrp_packet_debug(struct zbuf *zb, const char *dir)
+{
+ char buf[2][SU_ADDRSTRLEN];
+ 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);
+
+ sockunion2str(&src_proto, buf[0], sizeof buf[0]);
+ sockunion2str(&dst_proto, buf[1], sizeof buf[1]);
+
+ reply = packet_types[hdr->type].type == PACKET_REPLY;
+ debugf(NHRP_DEBUG_COMMON, "%s %s(%d) %s -> %s",
+ dir,
+ packet_types[hdr->type].name ? : "Unknown",
+ hdr->type,
+ reply ? buf[1] : buf[0],
+ reply ? buf[0] : buf[1]);
+}
+
+struct nhrp_route_info {
+ int local;
+ struct interface *ifp;
+ struct nhrp_vc *vc;
+};
+
+void nhrp_peer_recv(struct nhrp_peer *p, struct zbuf *zb)
+{
+ char buf[2][SU_ADDRSTRLEN];
+ 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 afi;
+
+ debugf(NHRP_DEBUG_KERNEL, "PACKET: Recv %s -> %s",
+ sockunion2str(&vc->remote.nbma, buf[0], sizeof buf[0]),
+ sockunion2str(&vc->local.nbma, buf[1], sizeof buf[1]));
+
+ 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;
+
+ afi = htons(hdr->afnum);
+ if (hdr->type > ZEBRA_NUM_OF(packet_types) ||
+ hdr->version != NHRP_VERSION_RFC2332 ||
+ afi >= AFI_MAX ||
+ packet_types[hdr->type].type == PACKET_UNKNOWN ||
+ htons(hdr->packet_size) > realsize) {
+ zlog_info("From %s: error: packet type %d, version %d, AFI %d, size %d (real size %d)",
+ sockunion2str(&vc->remote.nbma, buf[0], sizeof buf[0]),
+ (int) hdr->type, (int) hdr->version, (int) afi,
+ (int) htons(hdr->packet_size),
+ (int) realsize);
+ goto drop;
+ }
+ pp.if_ad = &((struct nhrp_interface *)ifp->info)->afi[afi];
+
+ extoff = htons(hdr->extension_offset);
+ if (extoff) {
+ if (extoff >= realsize) {
+ info = "extoff larger than packet";
+ goto drop;
+ }
+ paylen = extoff - (zb->head - zb->buf);
+ } 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[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 */
+ }
+ }
+ 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 %s: error: %s",
+ sockunion2str(&vc->remote.nbma, buf[0], sizeof buf[0]),
+ 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 00000000..a4bc9fa6
--- /dev/null
+++ b/nhrpd/nhrp_protocol.h
@@ -0,0 +1,128 @@
+/* nhrp_protocol.h - NHRP protocol definitions
+ *
+ * Copyright (c) 2007-2012 Timo Teräs <***@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
+
+/* 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 00000000..cc6b5fa2
--- /dev/null
+++ b/nhrpd/nhrp_route.c
@@ -0,0 +1,345 @@
+/* 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.
+ */
+
+#include "nhrpd.h"
+#include "table.h"
+#include "memory.h"
+#include "stream.h"
+#include "log.h"
+#include "zclient.h"
+
+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 void nhrp_zebra_connected(struct zclient *zclient)
+{
+ /* No real VRF support yet -- bind only to the default vrf */
+ zclient_send_requests (zclient, VRF_DEFAULT);
+}
+
+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_family(&ri->via) == AF_UNSPEC) {
+ XFREE(MTYPE_NHRP_ROUTE, rn->info);
+ rn->info = NULL;
+ 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_family(nexthop) != AF_UNSPEC) || ifp);
+ if (rn) {
+ ri = rn->info;
+ ri->via = *nexthop;
+ ri->ifp = ifp;
+ nhrp_route_update_put(rn);
+ }
+}
+
+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 in_addr *nexthop_ipv4;
+ int flags = 0;
+
+ if (zclient->sock < 0)
+ return;
+
+ switch (type) {
+ case NHRP_CACHE_NEGATIVE:
+ SET_FLAG(flags, ZEBRA_FLAG_REJECT);
+ 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(flags, ZEBRA_FLAG_FIB_OVERRIDE);
+ break;
+ }
+ SET_FLAG(flags, ZEBRA_FLAG_INTERNAL);
+
+ if (p->family == AF_INET) {
+ struct zapi_ipv4 api;
+
+ memset(&api, 0, sizeof(api));
+ api.flags = flags;
+ api.type = ZEBRA_ROUTE_NHRP;
+ api.safi = SAFI_UNICAST;
+
+ SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP);
+ if (nexthop) {
+ SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP);
+ nexthop_ipv4 = (struct in_addr *) sockunion_get_addr(nexthop);
+ api.nexthop_num = 1;
+ api.nexthop = &nexthop_ipv4;
+ }
+ if (ifp) {
+ SET_FLAG(api.message, ZAPI_MESSAGE_IFINDEX);
+ api.ifindex_num = 1;
+ api.ifindex = &ifp->ifindex;
+ }
+ if (mtu) {
+ SET_FLAG(api.message, ZAPI_MESSAGE_MTU);
+ api.mtu = mtu;
+ }
+
+ if (unlikely(debug_flags & NHRP_DEBUG_ROUTE)) {
+ char buf[2][INET_ADDRSTRLEN];
+ zlog_debug("Zebra send: IPv4 route %s %s/%d nexthop %s metric %u"
+ " count %d dev %s",
+ add ? "add" : "del",
+ inet_ntop(AF_INET, &p->u.prefix4, buf[0], sizeof(buf[0])),
+ p->prefixlen,
+ nexthop ? inet_ntop(AF_INET, api.nexthop[0], buf[1], sizeof(buf[1])) : "<onlink>",
+ api.metric, api.nexthop_num, ifp->name);
+ }
+
+ zapi_ipv4_route(
+ add ? ZEBRA_IPV4_ROUTE_ADD : ZEBRA_IPV4_ROUTE_DELETE,
+ zclient, (struct prefix_ipv4 *) p, &api);
+ }
+}
+
+int nhrp_route_read(int cmd, struct zclient *zclient, zebra_size_t length, vrf_id_t vrf_id)
+{
+ struct stream *s;
+ struct interface *ifp = NULL;
+ struct prefix prefix;
+ union sockunion nexthop_addr;
+ unsigned char message, nexthop_num, ifindex_num;
+ unsigned ifindex;
+ char buf[2][PREFIX_STRLEN];
+ int i, afaddrlen, added;
+
+ s = zclient->ibuf;
+ memset(&prefix, 0, sizeof(prefix));
+ sockunion_family(&nexthop_addr) = AF_UNSPEC;
+
+ /* Type, flags, message. */
+ /*type =*/ stream_getc(s);
+ /*flags =*/ stream_getc(s);
+ message = stream_getc(s);
+
+ /* Prefix */
+ switch (cmd) {
+ case ZEBRA_IPV4_ROUTE_ADD:
+ case ZEBRA_IPV4_ROUTE_DELETE:
+ prefix.family = AF_INET;
+ break;
+ case ZEBRA_IPV6_ROUTE_ADD:
+ case ZEBRA_IPV6_ROUTE_DELETE:
+ prefix.family = AF_INET6;
+ break;
+ default:
+ return -1;
+ }
+ afaddrlen = family2addrsize(prefix.family);
+ prefix.prefixlen = stream_getc(s);
+ stream_get(&prefix.u.val, s, PSIZE(prefix.prefixlen));
+
+ /* Nexthop, ifindex, distance, metric. */
+ if (CHECK_FLAG(message, ZAPI_MESSAGE_NEXTHOP|ZAPI_MESSAGE_IFINDEX)) {
+ nexthop_num = stream_getc(s);
+ for (i = 0; i < nexthop_num; i++) {
+ stream_get(buf[0], s, afaddrlen);
+ if (i == 0) sockunion_set(&nexthop_addr, prefix.family, (u_char*) buf[0], afaddrlen);
+ }
+ ifindex_num = stream_getc(s);
+ for (i = 0; i < ifindex_num; i++) {
+ ifindex = stream_getl(s);
+ if (i == 0 && ifindex != IFINDEX_INTERNAL)
+ ifp = if_lookup_by_index(ifindex);
+ }
+ }
+ if (CHECK_FLAG(message, ZAPI_MESSAGE_DISTANCE))
+ /*distance =*/ stream_getc(s);
+ if (CHECK_FLAG(message, ZAPI_MESSAGE_METRIC))
+ /*metric =*/ stream_getl(s);
+
+ added = (cmd == ZEBRA_IPV4_ROUTE_ADD || cmd == ZEBRA_IPV6_ROUTE_ADD);
+ debugf(NHRP_DEBUG_ROUTE, "if-route-%s: %s via %s dev %s",
+ added ? "add" : "del",
+ prefix2str(&prefix, buf[0], sizeof buf[0]),
+ sockunion2str(&nexthop_addr, buf[1], sizeof buf[1]),
+ ifp ? ifp->name : "(none)");
+
+ nhrp_route_update_zebra(&prefix, &nexthop_addr, ifp);
+ nhrp_shortcut_prefix_change(&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));
+ char buf[PREFIX_STRLEN];
+
+ 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 %s: nhrp_if=%s",
+ prefix2str(&lookup, buf, sizeof buf),
+ ri->nhrp_ifp->name);
+
+ if (via) sockunion_family(via) = AF_UNSPEC;
+ if (ifp) *ifp = ri->nhrp_ifp;
+ } else {
+ debugf(NHRP_DEBUG_ROUTE, "lookup %s: zebra route dev %s",
+ prefix2str(&lookup, buf, sizeof buf),
+ 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;
+}
+
+void nhrp_zebra_init(void)
+{
+ zebra_rib[AFI_IP] = route_table_init();
+ zebra_rib[AFI_IP6] = route_table_init();
+
+ zclient = zclient_new(master);
+ zclient->zebra_connected = nhrp_zebra_connected;
+ zclient->interface_add = nhrp_interface_add;
+ zclient->interface_delete = nhrp_interface_delete;
+ zclient->interface_up = nhrp_interface_up;
+ zclient->interface_down = nhrp_interface_down;
+ zclient->interface_address_add = nhrp_interface_address_add;
+ zclient->interface_address_delete = nhrp_interface_address_delete;
+ zclient->ipv4_route_add = nhrp_route_read;
+ zclient->ipv4_route_delete = nhrp_route_read;
+ zclient->ipv6_route_add = nhrp_route_read;
+ zclient->ipv6_route_delete = nhrp_route_read;
+
+ zclient_init(zclient, ZEBRA_ROUTE_NHRP);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_KERNEL, VRF_DEFAULT);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_CONNECT, VRF_DEFAULT);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_STATIC, VRF_DEFAULT);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_RIP, VRF_DEFAULT);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_OSPF, VRF_DEFAULT);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_ISIS, VRF_DEFAULT);
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, ZEBRA_ROUTE_BGP, VRF_DEFAULT);
+}
+
+void nhrp_zebra_terminate(void)
+{
+ zclient_stop(zclient);
+ route_table_finish(zebra_rib[AFI_IP]);
+ route_table_finish(zebra_rib[AFI_IP6]);
+}
+
diff --git a/nhrpd/nhrp_shortcut.c b/nhrpd/nhrp_shortcut.c
new file mode 100644
index 00000000..421f2886
--- /dev/null
+++ b/nhrpd/nhrp_shortcut.c
@@ -0,0 +1,402 @@
+/* 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.
+ */
+
+#include "nhrpd.h"
+#include "table.h"
+#include "memory.h"
+#include "thread.h"
+#include "log.h"
+#include "nhrp_protocol.h"
+
+static struct route_table *shortcut_rib[AFI_MAX];
+
+static int 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)
+{
+ char buf[PREFIX_STRLEN];
+
+ if (s->expiring && s->cache && s->cache->used) {
+ debugf(NHRP_DEBUG_ROUTE, "Shortcut %s used and expiring",
+ prefix2str(s->p, buf, sizeof buf));
+ nhrp_shortcut_send_resolution_req(s);
+ }
+}
+
+static int nhrp_shortcut_do_expire(struct thread *t)
+{
+ struct nhrp_shortcut *s = THREAD_ARG(t);
+
+ s->t_timer = NULL;
+ THREAD_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, s->holding_time/3);
+ s->expiring = 1;
+ nhrp_shortcut_check_use(s);
+
+ return 0;
+}
+
+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);
+
+ switch (cmd) {
+ case NOTIFY_CACHE_UP:
+ if (!s->route_installed) {
+ nhrp_route_announce(1, s->type, s->p, NULL, &s->cache->remote_addr, 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;
+ nhrp_shortcut_cache_notify(&s->cache_notifier, NOTIFY_CACHE_UP);
+ }
+ }
+ if (!s->cache || !s->cache->route_installed)
+ 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_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_expire, s, 2*holding_time/3);
+ }
+}
+
+static void nhrp_shortcut_delete(struct nhrp_shortcut *s)
+{
+ struct route_node *rn;
+ afi_t afi = family2afi(PREFIX_FAMILY(s->p));
+ char buf[PREFIX_STRLEN];
+
+ THREAD_OFF(s->t_timer);
+ nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid);
+
+ debugf(NHRP_DEBUG_ROUTE, "Shortcut %s purged",
+ prefix2str(s->p, buf, sizeof buf));
+
+ 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 int nhrp_shortcut_do_purge(struct thread *t)
+{
+ struct nhrp_shortcut *s = THREAD_ARG(t);
+ s->t_timer = NULL;
+ nhrp_shortcut_delete(s);
+ return 0;
+}
+
+static struct nhrp_shortcut *nhrp_shortcut_get(struct prefix *p)
+{
+ struct nhrp_shortcut *s;
+ struct route_node *rn;
+ char buf[PREFIX_STRLEN];
+ 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 %s created",
+ prefix2str(s->p, buf, sizeof buf));
+ } 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 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;
+ union sockunion *proto, cie_proto, *nbma, *nbma_natoa, cie_nbma, nat_nbma;
+ struct prefix prefix, route_prefix;
+ struct zbuf extpl;
+ char bufp[PREFIX_STRLEN], buf[3][SU_ADDRSTRLEN];
+ int holding_time = pp->if_ad->holdtime;
+
+ nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid);
+ THREAD_OFF(s->t_timer);
+ THREAD_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 1);
+
+ 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;
+ }
+
+ /* 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:
+ nhrp_cie_pull(&extpl, pp->hdr, &nat_nbma, &cie_proto);
+ break;
+ }
+ }
+
+ /* 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 %s to %s",
+ sockunion2str(&cie_proto, buf[0], sizeof buf[0]),
+ sockunion2str(&pp->dst_proto, buf[1], sizeof buf[1]));
+ }
+
+ /* 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 = 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;
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "Shortcut: %s is at proto %s cie-nbma %s nat-nbma %s cie-holdtime %d",
+ prefix2str(&prefix, bufp, sizeof bufp),
+ sockunion2str(proto, buf[0], sizeof buf[0]),
+ sockunion2str(&cie_nbma, buf[1], sizeof buf[1]),
+ sockunion2str(&nat_nbma, buf[2], sizeof buf[2]),
+ htons(cie->holding_time));
+
+ /* Update cache entry for the protocol to nbma binding */
+ if (sockunion_family(&nat_nbma) != AF_UNSPEC) {
+ nbma = &nat_nbma;
+ nbma_natoa = &cie_nbma;
+ } else {
+ nbma = &cie_nbma;
+ nbma_natoa = NULL;
+ }
+ if (sockunion_family(nbma)) {
+ c = nhrp_cache_get(pp->ifp, proto, 1);
+ if (c) {
+ nhrp_cache_update_binding(
+ c, NHRP_CACHE_CACHED, holding_time,
+ nhrp_peer_get(pp->ifp, nbma),
+ htons(cie->mtu), nbma_natoa);
+ }
+ }
+
+ /* Update shortcut entry for subnet to protocol gw binding */
+ if (c && !sockunion_same(proto, &pp->dst_proto)) {
+ ps = nhrp_shortcut_get(&prefix);
+ if (ps) {
+ ps->addr = s->addr;
+ nhrp_shortcut_update_binding(ps, NHRP_CACHE_CACHED, c, holding_time);
+ }
+ }
+
+ 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_peer *peer;
+
+ 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: Send holding time, and MTU */
+
+ nhrp_ext_request(zb, hdr, ifp);
+
+ /* Cisco NAT detection extension */
+ hdr->flags |= htons(NHRP_FLAG_RESOLUTION_NAT);
+ nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS);
+
+ 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;
+
+ sockunion2hostprefix(addr, &p);
+ s = nhrp_shortcut_get(&p);
+ if (s && s->type != NHRP_CACHE_INCOMPLETE) {
+ s->addr = *addr;
+ THREAD_OFF(s->t_timer);
+ THREAD_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 30);
+ 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_TIMER_MSEC_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 5);
+ } 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_TIMER_MSEC_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 3000);
+ 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 00000000..f9e1ee06
--- /dev/null
+++ b/nhrpd/nhrp_vc.c
@@ -0,0 +1,217 @@
+/* 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"
+
+struct child_sa {
+ uint32_t id;
+ struct nhrp_vc *vc;
+ struct list_head childlist_entry;
+};
+
+static struct hash *nhrp_vc_hash;
+static struct list_head childlist_head[512];
+
+static unsigned int nhrp_vc_key(void *peer_data)
+{
+ struct nhrp_vc *vc = peer_data;
+ return jhash_2words(
+ sockunion_hash(&vc->local.nbma),
+ sockunion_hash(&vc->remote.nbma),
+ 0);
+}
+
+static int 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));
+ if (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)
+{
+ char buf[2][SU_ADDRSTRLEN];
+ struct child_sa *sa = NULL, *lsa;
+ uint32_t child_hash = child_id % ZEBRA_NUM_OF(childlist_head);
+ int abort_migration = 0;
+
+ list_for_each_entry(lsa, &childlist_head[child_hash], childlist_entry) {
+ if (lsa->id == child_id) {
+ sa = lsa;
+ break;
+ }
+ }
+
+ if (!sa) {
+ if (!vc) return 0;
+
+ sa = XMALLOC(MTYPE_NHRP_VC, sizeof(struct child_sa));
+ if (!sa) return 0;
+
+ *sa = (struct child_sa) {
+ .id = child_id,
+ .childlist_entry = LIST_INITIALIZER(sa->childlist_entry),
+ .vc = NULL,
+ };
+ list_add_tail(&sa->childlist_entry, &childlist_head[child_hash]);
+ }
+
+ 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 %s to %s",
+ sockunion2str(&sa->vc->remote.nbma, buf[0], sizeof buf[0]),
+ sockunion2str(&vc->remote.nbma, buf[1], sizeof buf[1]));
+ 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) {
+ list_del(&sa->childlist_entry);
+ 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);
+ 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_backet *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);
+ for (i = 0; i < ZEBRA_NUM_OF(childlist_head); i++)
+ list_init(&childlist_head[i]);
+}
+
+void nhrp_vc_reset(void)
+{
+ struct child_sa *sa, *n;
+ size_t i;
+
+ for (i = 0; i < ZEBRA_NUM_OF(childlist_head); i++) {
+ list_for_each_entry_safe(sa, n, &childlist_head[i], childlist_entry)
+ 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 00000000..9b1c69de
--- /dev/null
+++ b/nhrpd/nhrp_vty.c
@@ -0,0 +1,928 @@
+/* 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 "nhrpd.h"
+#include "netlink.h"
+
+static struct cmd_node zebra_node = {
+ .node = ZEBRA_NODE,
+ .prompt = "%s(config-router)# ",
+ .vtysh = 1,
+};
+
+static struct cmd_node nhrp_interface_node = {
+ .node = INTERFACE_NODE,
+ .prompt = "%s(config-if)# ",
+ .vtysh = 1,
+};
+
+#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, NULL },
+};
+
+static const struct message interface_flags_desc[] = {
+ { NHRP_IFF_SHORTCUT, "shortcut" },
+ { NHRP_IFF_REDIRECT, "redirect" },
+ { NHRP_IFF_REG_NO_UNIQUE, "registration no-unique" },
+ { 0, NULL },
+};
+
+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 <= (int)ZEBRA_NUM_OF(errmsgs))
+ if (errmsgs[ret])
+ str = errmsgs[ret];
+
+ if (!str) {
+ str = buf;
+ snprintf(buf, sizeof(buf), "Unknown error %d", ret);
+ }
+
+ vty_out (vty, "%% %s%s", str, VTY_NEWLINE);
+
+ return CMD_WARNING;
+}
+
+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%s", name, VTY_NEWLINE);
+ return CMD_WARNING;
+}
+
+#ifndef NO_DEBUG
+
+DEFUN(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:%s", VTY_NEWLINE);
+
+ 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%s",
+ debug_flags_desc[i].str, VTY_NEWLINE);
+ }
+
+ 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[0], 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[0], 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%s", VTY_NEWLINE);
+ } 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%s", debug_flags_desc[i].str, VTY_NEWLINE);
+ }
+ }
+ vty_out(vty, "!%s", VTY_NEWLINE);
+#endif /* NO_DEBUG */
+
+ if (nhrp_event_socket_path) {
+ vty_out(vty, "nhrp event socket %s%s",
+ nhrp_event_socket_path, VTY_NEWLINE);
+ }
+ if (netlink_nflog_group) {
+ vty_out(vty, "nhrp nflog-group %d%s",
+ netlink_nflog_group, VTY_NEWLINE);
+ }
+
+ 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 char *cmd)
+{
+ return strncmp(cmd, "ipv6", 4) == 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")
+{
+ evmgr_set_socket(argv[0]);
+ 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")
+{
+ 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;
+
+ VTY_GET_INTEGER_RANGE("nflog-group", nfgroup, argv[0], 1, 65535);
+ 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(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")
+{
+ struct interface *ifp = vty->index;
+
+ nhrp_interface_set_protection(ifp, argv[0], argv[1]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_tunnel_protection, no_tunnel_protection_cmd,
+ "no tunnel protection",
+ NO_STR
+ "NHRP/GRE integration\n"
+ "IPsec protection\n")
+{
+ struct interface *ifp = vty->index;
+
+ 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")
+{
+ struct interface *ifp = vty->index;
+ nhrp_interface_set_source(ifp, argv[0]);
+ 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")
+{
+ struct interface *ifp = vty->index;
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ VTY_GET_INTEGER_RANGE("network-id", nifp->afi[afi].network_id, argv[1], 1, 4294967295);
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ return toggle_flag(vty, interface_flags_desc, argv[1], 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ return toggle_flag(vty, interface_flags_desc, argv[1], 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+ char name[256];
+ snprintf(name, sizeof(name), "registration %s", argv[1]);
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+ char name[256];
+ snprintf(name, sizeof(name), "registration %s", argv[1]);
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ VTY_GET_INTEGER_RANGE("holdtime", nifp->afi[afi].holdtime, argv[1], 1, 65000);
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+ afi_t afi = cmd_to_afi(argv[0]);
+
+ 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")
+{
+ struct interface *ifp = vty->index;
+ struct nhrp_interface *nifp = ifp->info;
+
+ if (argv[0][0] == 'o') {
+ nifp->afi[AFI_IP].configured_mtu = -1;
+ } else {
+ VTY_GET_INTEGER_RANGE("mtu", nifp->afi[AFI_IP].configured_mtu, argv[0], 576, 1500);
+ }
+ 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")
+{
+ struct interface *ifp = vty->index;
+ 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")
+{
+ struct interface *ifp = vty->index;
+ afi_t afi = cmd_to_afi(argv[0]);
+ union sockunion proto_addr, nbma_addr;
+ struct nhrp_cache *c;
+
+ if (str2sockunion(argv[1], &proto_addr) < 0 ||
+ afi2family(afi) != sockunion_family(&proto_addr))
+ return nhrp_vty_return(vty, NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH);
+
+ c = nhrp_cache_get(ifp, &proto_addr, 1);
+ if (!c)
+ return nhrp_vty_return(vty, NHRP_ERR_FAIL);
+
+ c->map = 1;
+ if (strcmp(argv[2], "local") == 0) {
+ nhrp_cache_update_binding(c, NHRP_CACHE_LOCAL, 0, NULL, 0, NULL);
+ } else{
+ if (str2sockunion(argv[2], &nbma_addr) < 0)
+ return nhrp_vty_return(vty, NHRP_ERR_FAIL);
+ nhrp_cache_update_binding(c, NHRP_CACHE_STATIC, 0,
+ nhrp_peer_get(ifp, &nbma_addr), 0, NULL);
+ }
+
+ return CMD_SUCCESS;
+}
+
+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"
+ "IPv4 NBMA address\n"
+ "Fully qualified domain name for NBMA address(es)\n")
+{
+ struct interface *ifp = vty->index;
+ afi_t afi = cmd_to_afi(argv[0]);
+ union sockunion proto_addr;
+ int ret;
+
+ if (str2sockunion(argv[1], &proto_addr) < 0)
+ sockunion_family(&proto_addr) = AF_UNSPEC;
+
+ ret = nhrp_nhs_add(ifp, afi, &proto_addr, argv[2]);
+ 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"
+ "IPv4 NBMA address\n"
+ "Fully qualified domain name for NBMA address(es)\n")
+{
+ struct interface *ifp = vty->index;
+ afi_t afi = cmd_to_afi(argv[0]);
+ union sockunion proto_addr;
+ int ret;
+
+ if (str2sockunion(argv[1], &proto_addr) < 0)
+ sockunion_family(&proto_addr) = AF_UNSPEC;
+
+ ret = nhrp_nhs_del(ifp, afi, &proto_addr, argv[2]);
+ return nhrp_vty_return(vty, ret);
+}
+
+struct info_ctx {
+ struct vty *vty;
+ afi_t afi;
+ int count;
+};
+
+static void show_ip_nhrp_cache(struct nhrp_cache *c, void *pctx)
+{
+ struct info_ctx *ctx = pctx;
+ struct vty *vty = ctx->vty;
+ char buf[2][SU_ADDRSTRLEN];
+
+ if (ctx->afi != family2afi(sockunion_family(&c->remote_addr)))
+ return;
+
+ if (!ctx->count) {
+ vty_out(vty, "%-8s %-8s %-24s %-24s %-6s %s%s",
+ "Iface",
+ "Type",
+ "Protocol",
+ "NBMA",
+ "Flags",
+ "Identity",
+ VTY_NEWLINE);
+ }
+ ctx->count++;
+
+ vty_out(ctx->vty, "%-8s %-8s %-24s %-24s %c%c%c %s%s",
+ c->ifp->name,
+ nhrp_cache_type_str[c->cur.type],
+ sockunion2str(&c->remote_addr, buf[0], sizeof buf[0]),
+ c->cur.peer ? sockunion2str(&c->cur.peer->vc->remote.nbma, buf[1], sizeof buf[1]) : "-",
+ c->used ? 'U' : ' ',
+ c->t_timeout ? 'T' : ' ',
+ c->t_auth ? 'A' : ' ',
+ c->cur.peer ? c->cur.peer->vc->remote.id : "-",
+ VTY_NEWLINE);
+}
+
+static void show_ip_opennhrp_cache(struct nhrp_cache *c, void *pctx)
+{
+ struct info_ctx *ctx = pctx;
+ struct vty *vty = ctx->vty;
+ char buf[SU_ADDRSTRLEN];
+
+ if (ctx->afi != family2afi(sockunion_family(&c->remote_addr)))
+ return;
+
+ vty_out(ctx->vty,
+ "Type: %s%s"
+ "Flags:%s%s%s"
+ "Protocol-Address: %s/%zu%s",
+ nhrp_cache_type_str[c->cur.type],
+ VTY_NEWLINE,
+ (c->cur.peer && c->cur.peer->online) ? " up": "",
+ c->used ? " used": "",
+ VTY_NEWLINE,
+ sockunion2str(&c->remote_addr, buf, sizeof buf),
+ 8 * family2addrsize(sockunion_family(&c->remote_addr)),
+ VTY_NEWLINE);
+
+ if (c->cur.peer) {
+ vty_out(ctx->vty,
+ "NBMA-Address: %s%s",
+ sockunion2str(&c->cur.peer->vc->remote.nbma, buf, sizeof buf),
+ VTY_NEWLINE);
+ }
+
+ if (sockunion_family(&c->cur.remote_nbma_natoa) != AF_UNSPEC) {
+ vty_out(ctx->vty,
+ "NBMA-NAT-OA-Address: %s%s",
+ sockunion2str(&c->cur.remote_nbma_natoa, buf, sizeof buf),
+ VTY_NEWLINE);
+ }
+
+ vty_out(ctx->vty, "%s", VTY_NEWLINE);
+}
+
+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];
+
+ if (!ctx->count) {
+ vty_out(vty, "%-8s %-24s %-24s %s%s",
+ "Type",
+ "Prefix",
+ "Via",
+ "Identity",
+ VTY_NEWLINE);
+ }
+ ctx->count++;
+
+ c = s->cache;
+ vty_out(ctx->vty, "%-8s %-24s %-24s %s%s",
+ nhrp_cache_type_str[s->type],
+ prefix2str(s->p, buf1, sizeof buf1),
+ c ? sockunion2str(&c->remote_addr, buf2, sizeof buf2) : "",
+ (c && c->cur.peer) ? c->cur.peer->vc->remote.id : "",
+ VTY_NEWLINE);
+}
+
+DEFUN(show_ip_nhrp, show_ip_nhrp_cmd,
+ "show " AFI_CMD " nhrp (cache|shortcut|opennhrp|)",
+ SHOW_STR
+ AFI_STR
+ "NHRP information\n"
+ "Forwarding cache information\n"
+ "Shortcut information\n"
+ "opennhrpctl style cache dump\n")
+{
+ struct listnode *node;
+ struct interface *ifp;
+ struct info_ctx ctx = {
+ .vty = vty,
+ .afi = cmd_to_afi(argv[0]),
+ };
+
+ if (!argv[1] || argv[1][0] == 'c') {
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp))
+ nhrp_cache_foreach(ifp, show_ip_nhrp_cache, &ctx);
+ } else if (argv[1][0] == 'o') {
+ vty_out(vty, "Status: ok%s%s", VTY_NEWLINE, VTY_NEWLINE);
+ ctx.count++;
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp))
+ nhrp_cache_foreach(ifp, show_ip_opennhrp_cache, &ctx);
+ } else {
+ nhrp_shortcut_foreach(ctx.afi, show_ip_nhrp_shortcut, &ctx);
+ }
+
+ if (!ctx.count) {
+ vty_out(vty, "%% No entries%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+static void show_dmvpn_entry(struct nhrp_vc *vc, void *ctx)
+{
+ struct vty *vty = ctx;
+ char buf[2][SU_ADDRSTRLEN];
+
+ vty_out(vty, "%-24s %-24s %c %-4d %-24s%s",
+ sockunion2str(&vc->local.nbma, buf[0], sizeof buf[0]),
+ sockunion2str(&vc->remote.nbma, buf[1], sizeof buf[1]),
+ notifier_active(&vc->notifier_list) ? 'n' : ' ',
+ vc->ipsec,
+ vc->remote.id,
+ VTY_NEWLINE);
+}
+
+DEFUN(show_dmvpn, show_dmvpn_cmd,
+ "show dmvpn",
+ SHOW_STR
+ "DMVPN information\n")
+{
+ vty_out(vty, "%-24s %-24s %-6s %-4s %-24s%s",
+ "Src",
+ "Dst",
+ "Flags",
+ "SAs",
+ "Identity",
+ VTY_NEWLINE);
+
+ nhrp_vc_foreach(show_dmvpn_entry, vty);
+
+ 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_CACHED) {
+ nhrp_cache_update_binding(c, c->cur.type, -1, NULL, 0, NULL);
+ 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 listnode *node;
+ struct interface *ifp;
+ struct info_ctx ctx = {
+ .vty = vty,
+ .afi = cmd_to_afi(argv[0]),
+ .count = 0,
+ };
+
+ if (!argv[1] || argv[1][0] == 'c') {
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp))
+ nhrp_cache_foreach(ifp, clear_nhrp_cache, &ctx);
+ } else {
+ nhrp_shortcut_foreach(ctx.afi, clear_nhrp_shortcut, &ctx);
+ }
+
+ if (!ctx.count) {
+ vty_out(vty, "%% No entries%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vty_out(vty, "%% %d entries cleared%s", ctx.count, VTY_NEWLINE);
+ 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 *c, void *data)
+{
+ struct write_map_ctx *ctx = data;
+ struct vty *vty = ctx->vty;
+ char buf[2][SU_ADDRSTRLEN];
+
+ if (!c->map) return;
+ if (sockunion_family(&c->remote_addr) != ctx->family) return;
+
+ vty_out(vty, " %s nhrp map %s %s%s",
+ ctx->aficmd,
+ sockunion2str(&c->remote_addr, buf[0], sizeof buf[0]),
+ c->cur.type == NHRP_CACHE_LOCAL ? "local" :
+ sockunion2str(&c->cur.peer->vc->remote.nbma, buf[1], sizeof buf[1]),
+ VTY_NEWLINE);
+}
+
+static int interface_config_write(struct vty *vty)
+{
+ struct write_map_ctx mapctx;
+ struct listnode *node;
+ struct interface *ifp;
+ struct nhrp_interface *nifp;
+ struct nhrp_nhs *nhs;
+ const char *aficmd;
+ afi_t afi;
+ char buf[SU_ADDRSTRLEN];
+ int i;
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+ vty_out(vty, "interface %s%s", ifp->name, VTY_NEWLINE);
+ if (ifp->desc)
+ vty_out(vty, " description %s%s", ifp->desc, VTY_NEWLINE);
+
+ 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, "%s", VTY_NEWLINE);
+ }
+ if (nifp->source)
+ vty_out(vty, " tunnel source %s%s",
+ nifp->source, VTY_NEWLINE);
+
+ 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%s",
+ aficmd, ad->network_id,
+ VTY_NEWLINE);
+
+ if (ad->holdtime != NHRPD_DEFAULT_HOLDTIME)
+ vty_out(vty, " %s nhrp holdtime %u%s",
+ aficmd, ad->holdtime,
+ VTY_NEWLINE);
+
+ if (ad->configured_mtu < 0)
+ vty_out(vty, " %s nhrp mtu opennhrp%s",
+ aficmd, VTY_NEWLINE);
+ else if (ad->configured_mtu)
+ vty_out(vty, " %s nhrp mtu %u%s",
+ aficmd, ad->configured_mtu,
+ VTY_NEWLINE);
+
+ 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%s",
+ aficmd, interface_flags_desc[i].str, VTY_NEWLINE);
+ }
+
+ mapctx = (struct write_map_ctx) {
+ .vty = vty,
+ .family = afi2family(afi),
+ .aficmd = aficmd,
+ };
+ nhrp_cache_foreach(ifp, interface_config_write_nhrp_map, &mapctx);
+
+ list_for_each_entry(nhs, &ad->nhslist_head, nhslist_entry) {
+ vty_out(vty, " %s nhrp nhs %s nbma %s%s",
+ aficmd,
+ sockunion_family(&nhs->proto_addr) == AF_UNSPEC ? "dynamic" : sockunion2str(&nhs->proto_addr, buf, sizeof buf),
+ nhs->nbma_fqdn,
+ VTY_NEWLINE);
+ }
+ }
+
+ vty_out (vty, "!%s", VTY_NEWLINE);
+ }
+
+ return 0;
+}
+
+void nhrp_config_init(void)
+{
+ install_node(&zebra_node, nhrp_config_write);
+ install_default(ZEBRA_NODE);
+
+ /* global commands */
+ install_element(VIEW_NODE, &show_debugging_nhrp_cmd);
+ install_element(VIEW_NODE, &show_ip_nhrp_cmd);
+ install_element(VIEW_NODE, &show_dmvpn_cmd);
+ install_element(ENABLE_NODE, &show_debugging_nhrp_cmd);
+ install_element(ENABLE_NODE, &show_ip_nhrp_cmd);
+ install_element(ENABLE_NODE, &show_dmvpn_cmd);
+ install_element(ENABLE_NODE, &clear_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);
+
+ /* interface specific commands */
+ install_node(&nhrp_interface_node, interface_config_write);
+ install_default(INTERFACE_NODE);
+
+ install_element(CONFIG_NODE, &interface_cmd);
+ install_element(CONFIG_NODE, &no_interface_cmd);
+ install_element(INTERFACE_NODE, &interface_cmd);
+ install_element(INTERFACE_NODE, &no_interface_cmd);
+ 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_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 00000000..307546e0
--- /dev/null
+++ b/nhrpd/nhrpd.h
@@ -0,0 +1,400 @@
+/* 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 "list.h"
+
+#include "zbuf.h"
+#include "zclient.h"
+#include "debug.h"
+
+#define NHRPD_DEFAULT_HOLDTIME 7200
+
+#define NHRP_VTY_PORT 2612
+#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,
+};
+
+struct notifier_block;
+
+typedef void (*notifier_fn_t)(struct notifier_block *, unsigned long);
+
+struct notifier_block {
+ struct list_head notifier_entry;
+ notifier_fn_t action;
+};
+
+struct notifier_list {
+ struct list_head notifier_head;
+};
+
+#define NOTIFIER_LIST_INITIALIZER(l) \
+ { .notifier_head = LIST_INITIALIZER((l)->notifier_head) }
+
+static inline void notifier_init(struct notifier_list *l)
+{
+ list_init(&l->notifier_head);
+}
+
+static inline void notifier_add(struct notifier_block *n, struct notifier_list *l, notifier_fn_t action)
+{
+ n->action = action;
+ list_add_tail(&n->notifier_entry, &l->notifier_head);
+}
+
+static inline void notifier_del(struct notifier_block *n)
+{
+ list_del(&n->notifier_entry);
+}
+
+static inline void notifier_call(struct notifier_list *l, int cmd)
+{
+ struct notifier_block *n, *nn;
+ list_for_each_entry_safe(n, nn, &l->notifier_head, notifier_entry)
+ n->action(n, cmd);
+}
+
+static inline int notifier_active(struct notifier_list *l)
+{
+ return !list_empty(&l->notifier_head);
+}
+
+struct resolver_query {
+ void (*callback)(struct resolver_query *, int n, union sockunion *);
+};
+
+void resolver_init(void);
+void resolver_resolve(struct resolver_query *query, int af, const char *hostname, void (*cb)(struct resolver_query *, int, union sockunion *));
+
+void nhrp_zebra_init(void);
+void nhrp_zebra_terminate(void);
+
+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_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;
+ uint8_t ipsec;
+ 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 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 {
+ 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;
+ struct nhrp_peer *peer;
+ time_t expires;
+ uint32_t mtu;
+ } 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;
+};
+
+struct nhrp_nhs {
+ struct interface *ifp;
+ struct list_head 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 list_head reglist_head;
+};
+
+#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 linkidx;
+ uint32_t grekey;
+
+ struct hash *peer_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 list_head nhslist_head;
+ } afi[AFI_MAX];
+};
+
+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);
+
+int nhrp_interface_add(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id);
+int nhrp_interface_delete(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id);
+int nhrp_interface_up(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id);
+int nhrp_interface_down(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id);
+int nhrp_interface_address_add(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id);
+int nhrp_interface_address_delete(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id);
+
+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);
+
+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_nhs *nhs);
+void nhrp_nhs_terminate(void);
+
+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(int command, struct zclient *zclient, zebra_size_t length, vrf_id_t vrf_id);
+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);
+
+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_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);
+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_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);
+
+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 *);
+
+#endif
diff --git a/nhrpd/os.h b/nhrpd/os.h
new file mode 100644
index 00000000..0fbe8b00
--- /dev/null
+++ b/nhrpd/os.h
@@ -0,0 +1,5 @@
+
+int os_socket(void);
+int os_sendmsg(const uint8_t *buf, size_t len, int ifindex, const uint8_t *addr, size_t addrlen);
+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 00000000..24b31993
--- /dev/null
+++ b/nhrpd/reqid.c
@@ -0,0 +1,49 @@
+#include "zebra.h"
+#include "hash.h"
+#include "nhrpd.h"
+
+static unsigned int nhrp_reqid_key(void *data)
+{
+ struct nhrp_reqid *r = data;
+ return r->request_id;
+}
+
+static int 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);
+ 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;
+ 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/resolver.c b/nhrpd/resolver.c
new file mode 100644
index 00000000..07bdb735
--- /dev/null
+++ b/nhrpd/resolver.c
@@ -0,0 +1,190 @@
+/* C-Ares integration to Quagga mainloop
+ * 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 <ares.h>
+#include <ares_version.h>
+
+#include "vector.h"
+#include "thread.h"
+#include "nhrpd.h"
+
+struct resolver_state {
+ ares_channel channel;
+ struct thread *timeout;
+ vector read_threads, write_threads;
+};
+
+static struct resolver_state state;
+
+#define THREAD_RUNNING ((struct thread *)-1)
+
+static void resolver_update_timeouts(struct resolver_state *r);
+
+static int resolver_cb_timeout(struct thread *t)
+{
+ struct resolver_state *r = THREAD_ARG(t);
+
+ r->timeout = THREAD_RUNNING;
+ ares_process(r->channel, NULL, NULL);
+ r->timeout = NULL;
+ resolver_update_timeouts(r);
+
+ return 0;
+}
+
+static int resolver_cb_socket_readable(struct thread *t)
+{
+ struct resolver_state *r = THREAD_ARG(t);
+ int fd = THREAD_FD(t);
+
+ vector_set_index(r->read_threads, fd, THREAD_RUNNING);
+ ares_process_fd(r->channel, fd, ARES_SOCKET_BAD);
+ if (vector_lookup(r->read_threads, fd) == THREAD_RUNNING) {
+ t = NULL;
+ THREAD_READ_ON(master, t, resolver_cb_socket_readable, r, fd);
+ vector_set_index(r->read_threads, fd, t);
+ }
+ resolver_update_timeouts(r);
+
+ return 0;
+}
+
+static int resolver_cb_socket_writable(struct thread *t)
+{
+ struct resolver_state *r = THREAD_ARG(t);
+ int fd = THREAD_FD(t);
+
+ vector_set_index(r->write_threads, fd, THREAD_RUNNING);
+ ares_process_fd(r->channel, ARES_SOCKET_BAD, fd);
+ if (vector_lookup(r->write_threads, fd) == THREAD_RUNNING) {
+ t = NULL;
+ THREAD_WRITE_ON(master, t, resolver_cb_socket_writable, r, fd);
+ vector_set_index(r->write_threads, fd, t);
+ }
+ resolver_update_timeouts(r);
+
+ return 0;
+}
+
+static void resolver_update_timeouts(struct resolver_state *r)
+{
+ struct timeval *tv, tvbuf;
+
+ if (r->timeout == THREAD_RUNNING) return;
+
+ THREAD_OFF(r->timeout);
+ tv = ares_timeout(r->channel, NULL, &tvbuf);
+ if (tv) {
+ unsigned int timeoutms = tv->tv_sec * 1000 + tv->tv_usec / 1000;
+ THREAD_TIMER_MSEC_ON(master, r->timeout, resolver_cb_timeout, r, timeoutms);
+ }
+}
+
+static void ares_socket_cb(void *data, ares_socket_t fd, int readable, int writable)
+{
+ struct resolver_state *r = (struct resolver_state *) data;
+ struct thread *t;
+
+ if (readable) {
+ t = vector_lookup_ensure(r->read_threads, fd);
+ if (!t) {
+ THREAD_READ_ON(master, t, resolver_cb_socket_readable, r, fd);
+ vector_set_index(r->read_threads, fd, t);
+ }
+ } else {
+ t = vector_lookup(r->read_threads, fd);
+ if (t) {
+ if (t != THREAD_RUNNING) {
+ THREAD_OFF(t);
+ }
+ vector_unset(r->read_threads, fd);
+ }
+ }
+
+ if (writable) {
+ t = vector_lookup_ensure(r->write_threads, fd);
+ if (!t) {
+ THREAD_READ_ON(master, t, resolver_cb_socket_writable, r, fd);
+ vector_set_index(r->write_threads, fd, t);
+ }
+ } else {
+ t = vector_lookup(r->write_threads, fd);
+ if (t) {
+ if (t != THREAD_RUNNING) {
+ THREAD_OFF(t);
+ }
+ vector_unset(r->write_threads, fd);
+ }
+ }
+}
+
+void resolver_init(void)
+{
+ struct ares_options ares_opts;
+
+ state.read_threads = vector_init(1);
+ state.write_threads = vector_init(1);
+
+ ares_opts = (struct ares_options) {
+ .sock_state_cb = &ares_socket_cb,
+ .sock_state_cb_data = &state,
+ .timeout = 2,
+ .tries = 3,
+ };
+
+ ares_init_options(&state.channel, &ares_opts,
+ ARES_OPT_SOCK_STATE_CB | ARES_OPT_TIMEOUT |
+ ARES_OPT_TRIES);
+}
+
+
+static void ares_address_cb(void *arg, int status, int timeouts, struct hostent *he)
+{
+ struct resolver_query *query = (struct resolver_query *) arg;
+ union sockunion addr[16];
+ size_t i;
+
+ if (status != ARES_SUCCESS) {
+ debugf(NHRP_DEBUG_COMMON, "[%p] Resolving failed", query);
+ query->callback(query, -1, NULL);
+ query->callback = NULL;
+ return;
+ }
+
+ for (i = 0; he->h_addr_list[i] != NULL && i < ZEBRA_NUM_OF(addr); i++) {
+ memset(&addr[i], 0, sizeof(addr[i]));
+ addr[i].sa.sa_family = he->h_addrtype;
+ switch (he->h_addrtype) {
+ case AF_INET:
+ memcpy(&addr[i].sin.sin_addr, (uint8_t *) he->h_addr_list[i], he->h_length);
+ break;
+ case AF_INET6:
+ memcpy(&addr[i].sin6.sin6_addr, (uint8_t *) he->h_addr_list[i], he->h_length);
+ break;
+ }
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "[%p] Resolved with %d results", query, (int) i);
+ query->callback(query, i, &addr[0]);
+ query->callback = NULL;
+}
+
+void resolver_resolve(struct resolver_query *query, int af, const char *hostname, void (*callback)(struct resolver_query *, int, union sockunion *))
+{
+ if (query->callback != NULL) {
+ zlog_err("Trying to resolve '%s', but previous query was not finished yet", hostname);
+ return;
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "[%p] Resolving '%s'", query, hostname);
+
+ query->callback = callback;
+ ares_gethostbyname(state.channel, hostname, af, ares_address_cb, query);
+ resolver_update_timeouts(&state);
+}
diff --git a/nhrpd/vici.c b/nhrpd/vici.c
new file mode 100644
index 00000000..507dd14a
--- /dev/null
+++ b/nhrpd/vici.c
@@ -0,0 +1,482 @@
+/* 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.
+ */
+
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "thread.h"
+#include "zbuf.h"
+#include "log.h"
+#include "nhrpd.h"
+
+#include "vici.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->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->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 int 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_TIMER_ON(master, vici->t_reconnect, vici_reconnect, vici, 2);
+}
+
+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;
+ struct blob val;
+
+ 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);
+ }
+ } else {
+ nhrp_vc_ipsec_updown(sactx->child_uniqueid, 0);
+ }
+ }
+ break;
+ default:
+ switch (key->ptr[0]) {
+ case 'l':
+ if (blob_equal(key, "local-host") && ctx->nsections == 1) {
+ if (blob2buf(val, buf, sizeof(buf)))
+ str2sockunion(buf, &sactx->local.host);
+ } 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)))
+ str2sockunion(buf, &sactx->remote.host);
+ } 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 vici_recv_sa(struct vici_conn *vici, struct zbuf *msg, int event)
+{
+ char buf[32];
+ struct handle_sa_ctx ctx = {
+ .event = event,
+ };
+
+ 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;
+
+ 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_EVENT_UNKNOWN:
+ zlog_err("VICI: StrongSwan does not support mandatory events (unpatched?)");
+ break;
+ case VICI_EVENT_CONFIRM:
+ case VICI_CMD_RESPONSE:
+ break;
+ default:
+ zlog_notice("VICI: Unrecognized message type %d", msgtype);
+ break;
+ }
+}
+
+static int vici_read(struct thread *t)
+{
+ struct vici_conn *vici = THREAD_ARG(t);
+ struct zbuf *ibuf = &vici->ibuf;
+ struct zbuf pktbuf;
+
+ vici->t_read = NULL;
+ if (zbuf_read(ibuf, vici->fd, (size_t) -1) < 0) {
+ vici_connection_error(vici);
+ return 0;
+ }
+
+ /* 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_READ_ON(master, vici->t_read, vici_read, vici, vici->fd);
+ return 0;
+}
+
+static int vici_write(struct thread *t)
+{
+ struct vici_conn *vici = THREAD_ARG(t);
+ int r;
+
+ vici->t_write = NULL;
+ r = zbufq_write(&vici->obuf, vici->fd);
+ if (r > 0) {
+ THREAD_WRITE_ON(master, vici->t_write, vici_write, vici, vici->fd);
+ } else if (r < 0) {
+ vici_connection_error(vici);
+ }
+
+ return 0;
+}
+
+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_WRITE_ON(master, vici->t_write, vici_write, vici, vici->fd);
+}
+
+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;
+ case VICI_END:
+ 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 int vici_reconnect(struct thread *t)
+{
+ struct vici_conn *vici = THREAD_ARG(t);
+ int fd;
+
+ vici->t_reconnect = NULL;
+ if (vici->fd >= 0) return 0;
+
+ fd = sock_open_unix("/var/run/charon.vici");
+ if (fd < 0) {
+ zlog_warn("%s: failure connecting VICI socket: %s",
+ __PRETTY_FUNCTION__, strerror(errno));
+ THREAD_TIMER_ON(master, vici->t_reconnect, vici_reconnect, vici, 2);
+ return 0;
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "VICI: Connected");
+ vici->fd = fd;
+ THREAD_READ_ON(master, vici->t_read, vici_read, vici, vici->fd);
+
+ /* 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);
+
+ return 0;
+}
+
+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_TIMER_MSEC_ON(master, vici->t_reconnect, vici_reconnect, vici, 10);
+}
+
+void vici_terminate(void)
+{
+}
+
+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", 2, "-1",
+ VICI_KEY_VALUE, "async", 1, "1",
+ VICI_KEY_VALUE, "init-limits", 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 (struct sockaddr_un));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, path, strlen (path));
+
+ ret = connect(fd, (struct sockaddr *) &addr, sizeof(addr.sun_family) + strlen(addr.sun_path));
+ if (ret < 0) {
+ close(fd);
+ return -1;
+ }
+
+ fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
+
+ return fd;
+}
diff --git a/nhrpd/vici.h b/nhrpd/vici.h
new file mode 100644
index 00000000..24b900b4
--- /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 00000000..ead7cfd2
--- /dev/null
+++ b/nhrpd/zbuf.c
@@ -0,0 +1,219 @@
+/* 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.
+ */
+
+#define _GNU_SOURCE
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include "zassert.h"
+#include "zbuf.h"
+#include "memory.h"
+#include "memtypes.h"
+#include "nhrpd.h"
+
+#define ERRNO_IO_RETRY(EN) (((EN) == EAGAIN) || ((EN) == EWOULDBLOCK) || ((EN) == EINTR))
+
+struct zbuf *zbuf_alloc(size_t size)
+{
+ struct zbuf *zb;
+
+ zb = XMALLOC(MTYPE_STREAM_DATA, sizeof(*zb) + size);
+ if (!zb)
+ return NULL;
+
+ 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_STREAM_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)
+{
+ zassert((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 (r < 0 && 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 (r < 0 && 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 (r < 0 && 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 = LIST_INITIALIZER(zbq->queue_head),
+ };
+}
+
+void zbufq_reset(struct zbuf_queue *zbq)
+{
+ struct zbuf *buf, *bufn;
+
+ list_for_each_entry_safe(buf, bufn, &zbq->queue_head, queue_list) {
+ list_del(&buf->queue_list);
+ zbuf_free(buf);
+ }
+}
+
+void zbufq_queue(struct zbuf_queue *zbq, struct zbuf *zb)
+{
+ list_add_tail(&zb->queue_list, &zbq->queue_head);
+}
+
+int zbufq_write(struct zbuf_queue *zbq, int fd)
+{
+ struct iovec iov[16];
+ struct zbuf *zb, *zbn;
+ ssize_t r;
+ size_t iovcnt = 0;
+
+ list_for_each_entry_safe(zb, zbn, &zbq->queue_head, queue_list) {
+ iov[iovcnt++] = (struct iovec) {
+ .iov_base = zb->head,
+ .iov_len = zbuf_used(zb),
+ };
+ if (iovcnt >= ZEBRA_NUM_OF(iov))
+ break;
+ }
+
+ r = writev(fd, iov, iovcnt);
+ if (r < 0)
+ return r;
+
+ list_for_each_entry_safe(zb, zbn, &zbq->queue_head, queue_list) {
+ if (r < (ssize_t)zbuf_used(zb)) {
+ zb->head += r;
+ return 1;
+ }
+
+ r -= zbuf_used(zb);
+ list_del(&zb->queue_list);
+ 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);
+}
diff --git a/nhrpd/zbuf.h b/nhrpd/zbuf.h
new file mode 100644
index 00000000..73d70734
--- /dev/null
+++ b/nhrpd/zbuf.h
@@ -0,0 +1,189 @@
+/* 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 "zassert.h"
+#include "list.h"
+
+struct zbuf {
+ struct list_head queue_list;
+ unsigned allocated : 1;
+ unsigned error : 1;
+ uint8_t *buf, *end;
+ uint8_t *head, *tail;
+};
+
+struct zbuf_queue {
+ struct list_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) ((void *)__zbuf_pull(zb, sz, 1))
+#define zbuf_may_pull(zb, type) ((type *)__zbuf_pull(zb, sizeof(type), 0))
+#define zbuf_may_pulln(zb, sz) ((void *)__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) ((void *)__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) ((void *)__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 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 00000000..2216d97e
--- /dev/null
+++ b/nhrpd/znl.c
@@ -0,0 +1,160 @@
+/* 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 <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;
+
+ fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+ 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 00000000..2cd630b5
--- /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);
+
diff --git a/vtysh/Makefile.am b/vtysh/Makefile.am
index e44cd49f..983103ff 100644
--- a/vtysh/Makefile.am
+++ b/vtysh/Makefile.am
@@ -24,6 +24,7 @@ vtysh_cmd_FILES = $(top_srcdir)/bgpd/*.c $(top_srcdir)/isisd/*.c \
$(top_srcdir)/ospfd/*.c $(top_srcdir)/ospf6d/*.c \
$(top_srcdir)/ripd/*.c $(top_srcdir)/ripngd/*.c \
$(top_srcdir)/pimd/pim_cmd.c \
+ $(top_srcdir)/nhrpd/nhrp_vty.c \
$(top_srcdir)/lib/keychain.c $(top_srcdir)/lib/routemap.c \
$(top_srcdir)/lib/filter.c $(top_srcdir)/lib/plist.c \
$(top_srcdir)/lib/distribute.c $(top_srcdir)/lib/if_rmap.c \
diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c
index 2e2203da..1ee8582a 100644
--- a/vtysh/vtysh.c
+++ b/vtysh/vtysh.c
@@ -60,6 +60,7 @@ struct vtysh_client
{ .fd = -1, .name = "bgpd", .flag = VTYSH_BGPD, .path = BGP_VTYSH_PATH},
{ .fd = -1, .name = "isisd", .flag = VTYSH_ISISD, .path = ISIS_VTYSH_PATH},
{ .fd = -1, .name = "pimd", .flag = VTYSH_PIMD, .path = PIM_VTYSH_PATH},
+ { .fd = -1, .name = "nhrpd", .flag = VTYSH_NHRPD, .path = NHRP_VTYSH_PATH},
};


diff --git a/vtysh/vtysh.h b/vtysh/vtysh.h
index 1681a71a..02ff7fd7 100644
--- a/vtysh/vtysh.h
+++ b/vtysh/vtysh.h
@@ -31,9 +31,10 @@
#define VTYSH_ISISD 0x40
#define VTYSH_BABELD 0x80
#define VTYSH_PIMD 0x100
-#define VTYSH_ALL VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_ISISD|VTYSH_BABELD|VTYSH_PIMD
+#define VTYSH_NHRPD 0x200
+#define VTYSH_ALL VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_ISISD|VTYSH_BABELD|VTYSH_PIMD|VTYSH_NHRPD
#define VTYSH_RMAP VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_BABELD
-#define VTYSH_INTERFACE VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_ISISD|VTYSH_BABELD|VTYSH_PIMD
+#define VTYSH_INTERFACE VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_ISISD|VTYSH_BABELD|VTYSH_PIMD|VTYSH_NHRPD

/* vtysh local configuration file. */
#define VTYSH_DEFAULT_CONFIG "vtysh.conf"
diff --git a/zebra/client_main.c b/zebra/client_main.c
index 43ab2997..75c88677 100644
--- a/zebra/client_main.c
+++ b/zebra/client_main.c
@@ -120,6 +120,7 @@ struct zebra_info
{ "ospf", ZEBRA_ROUTE_OSPF },
{ "ospf6", ZEBRA_ROUTE_OSPF6 },
{ "bgp", ZEBRA_ROUTE_BGP },
+ { "nhrp", ZEBRA_ROUTE_NHRP },
{ NULL, 0 }
};

diff --git a/zebra/zebra_rib.c b/zebra/zebra_rib.c
index abb9560a..b03ccc2f 100644
--- a/zebra/zebra_rib.c
+++ b/zebra/zebra_rib.c
@@ -72,6 +72,7 @@ static const struct
[ZEBRA_ROUTE_ISIS] = {ZEBRA_ROUTE_ISIS, 115},
[ZEBRA_ROUTE_BGP] = {ZEBRA_ROUTE_BGP, 20 /* IBGP is 200. */},
[ZEBRA_ROUTE_BABEL] = {ZEBRA_ROUTE_BABEL, 95},
+ [ZEBRA_ROUTE_NHRP] = {ZEBRA_ROUTE_NHRP, 10},
/* no entry/default: 150 */
};

@@ -1423,6 +1424,7 @@ static const u_char meta_queue_map[ZEBRA_ROUTE_MAX] = {
[ZEBRA_ROUTE_BGP] = 3,
[ZEBRA_ROUTE_HSLS] = 4,
[ZEBRA_ROUTE_BABEL] = 2,
+ [ZEBRA_ROUTE_NHRP] = 2,
};

/* Look into the RN and queue it into one or more priority queues,
diff --git a/zebra/zebra_rnh.c b/zebra/zebra_rnh.c
index 5762d3f2..859b6d79 100644
--- a/zebra/zebra_rnh.c
+++ b/zebra/zebra_rnh.c
@@ -223,16 +223,27 @@ zebra_evaluate_rnh_table (vrf_id_t vrfid, int family)
{
if (CHECK_FLAG (rib->status, RIB_ENTRY_REMOVED))
continue;
- if (CHECK_FLAG (rib->status, RIB_ENTRY_SELECTED_FIB))
+ if (! CHECK_FLAG (rib->status, RIB_ENTRY_SELECTED_FIB))
+ continue;
+
+ if (CHECK_FLAG(rnh->flags, ZEBRA_NHT_CONNECTED))
{
- if (CHECK_FLAG(rnh->flags, ZEBRA_NHT_CONNECTED))
+ if (rib->type == ZEBRA_ROUTE_CONNECT)
+ break;
+
+ if (rib->type == ZEBRA_ROUTE_NHRP)
{
- if (rib->type == ZEBRA_ROUTE_CONNECT)
+ struct nexthop *nexthop;
+ for (nexthop = rib->nexthop; nexthop; nexthop = nexthop->next)
+ if (nexthop->type == NEXTHOP_TYPE_IFINDEX ||
+ nexthop->type == NEXTHOP_TYPE_IFNAME)
+ break;
+ if (nexthop)
break;
}
- else
- break;
}
+ else
+ break;
}
}

diff --git a/zebra/zebra_vty.c b/zebra/zebra_vty.c
index 38f61e9d..c73896b3 100644
--- a/zebra/zebra_vty.c
+++ b/zebra/zebra_vty.c
@@ -2121,6 +2121,7 @@ vty_show_ip_route_detail (struct vty *vty, struct route_node *rn, int mcast)
|| rib->type == ZEBRA_ROUTE_OSPF6
|| rib->type == ZEBRA_ROUTE_BABEL
|| rib->type == ZEBRA_ROUTE_ISIS
+ || rib->type == ZEBRA_ROUTE_NHRP
|| rib->type == ZEBRA_ROUTE_BGP)
{
time_t uptime;
@@ -2341,6 +2342,7 @@ vty_show_ip_route (struct vty *vty, struct route_node *rn, struct rib *rib)
|| rib->type == ZEBRA_ROUTE_OSPF6
|| rib->type == ZEBRA_ROUTE_BABEL
|| rib->type == ZEBRA_ROUTE_ISIS
+ || rib->type == ZEBRA_ROUTE_NHRP
|| rib->type == ZEBRA_ROUTE_BGP)
{
time_t uptime;
--
2.11.0
Paul Jakma
2017-01-20 14:59:35 UTC
Permalink
Post by Timo Teräs
This provides DMVPN support and integrates to strongSwan. Please read
README.nhrpd and README.kernel for more details.
Thanks Timo.

Second what Jafar wrote. Great to see good documentation with a
contribution. Proper software engineering.

regards,
--
Paul Jakma | ***@jakma.org | @pjakma | Key ID: 0xD86BF79464A2FF6A
Fortune:
If all the world's economists were laid end to end, we wouldn't reach a
conclusion.
-- William Baumol
Loading...