From e756c7948078bd5109c5b8a0f252851efc4532d6 Mon Sep 17 00:00:00 2001 From: Mark Bryars Date: Fri, 4 May 2012 22:19:13 +0100 Subject: Imported Upstream version 0.13 --- nhrp/nhrp_peer.c | 2106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2106 insertions(+) create mode 100644 nhrp/nhrp_peer.c (limited to 'nhrp/nhrp_peer.c') diff --git a/nhrp/nhrp_peer.c b/nhrp/nhrp_peer.c new file mode 100644 index 0000000..c53d4c4 --- /dev/null +++ b/nhrp/nhrp_peer.c @@ -0,0 +1,2106 @@ +/* nhrp_peer.c - NHRP peer cache implementation + * + * Copyright (C) 2007-2009 Timo Teräs + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 or later as + * published by the Free Software Foundation. + * + * See http://www.gnu.org/ for details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "nhrp_common.h" +#include "nhrp_peer.h" +#include "nhrp_interface.h" + +#define NHRP_PEER_FORMAT_LEN 128 + +#define NHRP_SCRIPT_TIMEOUT (2*60) +#define NHRP_NEGATIVE_CACHE_TIME (3*60) +#define NHRP_EXPIRY_TIME (5*60) + +#define NHRP_HOLDING_TIME_DIVISOR 3 /* See RFC-2332 5.2.3 */ + +#define NHRP_RETRY_REGISTER_TIME (30 + random()/(RAND_MAX/60)) +#define NHRP_RETRY_ERROR_TIME (60 + random()/(RAND_MAX/120)) + +#define NHRP_PEER_FLAG_PRUNE_PENDING 0x00010000 + +const char * const nhrp_peer_type[] = { + [NHRP_PEER_TYPE_INCOMPLETE] = "incomplete", + [NHRP_PEER_TYPE_NEGATIVE] = "negative", + [NHRP_PEER_TYPE_CACHED] = "cached", + [NHRP_PEER_TYPE_SHORTCUT_ROUTE] = "shortcut-route", + [NHRP_PEER_TYPE_DYNAMIC] = "dynamic", + [NHRP_PEER_TYPE_DYNAMIC_NHS] = "dynamic-nhs", + [NHRP_PEER_TYPE_STATIC] = "static", + [NHRP_PEER_TYPE_STATIC_DNS] = "dynamic-map", + [NHRP_PEER_TYPE_LOCAL_ROUTE] = "local-route", + [NHRP_PEER_TYPE_LOCAL_ADDR] = "local", +}; + +static int nhrp_peer_num_total = 0; +static struct list_head local_peer_list = LIST_INITIALIZER(local_peer_list); + +/* Peer entrys life, pending callbacks and their call order are listed + * here. + * + * Generally everything starts from nhrp_peer_insert() call which schedules + * (during startup) or directly invokes nhrp_peer_insert_cb(). + * + * INCOMPLETE: + * 1. nhrp_peer_insert_cb: send resolution request + * 2. nhrp_peer_handle_resolution_reply: entry deleted or reinserted NEGATIVE + * + * NEGATIVE: + * 1. nhrp_peer_insert_cb: schedule task remove + * + * CACHED, STATIC, DYNAMIC, DYNAMIC_NHS: + * 1. nhrp_peer_insert_cb: calls nhrp_peer_restart_cb + * 2. nhrp_peer_restart_cb: resolves dns name, or calls nhrp_run_up_script() + * 3. nhrp_peer_address_query_cb: calls nhrp_peer_run_up_script() + * 4. nhrp_peer_run_up_script: spawns script, or goes to nhrp_peer_lower_is_up() + * 5. nhrp_peer_script_peer_up_done: calls nhrp_peer_lower_is_up() + * 6. nhrp_peer_lower_is_up: sends registration, or goes to nhrp_peer_is_up() + * 7. nhrp_peer_handle_registration_reply: + * a. on success: calls nhrp_peer_is_up() + * b. on error reply: calls nhrp_peer_send_purge_protocol() + * nhrp_peer_handle_purge_protocol_reply: sends new registration + * 8. nhrp_peer_is_up: schedules re-register, expire or deletion + * + * ON EXPIRE: + * schedule remove + * nhrp_peer_renew is called if peer has USED flag set or becomes set, + * while the peer is expired + * ON RENEW: calls sends resolution request, schedule EXPIRE + * + * ON ERROR for CACHED: reinsert as NEGATIVE + * ON ERROR for STATIC: fork peer-down script (if was lower up) + * schedule task request link + * ON ERROR for DYNAMIC: fork peer-down script (if was lower up) + * delete peer + * + * SHORTCUT_ROUTE: + * 1. nhrp_peer_insert_cb: spawns route-up script, or schedules EXPIRE + * + * STATIC_DNS: + * 1. nhrp_peer_insert_cb: calls nhrp_peer_dnsmap_restart_cb + * 2. nhrp_peer_dnsmap_restart_cb: resolves dns name + * 3. nhrp_peer_dnsmap_query_cb: create new peer entries, + * renew existing and delete expired, schedule restart + * + * LOCAL: + * nothing, only netlink code modifies these + */ + +static void nhrp_peer_reinsert(struct nhrp_peer *peer, int type); +static void nhrp_peer_restart_cb(struct ev_timer *w, int revents); +static void nhrp_peer_dnsmap_restart_cb(struct ev_timer *w, int revents); +static void nhrp_peer_remove_cb(struct ev_timer *w, int revents); +static void nhrp_peer_send_resolve(struct nhrp_peer *peer); +static void nhrp_peer_send_register_cb(struct ev_timer *w, int revents); +static void nhrp_peer_expire_cb(struct ev_timer *w, int revents); + +static const char *nhrp_error_indication_text(int ei) +{ + switch (ei) { + case -1: + return "timeout"; + case NHRP_ERROR_UNRECOGNIZED_EXTENSION: + return "unrecognized extension"; + case NHRP_ERROR_LOOP_DETECTED: + return "loop detected"; + case NHRP_ERROR_PROTOCOL_ADDRESS_UNREACHABLE: + return "protocol address unreachable"; + case NHRP_ERROR_PROTOCOL_ERROR: + return "protocol error"; + case NHRP_ERROR_SDU_SIZE_EXCEEDED: + return "SDU size exceeded"; + case NHRP_ERROR_INVALID_EXTENSION: + return "invalid extension"; + case NHRP_ERROR_INVALID_RESOLUTION_REPLY: + return "unexpected resolution reply"; + case NHRP_ERROR_AUTHENTICATION_FAILURE: + return "authentication failure"; + case NHRP_ERROR_HOP_COUNT_EXCEEDED: + return "hop count exceeded"; + } + return "unknown"; +} + +static const char *nhrp_cie_code_text(int ct) +{ + switch (ct) { + case NHRP_CODE_SUCCESS: + return "success"; + case NHRP_CODE_ADMINISTRATIVELY_PROHIBITED: + return "administratively prohibited"; + case NHRP_CODE_INSUFFICIENT_RESOURCES: + return "insufficient resources"; + case NHRP_CODE_NO_BINDING_EXISTS: + return "no binding exists"; + case NHRP_CODE_BINDING_NON_UNIQUE: + return "binding non-unique"; + case NHRP_CODE_UNIQUE_ADDRESS_REGISTERED: + return "unique address already registered"; + } + return "unknown"; +} + +static char *nhrp_peer_format_full(struct nhrp_peer *peer, size_t len, + char *buf, int full) +{ + char tmp[NHRP_PEER_FORMAT_LEN], *str; + int i = 0; + + if (peer == NULL) { + snprintf(buf, len, "(null)"); + return buf; + } + + i += snprintf(&buf[i], len - i, "%s/%d", + nhrp_address_format(&peer->protocol_address, sizeof(tmp), tmp), + peer->prefix_length); + + if (peer->next_hop_address.type != PF_UNSPEC) { + switch (peer->type) { + case NHRP_PEER_TYPE_SHORTCUT_ROUTE: + case NHRP_PEER_TYPE_LOCAL_ROUTE: + str = "nexthop"; + break; + case NHRP_PEER_TYPE_LOCAL_ADDR: + str = "alias"; + break; + default: + str = "nbma"; + break; + } + i += snprintf(&buf[i], len - i, " %s %s", + str, + nhrp_address_format(&peer->next_hop_address, + sizeof(tmp), tmp)); + } + if (peer->nbma_hostname != NULL) { + i += snprintf(&buf[i], len - i, " hostname %s", + peer->nbma_hostname); + } + if (peer->next_hop_nat_oa.type != PF_UNSPEC) { + i += snprintf(&buf[i], len - i, " nbma-nat-oa %s", + nhrp_address_format(&peer->next_hop_nat_oa, + sizeof(tmp), tmp)); + } + if (peer->interface != NULL) + i += snprintf(&buf[i], len - i, " dev %s", + peer->interface->name); + if (peer->mtu) + i += snprintf(&buf[i], len - i, " mtu %d", peer->mtu); + + if (!full) + return buf; + + if (peer->flags & NHRP_PEER_FLAG_USED) + i += snprintf(&buf[i], len - i, " used"); + if (peer->flags & NHRP_PEER_FLAG_UNIQUE) + i += snprintf(&buf[i], len - i, " unique"); + if (peer->flags & NHRP_PEER_FLAG_UP) + i += snprintf(&buf[i], len - i, " up"); + else if (peer->flags & NHRP_PEER_FLAG_LOWER_UP) + i += snprintf(&buf[i], len - i, " lower-up"); + if (peer->expire_time != 0.0) { + int rel; + + rel = peer->expire_time - ev_now(); + if (rel >= 0) { + i += snprintf(&buf[i], len - i, " expires_in %d:%02d", + rel / 60, rel % 60); + } else { + i += snprintf(&buf[i], len - i, " expired"); + } + } + if (peer->flags & NHRP_PEER_FLAG_PRUNE_PENDING) + i += snprintf(&buf[i], len - i, " dying"); + + return buf; +} + +static inline char *nhrp_peer_format(struct nhrp_peer *peer, + size_t len, char *buf) +{ + return nhrp_peer_format_full(peer, len, buf, TRUE); +} + +static inline void nhrp_peer_debug_refcount(const char *func, + struct nhrp_peer *peer) +{ +#if 0 + char tmp[NHRP_PEER_FORMAT_LEN]; + nhrp_debug("%s(%s %s) ref=%d", + func, nhrp_peer_type[peer->type], + nhrp_peer_format(peer, sizeof(tmp), tmp), + peer->ref); +#endif +} + +static void nhrp_peer_resolve_nbma(struct nhrp_peer *peer) +{ + char tmp[64]; + int r; + + if (peer->interface->nbma_address.type == PF_UNSPEC) { + r = kernel_route(NULL, &peer->next_hop_address, + &peer->my_nbma_address, NULL, + &peer->my_nbma_mtu); + if (!r) { + nhrp_error("No route to next hop address %s", + nhrp_address_format(&peer->next_hop_address, + sizeof(tmp), tmp)); + } + } else { + peer->my_nbma_address = peer->interface->nbma_address; + peer->my_nbma_mtu = peer->interface->nbma_mtu; + } +} + +static char *env(const char *key, const char *value) +{ + char *buf; + buf = malloc(strlen(key)+strlen(value)+2); + if (buf == NULL) + return NULL; + sprintf(buf, "%s=%s", key, value); + return buf; +} + +static char *envu32(const char *key, uint32_t value) +{ + char *buf; + buf = malloc(strlen(key)+16); + if (buf == NULL) + return NULL; + sprintf(buf, "%s=%u", key, value); + return buf; +} + +int nhrp_peer_event_ok(union nhrp_peer_event e, int revents) +{ + int status; + + if (revents == 0) + return TRUE; + if (!(revents & EV_CHILD)) + return FALSE; + status = e.child->rstatus; + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) + return TRUE; + return FALSE; +} + +char *nhrp_peer_event_reason(union nhrp_peer_event e, int revents, + size_t buflen, char *buf) +{ + int status; + + if (revents & EV_CHILD) { + status = e.child->rstatus; + if (WIFEXITED(status)) + snprintf(buf, buflen, "exitstatus %d", + WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) + snprintf(buf, buflen, "signal %d", + WTERMSIG(status)); + else + snprintf(buf, buflen, "rstatus %d", status); + } else if (revents & EV_TIMEOUT) { + snprintf(buf, buflen, "timeout"); + } else if (revents == 0) { + snprintf(buf, buflen, "success"); + } else { + snprintf(buf, buflen, "unknown, revents=%x", revents); + } + return buf; +} + +struct nhrp_peer *nhrp_peer_from_event(union nhrp_peer_event e, int revents) +{ + struct nhrp_peer *peer; + + if (revents & EV_CHILD) { + peer = container_of(e.child, struct nhrp_peer, child); + } else if (revents & EV_TIMEOUT) { + peer = container_of(e.timer, struct nhrp_peer, timer); + } else { + NHRP_BUG_ON(revents != 0); + peer = container_of(e.child, struct nhrp_peer, child); + } + + ev_child_stop(&peer->child); + ev_timer_stop(&peer->timer); + + return peer; +} + +void nhrp_peer_run_script(struct nhrp_peer *peer, char *action, + void (*cb)(union nhrp_peer_event, int)) +{ + struct nhrp_interface *iface = peer->interface; + const char *argv[] = { nhrp_script_file, action, NULL }; + char *envp[32]; + char tmp[64]; + pid_t pid; + int i = 0; + + /* Resolve own NBMA address before forking if required + * since it requires traversing peer cache and can trigger + * logging and other stuff. */ + if (peer->my_nbma_address.type == PF_UNSPEC) + nhrp_peer_resolve_nbma(peer); + + /* Fork and execute script */ + pid = fork(); + if (pid == -1) { + if (cb != NULL) + cb(&peer->child, EV_CHILD | EV_ERROR); + return; + } else if (pid > 0) { + if (cb != NULL) { + ev_child_stop(&peer->child); + ev_child_init(&peer->child, cb, pid, 0); + ev_child_start(&peer->child); + + ev_set_cb(&peer->timer, cb); + peer->timer.repeat = NHRP_SCRIPT_TIMEOUT; + ev_timer_again(&peer->timer); + } + return; + } + + envp[i++] = env("NHRP_TYPE", nhrp_peer_type[peer->type]); + if (iface->protocol_address.type != PF_UNSPEC) + envp[i++] = env("NHRP_SRCADDR", + nhrp_address_format(&iface->protocol_address, + sizeof(tmp), tmp)); + if (peer->my_nbma_address.type != PF_UNSPEC) + envp[i++] = env("NHRP_SRCNBMA", + nhrp_address_format(&peer->my_nbma_address, + sizeof(tmp), tmp)); + envp[i++] = env("NHRP_DESTADDR", + nhrp_address_format(&peer->protocol_address, + sizeof(tmp), tmp)); + envp[i++] = envu32("NHRP_DESTPREFIX", peer->prefix_length); + + if (peer->purge_reason) + envp[i++] = env("NHRP_PEER_DOWN_REASON", peer->purge_reason); + + switch (peer->type) { + case NHRP_PEER_TYPE_CACHED: + case NHRP_PEER_TYPE_LOCAL_ADDR: + case NHRP_PEER_TYPE_STATIC: + case NHRP_PEER_TYPE_DYNAMIC: + case NHRP_PEER_TYPE_DYNAMIC_NHS: + envp[i++] = env("NHRP_DESTNBMA", + nhrp_address_format(&peer->next_hop_address, + sizeof(tmp), tmp)); + if (peer->mtu) + envp[i++] = envu32("NHRP_DESTMTU", peer->mtu); + if (peer->next_hop_nat_oa.type != PF_UNSPEC) + envp[i++] = env("NHRP_DESTNBMA_NAT_OA", + nhrp_address_format(&peer->next_hop_nat_oa, + sizeof(tmp), tmp)); + break; + case NHRP_PEER_TYPE_SHORTCUT_ROUTE: + case NHRP_PEER_TYPE_LOCAL_ROUTE: + envp[i++] = env("NHRP_NEXTHOP", + nhrp_address_format(&peer->next_hop_address, + sizeof(tmp), tmp)); + break; + default: + NHRP_BUG_ON("invalid peer type"); + } + envp[i++] = env("NHRP_INTERFACE", peer->interface->name); + envp[i++] = envu32("NHRP_GRE_KEY", peer->interface->gre_key); + envp[i++] = NULL; + + execve(nhrp_script_file, (char **) argv, envp); + exit(1); +} + +void nhrp_peer_cancel_async(struct nhrp_peer *peer) +{ + if (peer->queued_packet) { + nhrp_packet_put(peer->queued_packet); + peer->queued_packet = NULL; + } + if (peer->request) { + nhrp_server_finish_request(peer->request); + peer->request = NULL; + } + + nhrp_address_resolve_cancel(&peer->address_query); + ev_timer_stop(&peer->timer); + if (ev_is_active(&peer->child)) { + kill(SIGINT, peer->child.pid); + ev_child_stop(&peer->child); + } +} + +void nhrp_peer_send_packet_queue(struct nhrp_peer *peer) +{ + if (peer->queued_packet == NULL) + return; + + nhrp_packet_marshall_and_send(peer->queued_packet); + nhrp_packet_put(peer->queued_packet); + peer->queued_packet = NULL; +} + +static void nhrp_peer_schedule(struct nhrp_peer *peer, ev_tstamp timeout, + void (*cb)(struct ev_timer *w, int revents)) +{ + ev_timer_stop(&peer->timer); + ev_timer_init(&peer->timer, cb, timeout, 0.); + ev_timer_start(&peer->timer); +} + +static void nhrp_peer_restart_error(struct nhrp_peer *peer) +{ + switch (peer->type) { + case NHRP_PEER_TYPE_STATIC: + case NHRP_PEER_TYPE_DYNAMIC_NHS: + nhrp_peer_schedule(peer, NHRP_RETRY_ERROR_TIME, + nhrp_peer_restart_cb); + break; + default: + nhrp_peer_reinsert(peer, NHRP_PEER_TYPE_NEGATIVE); + break; + } +} + +static void nhrp_peer_script_route_up_done(union nhrp_peer_event e, int revents) +{ + struct nhrp_peer *peer = nhrp_peer_from_event(e, revents); + char tmp[64], reason[32]; + + if (nhrp_peer_event_ok(e, revents)) { + if (revents) + nhrp_debug("[%s] Route up script: success", + nhrp_address_format(&peer->protocol_address, + sizeof(tmp), tmp)); + + peer->flags |= NHRP_PEER_FLAG_UP; + nhrp_peer_schedule(peer, peer->expire_time - NHRP_EXPIRY_TIME + - 10 - ev_now(), nhrp_peer_expire_cb); + } else { + nhrp_info("[%s] Route up script: %s; " + "adding negative cached entry", + nhrp_address_format(&peer->protocol_address, + sizeof(tmp), tmp), + nhrp_peer_event_reason(e, revents, + sizeof(reason), reason)); + + nhrp_peer_reinsert(peer, NHRP_PEER_TYPE_NEGATIVE); + } +} + +static int nhrp_peer_routes_up(void *ctx, struct nhrp_peer *peer) +{ + if (!(peer->flags & NHRP_PEER_FLAG_UP)) + nhrp_peer_run_script(peer, "route-up", + nhrp_peer_script_route_up_done); + + return 0; +} + +static int nhrp_peer_routes_renew(void *ctx, struct nhrp_peer *peer) +{ + int *num_routes = (int *) ctx; + + if (peer->flags & NHRP_PEER_FLAG_PRUNE_PENDING) { + peer->flags &= ~NHRP_PEER_FLAG_PRUNE_PENDING; + nhrp_peer_cancel_async(peer); + nhrp_peer_send_resolve(peer); + (*num_routes)++; + } + + return 0; +} + +static void nhrp_peer_renew(struct nhrp_peer *peer) +{ + struct nhrp_interface *iface = peer->interface; + struct nhrp_peer_selector sel; + int num_routes = 0; + + /* Renew the cached information: all related routes + * or the peer itself */ + if (peer->type != NHRP_PEER_TYPE_SHORTCUT_ROUTE) { + memset(&sel, 0, sizeof(sel)); + sel.flags = NHRP_PEER_FIND_UP; + sel.type_mask = BIT(NHRP_PEER_TYPE_SHORTCUT_ROUTE); + sel.interface = iface; + sel.next_hop_address = peer->protocol_address; + nhrp_peer_foreach(nhrp_peer_routes_renew, &num_routes, &sel); + } + + if (peer->flags & NHRP_PEER_FLAG_PRUNE_PENDING) { + peer->flags &= ~NHRP_PEER_FLAG_PRUNE_PENDING; + nhrp_peer_cancel_async(peer); + nhrp_peer_send_resolve(peer); + } +} + +static int is_used(void *ctx, struct nhrp_peer *peer) +{ + if (peer->flags & NHRP_PEER_FLAG_USED) + return 1; + + return 0; +} + +static void nhrp_peer_expire_cb(struct ev_timer *w, int revents) +{ + struct nhrp_peer *peer = container_of(w, struct nhrp_peer, timer); + struct nhrp_peer_selector sel; + int used; + + peer->flags |= NHRP_PEER_FLAG_PRUNE_PENDING; + nhrp_peer_schedule(peer, peer->expire_time - ev_now(), + nhrp_peer_remove_cb); + + if (peer->type == NHRP_PEER_TYPE_SHORTCUT_ROUTE) { + memset(&sel, 0, sizeof(sel)); + sel.interface = peer->interface; + sel.protocol_address = peer->next_hop_address; + used = nhrp_peer_foreach(is_used, NULL, &sel); + } else + used = peer->flags & NHRP_PEER_FLAG_USED; + + if (used) + nhrp_peer_renew(peer); +} + +static void nhrp_peer_is_down(struct nhrp_peer *peer) +{ + struct nhrp_peer_selector sel; + + /* Remove UP flags if not being removed permanently, so futher + * lookups are valid */ + if (!(peer->flags & NHRP_PEER_FLAG_REMOVED)) + peer->flags &= ~(NHRP_PEER_FLAG_LOWER_UP | NHRP_PEER_FLAG_UP); + + /* Check if there are routes using this peer as next-hop */ + if (peer->type != NHRP_PEER_TYPE_SHORTCUT_ROUTE) { + memset(&sel, 0, sizeof(sel)); + sel.type_mask = BIT(NHRP_PEER_TYPE_SHORTCUT_ROUTE); + sel.interface = peer->interface; + sel.next_hop_address = peer->protocol_address; + nhrp_peer_foreach(nhrp_peer_remove_matching, NULL, &sel); + } + + /* Remove from lists */ + if (list_hashed(&peer->mcast_list_entry)) + list_del(&peer->mcast_list_entry); + if (hlist_hashed(&peer->nbma_hash_entry)) + hlist_del(&peer->nbma_hash_entry); +} + +static void nhrp_peer_is_up(struct nhrp_peer *peer) +{ + struct nhrp_interface *iface = peer->interface; + struct nhrp_peer_selector sel; + int mcast = 0, i; + char tmp[64]; + + if ((peer->flags & (NHRP_PEER_FLAG_UP | NHRP_PEER_FLAG_REGISTER)) + == NHRP_PEER_FLAG_REGISTER) { + /* First time registration reply received */ + nhrp_peer_run_script(peer, "nhs-up", NULL); + } + + /* Remove from mcast list if previously there */ + if (list_hashed(&peer->mcast_list_entry)) + list_del(&peer->mcast_list_entry); + + /* Check if this one needs multicast traffic */ + if (BIT(peer->type) & iface->mcast_mask) { + mcast = 1; + } else { + for (i = 0; i < iface->mcast_numaddr; i++) { + if (!nhrp_address_cmp(&peer->protocol_address, + &iface->mcast_addr[i])) { + mcast = 1; + break; + } + } + } + + if (mcast) { + list_add(&peer->mcast_list_entry, &iface->mcast_list); + nhrp_info("[%s] Peer inserted to multicast list", + nhrp_address_format(&peer->protocol_address, + sizeof(tmp), tmp)); + } + + /* Searchable by NBMA */ + if (hlist_hashed(&peer->nbma_hash_entry)) + hlist_del(&peer->nbma_hash_entry); + if (BIT(peer->type) & (BIT(NHRP_PEER_TYPE_CACHED) | + BIT(NHRP_PEER_TYPE_DYNAMIC) | + BIT(NHRP_PEER_TYPE_DYNAMIC_NHS) | + BIT(NHRP_PEER_TYPE_STATIC))) { + i = nhrp_address_hash(&peer->next_hop_address) % NHRP_INTERFACE_NBMA_HASH_SIZE; + hlist_add_head(&peer->nbma_hash_entry, &iface->nbma_hash[i]); + } + + peer->flags |= NHRP_PEER_FLAG_UP | NHRP_PEER_FLAG_LOWER_UP; + + /* Check if there are routes using this peer as next-hop*/ + if (peer->type != NHRP_PEER_TYPE_SHORTCUT_ROUTE) { + memset(&sel, 0, sizeof(sel)); + sel.type_mask = BIT(NHRP_PEER_TYPE_SHORTCUT_ROUTE); + sel.interface = iface; + sel.next_hop_address = peer->protocol_address; + nhrp_peer_foreach(nhrp_peer_routes_up, NULL, &sel); + } + + nhrp_peer_send_packet_queue(peer); + + /* Schedule expiry or renewal */ + switch (peer->type) { + case NHRP_PEER_TYPE_DYNAMIC: + nhrp_peer_schedule(peer, peer->expire_time - ev_now(), + nhrp_peer_remove_cb); + break; + case NHRP_PEER_TYPE_CACHED: + nhrp_peer_schedule( + peer, + peer->expire_time - NHRP_EXPIRY_TIME - ev_now(), + nhrp_peer_expire_cb); + break; + case NHRP_PEER_TYPE_STATIC: + case NHRP_PEER_TYPE_DYNAMIC_NHS: + if (peer->flags & NHRP_PEER_FLAG_REGISTER) { + nhrp_peer_schedule( + peer, iface->holding_time / + NHRP_HOLDING_TIME_DIVISOR + 1, + nhrp_peer_send_register_cb); + } + break; + default: + NHRP_BUG_ON("invalid peer type"); + break; + } +} + +static void nhrp_peer_lower_is_up(struct nhrp_peer *peer) +{ + peer->flags |= NHRP_PEER_FLAG_LOWER_UP; + + if (peer->flags & NHRP_PEER_FLAG_REGISTER) + nhrp_peer_send_register_cb(&peer->timer, 0); + else + nhrp_peer_is_up(peer); +} + +static void nhrp_peer_script_peer_up_done(union nhrp_peer_event e, int revents) +{ + struct nhrp_peer *peer = nhrp_peer_from_event(e, revents); + char tmp[64], reason[32]; + + if (nhrp_peer_event_ok(e, revents)) { + nhrp_debug("[%s] Peer up script: success", + nhrp_address_format(&peer->protocol_address, + sizeof(tmp), tmp)); + + kernel_inject_neighbor(&peer->protocol_address, + &peer->next_hop_address, + peer->interface); + nhrp_peer_lower_is_up(peer); + } else { + nhrp_error("[%s] Peer up script failed: %s", + nhrp_address_format(&peer->protocol_address, + sizeof(tmp), tmp), + nhrp_peer_event_reason(e, revents, + sizeof(reason), reason)); + nhrp_peer_restart_error(peer); + } +} + +static void nhrp_peer_run_up_script(struct nhrp_peer *peer) +{ + nhrp_peer_run_script(peer, "peer-up", + nhrp_peer_script_peer_up_done); +} + +static void nhrp_peer_address_query_cb(struct nhrp_address_query *query, + int num_addr, struct nhrp_address *addrs) +{ + struct nhrp_peer *peer = container_of(query, struct nhrp_peer, + address_query); + char host[64]; + + if (num_addr > 0) { + nhrp_info("Resolved '%s' as %s", + peer->nbma_hostname, + nhrp_address_format(&addrs[0], sizeof(host), host)); + peer->next_hop_address = addrs[0]; + peer->afnum = nhrp_afnum_from_pf(peer->next_hop_address.type); + nhrp_peer_run_up_script(peer); + } else { + nhrp_error("Failed to resolve '%s'", peer->nbma_hostname); + nhrp_peer_restart_error(peer); + } +} + +static void nhrp_peer_restart_cb(struct ev_timer *w, int revents) +{ + struct nhrp_peer *peer = container_of(w, struct nhrp_peer, timer); + + if (peer->nbma_hostname != NULL) { + nhrp_address_resolve(&peer->address_query, + peer->nbma_hostname, + nhrp_peer_address_query_cb); + } else { + nhrp_peer_resolve_nbma(peer); + + if (!(peer->flags & NHRP_PEER_FLAG_LOWER_UP)) + nhrp_peer_run_up_script(peer); + else + nhrp_peer_script_peer_up_done(&peer->child, 0); + } +} + +static void nhrp_peer_send_protocol_purge(struct nhrp_peer *peer) +{ + char tmp[64]; + struct nhrp_packet *packet; + struct nhrp_cie *cie; + struct nhrp_payload *payload; + int sent = FALSE; + + packet = nhrp_packet_alloc(); + if (packet == NULL) + goto error; + + packet->hdr = (struct nhrp_packet_header) { + .afnum = peer->afnum, + .protocol_type = peer->protocol_type, + .version = NHRP_VERSION_RFC2332, + .type = NHRP_PACKET_PURGE_REQUEST, + .hop_count = NHRP_PACKET_DEFAULT_HOP_COUNT, + .flags = NHRP_FLAG_PURGE_NO_REPLY, + }; + if (peer->flags & NHRP_PEER_FLAG_CISCO) { + /* Cisco IOS seems to require reqistration and purge + * request id to match, so we need to used a fixed + * value. This is in violation of RFC, though. */ + packet->hdr.u.request_id = + nhrp_address_hash(&peer->interface->protocol_address); + } + packet->dst_protocol_address = peer->protocol_address; + + /* Payload CIE */ + cie = nhrp_cie_alloc(); + if (cie == NULL) + goto error_free_packet; + + *cie = (struct nhrp_cie) { + .hdr.code = NHRP_CODE_SUCCESS, + .hdr.mtu = 0, + .hdr.preference = 0, + .hdr.prefix_length = 0xff, + }; + cie->protocol_address = peer->interface->protocol_address; + + payload = nhrp_packet_payload(packet, NHRP_PAYLOAD_TYPE_CIE_LIST); + nhrp_payload_add_cie(payload, cie); + + nhrp_info("Sending Purge Request (of protocol address) to %s", + nhrp_address_format(&peer->protocol_address, + sizeof(tmp), tmp)); + + packet->dst_peer = nhrp_peer_get(peer); + packet->dst_iface = peer->interface; + sent = nhrp_packet_send(packet); +error_free_packet: + nhrp_packet_put(packet); +error: + if (sent) + nhrp_peer_schedule(peer, 2, nhrp_peer_send_register_cb); + else + nhrp_peer_restart_error(peer); +} + +static int nhrp_add_local_route_cie(void *ctx, struct nhrp_peer *route) +{ + struct nhrp_packet *packet = (struct nhrp_packet *) ctx; + struct nhrp_payload *payload; + struct nhrp_cie *cie; + + if (route->interface != NULL && + !(route->interface->flags & NHRP_INTERFACE_FLAG_SHORTCUT_DEST)) + return 0; + + cie = nhrp_cie_alloc(); + if (cie == NULL) + return 0; + + *cie = (struct nhrp_cie) { + .hdr.code = 0, + .hdr.prefix_length = route->prefix_length, + .protocol_address = route->protocol_address, + }; + + payload = nhrp_packet_payload(packet, NHRP_PAYLOAD_TYPE_CIE_LIST); + nhrp_payload_add_cie(payload, cie); + + return 0; +} + +int nhrp_peer_discover_nhs(struct nhrp_peer *peer, + struct nhrp_address *newaddr) +{ + struct nhrp_peer_selector sel; + char tmp[32], tmp2[32]; + + if (nhrp_address_cmp(&peer->protocol_address, newaddr) == 0) + return TRUE; + + if (peer->type != NHRP_PEER_TYPE_DYNAMIC_NHS || + !nhrp_address_is_network(&peer->protocol_address, + peer->prefix_length)) { + nhrp_error("Unexpected NHS protocol address change %s -> %s", + nhrp_address_format(&peer->protocol_address, + sizeof(tmp2), tmp2), + nhrp_address_format(newaddr, sizeof(tmp), tmp)); + return FALSE; + } + + if (nhrp_address_prefix_cmp(&peer->protocol_address, newaddr, + peer->prefix_length) != 0) { + nhrp_error("Protocol address change to %s is not within %s/%d", + nhrp_address_format(newaddr, sizeof(tmp), tmp), + nhrp_address_format(&peer->protocol_address, + sizeof(tmp2), tmp2), + peer->prefix_length); + return FALSE; + } + + /* Remove incomplete/cached entries */ + memset(&sel, 0, sizeof(sel)); + sel.flags = NHRP_PEER_FIND_EXACT; + sel.type_mask = NHRP_PEER_TYPEMASK_REMOVABLE; + sel.interface = peer->interface; + sel.protocol_address = *newaddr; + nhrp_peer_foreach(nhrp_peer_remove_matching, NULL, &sel); + + /* Update protocol address */ + peer->protocol_address = *newaddr; + + return TRUE; +} + +static void nhrp_peer_handle_registration_reply(void *ctx, + struct nhrp_packet *reply) +{ + struct nhrp_peer *peer = (struct nhrp_peer *) ctx; + struct nhrp_payload *payload; + struct nhrp_cie *cie; + struct nhrp_packet *packet; + char tmp[NHRP_PEER_FORMAT_LEN]; + int ec = -1; + + if (peer->flags & NHRP_PEER_FLAG_REMOVED) + goto ret; + + if (reply == NULL || + reply->hdr.type != NHRP_PACKET_REGISTRATION_REPLY) { + ec = reply ? reply->hdr.u.error.code : -1; + nhrp_info("Failed to register to %s: %s (%d)", + nhrp_address_format(&peer->protocol_address, + sizeof(tmp), tmp), + nhrp_error_indication_text(ec), ntohs(ec)); + + if (ec == NHRP_ERROR_HOP_COUNT_EXCEEDED) + nhrp_peer_discover_nhs(peer, + &reply->src_protocol_address); + + if (reply != NULL) { + nhrp_peer_schedule(peer, NHRP_RETRY_REGISTER_TIME, + nhrp_peer_send_register_cb); + } else { + nhrp_peer_restart_error(peer); + } + goto ret; + } + + /* Check servers protocol address */ + if (!nhrp_peer_discover_nhs(peer, &reply->dst_protocol_address)) { + nhrp_peer_restart_error(peer); + goto ret; + } + + /* Check result */ + payload = nhrp_packet_payload(reply, NHRP_PAYLOAD_TYPE_CIE_LIST); + if (payload != NULL) { + cie = nhrp_payload_get_cie(payload, 1); + if (cie != NULL) + ec = cie->hdr.code; + } + + nhrp_info("Received Registration Reply from %s: %s", + nhrp_address_format(&peer->protocol_address, + sizeof(tmp), tmp), + nhrp_cie_code_text(ec)); + + switch (ec) { + case NHRP_CODE_SUCCESS: + break; + case NHRP_CODE_UNIQUE_ADDRESS_REGISTERED: + nhrp_peer_send_protocol_purge(peer); + goto ret; + default: + nhrp_peer_schedule(peer, NHRP_RETRY_REGISTER_TIME, + nhrp_peer_send_register_cb); + goto ret; + } + + /* Check for NAT */ + payload = nhrp_packet_extension(reply, + NHRP_EXTENSION_NAT_ADDRESS | + NHRP_EXTENSION_FLAG_NOCREATE, + NHRP_PAYLOAD_TYPE_CIE_LIST); + if (payload != NULL) { + cie = nhrp_payload_get_cie(payload, 2); + if (cie != NULL) { + nhrp_info("NAT detected: our real NBMA address is %s", + nhrp_address_format(&cie->nbma_address, + sizeof(tmp), tmp)); + peer->interface->nat_cie = *cie; + } + } + + /* If not re-registration, send a purge request for each subnet + * we accept shortcuts to, to clear server redirection cache. */ + if (!(peer->flags & NHRP_PEER_FLAG_UP) && + (packet = nhrp_packet_alloc()) != NULL) { + struct nhrp_peer_selector sel; + + packet->hdr = (struct nhrp_packet_header) { + .afnum = peer->afnum, + .protocol_type = peer->protocol_type, + .version = NHRP_VERSION_RFC2332, + .type = NHRP_PACKET_PURGE_REQUEST, + .hop_count = NHRP_PACKET_DEFAULT_HOP_COUNT, + }; + packet->dst_protocol_address = peer->protocol_address; + + memset(&sel, 0, sizeof(sel)); + sel.type_mask = BIT(NHRP_PEER_TYPE_LOCAL_ADDR); + nhrp_peer_foreach(nhrp_add_local_route_cie, packet, &sel); + + nhrp_packet_extension(packet, + NHRP_EXTENSION_FORWARD_TRANSIT_NHS | + NHRP_EXTENSION_FLAG_COMPULSORY, + NHRP_PAYLOAD_TYPE_CIE_LIST); + nhrp_packet_extension(packet, + NHRP_EXTENSION_REVERSE_TRANSIT_NHS | + NHRP_EXTENSION_FLAG_COMPULSORY, + NHRP_PAYLOAD_TYPE_CIE_LIST); + nhrp_packet_extension(packet, + NHRP_EXTENSION_RESPONDER_ADDRESS | + NHRP_EXTENSION_FLAG_COMPULSORY, + NHRP_PAYLOAD_TYPE_CIE_LIST); + + nhrp_info("Sending Purge Request (of local routes) to %s", + nhrp_address_format(&peer->protocol_address, + sizeof(tmp), tmp)); + + packet->dst_peer = nhrp_peer_get(peer); + packet->dst_iface = peer->interface; + nhrp_packet_send_request(packet, NULL, NULL); + nhrp_packet_put(packet); + } + + /* Re-register after holding time expires */ + nhrp_peer_is_up(peer); +ret: + nhrp_peer_put(peer); +} + +static void nhrp_peer_send_register_cb(struct ev_timer *w, int revents) +{ + struct nhrp_peer *peer = container_of(w, struct nhrp_peer, timer); + char dst[64]; + struct nhrp_packet *packet; + struct nhrp_cie *cie; + struct nhrp_payload *payload; + int sent = FALSE; + + packet = nhrp_packet_alloc(); + if (packet == NULL) + goto error; + + packet->hdr = (struct nhrp_packet_header) { + .afnum = peer->afnum, + .protocol_type = peer->protocol_type, + .version = NHRP_VERSION_RFC2332, + .type = NHRP_PACKET_REGISTRATION_REQUEST, + .hop_count = NHRP_PACKET_DEFAULT_HOP_COUNT, + .flags = NHRP_FLAG_REGISTRATION_UNIQUE | + NHRP_FLAG_REGISTRATION_NAT + }; + if (peer->flags & NHRP_PEER_FLAG_CISCO) { + /* Cisco IOS seems to require reqistration and purge + * request id to match, so we need to used a fixed + * value. This is in violation of RFC, though. */ + packet->hdr.u.request_id = + nhrp_address_hash(&peer->interface->protocol_address); + } + packet->dst_protocol_address = peer->protocol_address; + + if (peer->type == NHRP_PEER_TYPE_DYNAMIC_NHS && + nhrp_address_is_network(&peer->protocol_address, + peer->prefix_length)) { + /* We are not yet sure of the protocol address of the NHS - + * send registration to the broadcast address with one hop + * limit. Except the NHS to reply with it's real protocol + * address. */ + nhrp_address_set_broadcast(&packet->dst_protocol_address, + peer->prefix_length); + packet->hdr.hop_count = 0; + } + + + /* Payload CIE */ + cie = nhrp_cie_alloc(); + if (cie == NULL) + goto error; + + *cie = (struct nhrp_cie) { + .hdr.code = NHRP_CODE_SUCCESS, + .hdr.prefix_length = 0xff, + .hdr.mtu = htons(peer->my_nbma_mtu), + .hdr.holding_time = htons(peer->interface->holding_time), + .hdr.preference = 0, + }; + + payload = nhrp_packet_payload(packet, NHRP_PAYLOAD_TYPE_CIE_LIST); + nhrp_payload_add_cie(payload, cie); + + /* Standard extensions */ + nhrp_packet_extension(packet, + NHRP_EXTENSION_FORWARD_TRANSIT_NHS | + NHRP_EXTENSION_FLAG_COMPULSORY, + NHRP_PAYLOAD_TYPE_CIE_LIST); + nhrp_packet_extension(packet, + NHRP_EXTENSION_REVERSE_TRANSIT_NHS | + NHRP_EXTENSION_FLAG_COMPULSORY, + NHRP_PAYLOAD_TYPE_CIE_LIST); + nhrp_packet_extension(packet, + NHRP_EXTENSION_RESPONDER_ADDRESS | + NHRP_EXTENSION_FLAG_COMPULSORY, + NHRP_PAYLOAD_TYPE_CIE_LIST); + + /* Cisco NAT extension CIE */ + cie = nhrp_cie_alloc(); + if (cie == NULL) + goto error_free_packet; + + *cie = (struct nhrp_cie) { + .hdr.code = NHRP_CODE_SUCCESS, + .hdr.prefix_length = peer->protocol_address.addr_len * 8, + .hdr.preference = 0, + .nbma_address = peer->next_hop_address, + .protocol_address = peer->protocol_address, + }; + + payload = nhrp_packet_extension(packet, NHRP_EXTENSION_NAT_ADDRESS, + NHRP_PAYLOAD_TYPE_CIE_LIST); + nhrp_payload_add_cie(payload, cie); + + nhrp_info("Sending Registration Request to %s (my mtu=%d)", + nhrp_address_format(&peer->protocol_address, + sizeof(dst), dst), + peer->my_nbma_mtu); + + packet->dst_peer = nhrp_peer_get(peer); + packet->dst_iface = peer->interface; + sent = nhrp_packet_send_request(packet, + nhrp_peer_handle_registration_reply, + nhrp_peer_get(peer)); + +error_free_packet: + nhrp_packet_put(packet); +error: + if (!sent) + nhrp_peer_restart_error(peer); +} + +static int error_on_matching(void *ctx, struct nhrp_peer *peer) +{ + return 1; +} + +static void nhrp_peer_handle_resolution_reply(void *ctx, + struct nhrp_packet *reply) +{ + struct nhrp_peer *peer = (struct nhrp_peer *) ctx, *np; + struct nhrp_payload *payload; + struct nhrp_cie *cie, *natcie = NULL, *natoacie = NULL; + struct nhrp_interface *iface; + struct nhrp_peer_selector sel; + char dst[64], tmp[64], nbma[64]; + int ec; + + if (peer->flags & NHRP_PEER_FLAG_REMOVED) + goto ret; + + if (reply == NULL || + reply->hdr.type != NHRP_PACKET_RESOLUTION_REPLY) { + ec = reply ? reply->hdr.u.error.code : -1; + + nhrp_info("Failed to resolve %s: %s (%d)", + nhrp_address_format(&peer->protocol_address, + sizeof(tmp), tmp), + nhrp_error_indication_text(ec), ntohs(ec)); + + if (reply != NULL) { + /* We got reply that this address is not available - + * negative cache it. */ + peer->flags |= NHRP_PEER_FLAG_UP; + nhrp_peer_reinsert(peer, NHRP_PEER_TYPE_NEGATIVE); + } else { + /* Time out - NHS reachable, or packet lost multiple + * times. Keep trying if still needed. */ + nhrp_peer_remove(peer); + } + goto ret; + } + + payload = nhrp_packet_payload(reply, NHRP_PAYLOAD_TYPE_CIE_LIST); + cie = list_next(&payload->u.cie_list, struct nhrp_cie, cie_list_entry); + if (cie == NULL) + goto ret; + + nhrp_info("Received Resolution Reply %s/%d is at proto %s nbma %s", + nhrp_address_format(&peer->protocol_address, + sizeof(dst), dst), + cie->hdr.prefix_length, + nhrp_address_format(&cie->protocol_address, + sizeof(tmp), tmp), + nhrp_address_format(&cie->nbma_address, + sizeof(nbma), nbma)); + + payload = nhrp_packet_extension(reply, + NHRP_EXTENSION_NAT_ADDRESS | + NHRP_EXTENSION_FLAG_NOCREATE, + NHRP_PAYLOAD_TYPE_CIE_LIST); + if ((reply->hdr.flags & NHRP_FLAG_RESOLUTION_NAT) && + (payload != NULL)) { + natcie = list_next(&payload->u.cie_list, struct nhrp_cie, cie_list_entry); + if (natcie != NULL) { + natoacie = cie; + nhrp_info("NAT detected: really at proto %s nbma %s", + nhrp_address_format(&natcie->protocol_address, + sizeof(tmp), tmp), + nhrp_address_format(&natcie->nbma_address, + sizeof(nbma), nbma)); + } + } + if (natcie == NULL) + natcie = cie; + + if (nhrp_address_cmp(&peer->protocol_address, &cie->protocol_address) + == 0) { + /* Destination is within NBMA network; update cache */ + peer->mtu = ntohs(cie->hdr.mtu); + peer->prefix_length = cie->hdr.prefix_length; + peer->next_hop_address = natcie->nbma_address; + if (natoacie != NULL) + peer->next_hop_nat_oa = natoacie->nbma_address; + peer->expire_time = ev_now() + ntohs(cie->hdr.holding_time); + nhrp_address_set_network(&peer->protocol_address, + peer->prefix_length); + nhrp_peer_reinsert(peer, NHRP_PEER_TYPE_CACHED); + goto ret; + } + + /* Check that we won't replace a local address */ + sel = (struct nhrp_peer_selector) { + .flags = NHRP_PEER_FIND_EXACT, + .type_mask = BIT(NHRP_PEER_TYPE_LOCAL_ADDR), + .protocol_address = peer->protocol_address, + .prefix_length = cie->hdr.prefix_length, + }; + if (nhrp_peer_foreach(error_on_matching, NULL, &sel)) { + nhrp_error("Local route %s/%d exists: not replacing " + "with shortcut", + nhrp_address_format(&peer->protocol_address, + sizeof(tmp), tmp), + cie->hdr.prefix_length); + peer->flags |= NHRP_PEER_FLAG_UP; + nhrp_peer_reinsert(peer, NHRP_PEER_TYPE_NEGATIVE); + goto ret; + } + + /* Update the received NBMA address to nexthop */ + iface = peer->interface; + np = nhrp_peer_route(iface, &cie->protocol_address, + NHRP_PEER_FIND_EXACT, 0); + if (np == NULL) { + np = nhrp_peer_alloc(iface); + np->type = NHRP_PEER_TYPE_CACHED; + np->afnum = reply->hdr.afnum; + np->protocol_type = reply->hdr.protocol_type; + np->protocol_address = cie->protocol_address; + np->next_hop_address = natcie->nbma_address; + if (natoacie != NULL) + np->next_hop_nat_oa = natoacie->nbma_address; + np->mtu = ntohs(cie->hdr.mtu); + np->prefix_length = cie->protocol_address.addr_len * 8; + np->expire_time = ev_now() + ntohs(cie->hdr.holding_time); + nhrp_peer_insert(np); + nhrp_peer_put(np); + } + + /* Off NBMA destination; a shortcut route */ + np = nhrp_peer_alloc(iface); + np->type = NHRP_PEER_TYPE_SHORTCUT_ROUTE; + np->afnum = reply->hdr.afnum; + np->protocol_type = reply->hdr.protocol_type; + np->protocol_address = peer->protocol_address; + np->prefix_length = cie->hdr.prefix_length; + np->next_hop_address = cie->protocol_address; + np->expire_time = ev_now() + ntohs(cie->hdr.holding_time); + nhrp_address_set_network(&np->protocol_address, np->prefix_length); + nhrp_peer_insert(np); + nhrp_peer_put(np); + + /* Delete the incomplete entry */ + nhrp_peer_remove(peer); +ret: + nhrp_peer_put(peer); +} + +static void nhrp_peer_send_resolve(struct nhrp_peer *peer) +{ + char dst[64]; + struct nhrp_packet *packet; + struct nhrp_cie *cie; + struct nhrp_payload *payload; + + packet = nhrp_packet_alloc(); + if (packet == NULL) + goto error; + + packet->hdr = (struct nhrp_packet_header) { + .afnum = peer->afnum, + .protocol_type = peer->protocol_type, + .version = NHRP_VERSION_RFC2332, + .type = NHRP_PACKET_RESOLUTION_REQUEST, + .hop_count = NHRP_PACKET_DEFAULT_HOP_COUNT, + .flags = NHRP_FLAG_RESOLUTION_SOURCE_IS_ROUTER | + NHRP_FLAG_RESOLUTION_AUTHORATIVE | + NHRP_FLAG_RESOLUTION_NAT + }; + packet->dst_protocol_address = peer->protocol_address; + + /* Payload CIE */ + cie = nhrp_cie_alloc(); + if (cie == NULL) + goto error; + + *cie = (struct nhrp_cie) { + .hdr.code = NHRP_CODE_SUCCESS, + .hdr.prefix_length = 0, + .hdr.mtu = 0, + .hdr.holding_time = htons(peer->interface->holding_time), + }; + + payload = nhrp_packet_payload(packet, NHRP_PAYLOAD_TYPE_CIE_LIST); + nhrp_payload_add_cie(payload, cie); + + nhrp_info("Sending Resolution Request to %s", + nhrp_address_format(&peer->protocol_address, + sizeof(dst), dst)); + + /* Standard extensions */ + nhrp_packet_extension(packet, + NHRP_EXTENSION_FORWARD_TRANSIT_NHS | + NHRP_EXTENSION_FLAG_COMPULSORY, + NHRP_PAYLOAD_TYPE_CIE_LIST); + nhrp_packet_extension(packet, + NHRP_EXTENSION_REVERSE_TRANSIT_NHS | + NHRP_EXTENSION_FLAG_COMPULSORY, + NHRP_PAYLOAD_TYPE_CIE_LIST); + nhrp_packet_extension(packet, + NHRP_EXTENSION_RESPONDER_ADDRESS | + NHRP_EXTENSION_FLAG_COMPULSORY, + NHRP_PAYLOAD_TYPE_CIE_LIST); + nhrp_packet_extension(packet, + NHRP_EXTENSION_NAT_ADDRESS, + NHRP_PAYLOAD_TYPE_CIE_LIST); + + packet->dst_iface = peer->interface; + nhrp_packet_send_request(packet, + nhrp_peer_handle_resolution_reply, + nhrp_peer_get(peer)); + +error: + nhrp_packet_put(packet); +} + +struct nhrp_peer *nhrp_peer_alloc(struct nhrp_interface *iface) +{ + struct nhrp_peer *p; + + nhrp_peer_num_total++; + p = calloc(1, sizeof(struct nhrp_peer)); + p->ref = 1; + p->interface = iface; + list_init(&p->peer_list_entry); + list_init(&p->mcast_list_entry); + ev_timer_init(&p->timer, NULL, 0., 0.); + ev_child_init(&p->child, NULL, 0, 0); + + return p; +} + +struct nhrp_peer *nhrp_peer_get(struct nhrp_peer *peer) +{ + if (peer == NULL) + return NULL; + + peer->ref++; + nhrp_peer_debug_refcount(__FUNCTION__, peer); + + return peer; +} + +static void nhrp_peer_run_nhs_down(struct nhrp_peer *peer) +{ + if ((peer->flags & (NHRP_PEER_FLAG_REGISTER | + NHRP_PEER_FLAG_UP | + NHRP_PEER_FLAG_REPLACED)) + == (NHRP_PEER_FLAG_REGISTER | NHRP_PEER_FLAG_UP)) + nhrp_peer_run_script(peer, "nhs-down", NULL); +} + +static void nhrp_peer_release(struct nhrp_peer *peer) +{ + struct nhrp_interface *iface = peer->interface; + struct nhrp_peer_selector sel; + + nhrp_peer_cancel_async(peer); + + /* Remove from lists */ + if (list_hashed(&peer->mcast_list_entry)) + list_del(&peer->mcast_list_entry); + if (hlist_hashed(&peer->nbma_hash_entry)) + hlist_del(&peer->nbma_hash_entry); + + if (peer->parent != NULL) { + nhrp_peer_put(peer->parent); + peer->parent = NULL; + } + + switch (peer->type) { + case NHRP_PEER_TYPE_SHORTCUT_ROUTE: + if ((peer->flags & NHRP_PEER_FLAG_UP) && + !(peer->flags & NHRP_PEER_FLAG_REPLACED)) + nhrp_peer_run_script(peer, "route-down", NULL); + break; + case NHRP_PEER_TYPE_CACHED: + case NHRP_PEER_TYPE_DYNAMIC: + case NHRP_PEER_TYPE_STATIC: + case NHRP_PEER_TYPE_DYNAMIC_NHS: + if (peer->flags & NHRP_PEER_FLAG_REPLACED) + break; + + /* Remove cached routes using this entry as next-hop */ + memset(&sel, 0, sizeof(sel)); + sel.type_mask = BIT(NHRP_PEER_TYPE_SHORTCUT_ROUTE); + sel.interface = iface; + sel.next_hop_address = peer->protocol_address; + nhrp_peer_foreach(nhrp_peer_remove_matching, NULL, + &sel); + + /* Execute peer-down */ + nhrp_peer_run_nhs_down(peer); + if (peer->flags & NHRP_PEER_FLAG_UP) { + peer->purge_reason = "timeout"; + nhrp_peer_run_script(peer, "peer-down", NULL); + } + + /* Remove from arp cache */ + if (peer->protocol_address.type != PF_UNSPEC) + kernel_inject_neighbor(&peer->protocol_address, + NULL, peer->interface); + break; + case NHRP_PEER_TYPE_INCOMPLETE: + case NHRP_PEER_TYPE_NEGATIVE: + case NHRP_PEER_TYPE_LOCAL_ADDR: + case NHRP_PEER_TYPE_LOCAL_ROUTE: + case NHRP_PEER_TYPE_STATIC_DNS: + break; + default: + NHRP_BUG_ON("invalid peer type"); + break; + } + + if (peer->nbma_hostname) { + free(peer->nbma_hostname); + peer->nbma_hostname = NULL; + } + + free(peer); + nhrp_peer_num_total--; +} + +int nhrp_peer_put(struct nhrp_peer *peer) +{ + NHRP_BUG_ON(peer->ref == 0); + + peer->ref--; + nhrp_peer_debug_refcount(__FUNCTION__, peer); + + if (peer->ref > 0) + return FALSE; + + nhrp_peer_release(peer); + + return TRUE; +} + +static int nhrp_peer_mark_matching(void *ctx, struct nhrp_peer *peer) +{ + peer->flags |= NHRP_PEER_FLAG_MARK; + return 0; +} + +static int nhrp_peer_renew_nhs_matching(void *ctx, struct nhrp_peer *peer) +{ + peer->flags &= ~NHRP_PEER_FLAG_MARK; + return 1; +} + +static void nhrp_peer_dnsmap_query_cb(struct nhrp_address_query *query, + int num_addr, struct nhrp_address *addrs) +{ + struct nhrp_peer *np, *peer = + container_of(query, struct nhrp_peer, address_query); + struct nhrp_peer_selector sel; + int i; + + if (num_addr < 0) { + nhrp_error("Failed to resolve '%s'", peer->nbma_hostname); + nhrp_peer_schedule(peer, 10, nhrp_peer_dnsmap_restart_cb); + return; + } + + if (num_addr > 0) { + /* Refresh protocol */ + peer->afnum = nhrp_afnum_from_pf(addrs[0].type); + } + + /* Mark existing dynamic nhs entries as expired */ + memset(&sel, 0, sizeof(sel)); + sel.type_mask = BIT(NHRP_PEER_TYPE_DYNAMIC_NHS); + sel.interface = peer->interface; + sel.parent = peer; + nhrp_peer_foreach(nhrp_peer_mark_matching, NULL, &sel); + + for (i = 0; i < num_addr; i++) { + /* If this NBMA exists as dynamic NHS, mark it ok. */ + sel.next_hop_address = addrs[i]; + if (nhrp_peer_foreach(nhrp_peer_renew_nhs_matching, + NULL, &sel) != 0) + continue; + + /* New NHS, create a peer entry */ + np = nhrp_peer_alloc(peer->interface); + np->type = NHRP_PEER_TYPE_DYNAMIC_NHS; + np->flags |= NHRP_PEER_FLAG_REGISTER; + np->afnum = peer->afnum; + np->protocol_type = peer->protocol_type; + np->protocol_address = peer->protocol_address; + np->prefix_length = peer->prefix_length; + np->next_hop_address = addrs[i]; + np->parent = nhrp_peer_get(peer); + nhrp_address_set_network(&np->protocol_address, + np->prefix_length); + nhrp_peer_insert(np); + nhrp_peer_put(np); + } + + /* Delete all dynamic nhs:s that were not in the DNS reply */ + nhrp_address_set_type(&sel.next_hop_address, AF_UNSPEC); + sel.flags = NHRP_PEER_FIND_MARK; + nhrp_peer_foreach(nhrp_peer_remove_matching, NULL, &sel); + + /* Refresh DNS info */ + nhrp_peer_schedule(peer, peer->interface->holding_time, + nhrp_peer_dnsmap_restart_cb); +} + +static void nhrp_peer_dnsmap_restart_cb(struct ev_timer *w, int revents) +{ + struct nhrp_peer *peer = container_of(w, struct nhrp_peer, timer); + + NHRP_BUG_ON(peer->nbma_hostname == NULL); + nhrp_address_resolve(&peer->address_query, peer->nbma_hostname, + nhrp_peer_dnsmap_query_cb); +} + +static void nhrp_peer_insert_cb(struct ev_timer *w, int revents) +{ + struct nhrp_peer *peer = container_of(w, struct nhrp_peer, timer); + + nhrp_peer_cancel_async(peer); + switch (peer->type) { + case NHRP_PEER_TYPE_LOCAL_ADDR: + peer->flags |= NHRP_PEER_FLAG_UP; + forward_local_addresses_changed(); + break; + case NHRP_PEER_TYPE_LOCAL_ROUTE: + peer->flags |= NHRP_PEER_FLAG_UP; + break; + case NHRP_PEER_TYPE_INCOMPLETE: + nhrp_peer_send_resolve(peer); + break; + case NHRP_PEER_TYPE_CACHED: + case NHRP_PEER_TYPE_DYNAMIC: + case NHRP_PEER_TYPE_STATIC: + case NHRP_PEER_TYPE_DYNAMIC_NHS: + nhrp_peer_restart_cb(w, 0); + break; + case NHRP_PEER_TYPE_STATIC_DNS: + nhrp_peer_dnsmap_restart_cb(w, 0); + break; + case NHRP_PEER_TYPE_SHORTCUT_ROUTE: + if (peer->flags & NHRP_PEER_FLAG_UP) + nhrp_peer_script_route_up_done(&peer->child, 0); + else if (nhrp_peer_route(peer->interface, + &peer->next_hop_address, + NHRP_PEER_FIND_UP | NHRP_PEER_FIND_EXACT, + NHRP_PEER_TYPEMASK_ADJACENT) != NULL) + nhrp_peer_run_script(peer, "route-up", + nhrp_peer_script_route_up_done); + else + nhrp_peer_schedule(peer, peer->expire_time - NHRP_EXPIRY_TIME + - 10 - ev_now(), nhrp_peer_expire_cb); + break; + case NHRP_PEER_TYPE_NEGATIVE: + peer->expire_time = ev_now() + NHRP_NEGATIVE_CACHE_TIME; + + if (peer->flags & NHRP_PEER_FLAG_UP) + kernel_inject_neighbor(&peer->protocol_address, + NULL, peer->interface); + nhrp_peer_schedule(peer, NHRP_NEGATIVE_CACHE_TIME, + nhrp_peer_remove_cb); + break; + default: + NHRP_BUG_ON("invalid peer type"); + break; + } +} + +static void nhrp_peer_reinsert(struct nhrp_peer *peer, int type) +{ + NHRP_BUG_ON((peer->type == NHRP_PEER_TYPE_LOCAL_ADDR) != + (type == NHRP_PEER_TYPE_LOCAL_ADDR)); + NHRP_BUG_ON((peer->type == NHRP_PEER_TYPE_LOCAL_ROUTE) != + (type == NHRP_PEER_TYPE_LOCAL_ROUTE)); + + peer->flags &= ~NHRP_PEER_FLAG_REMOVED; + peer->type = type; + nhrp_peer_insert_cb(&peer->timer, 0); +} + +static int nhrp_peer_replace_shortcut(void *ctx, struct nhrp_peer *peer) +{ + struct nhrp_peer *shortcut = (struct nhrp_peer *) ctx; + + /* Shortcut of identical prefix is replacement, either + * due to renewal, or new shortcut next-hop. */ + if (nhrp_address_cmp(&peer->protocol_address, + &shortcut->protocol_address) == 0 && + peer->prefix_length == shortcut->prefix_length) { + peer->flags |= NHRP_PEER_FLAG_REPLACED; + + /* If identical shortcut is being refreshed, + * mark the refresher peer entry up. */ + if ((peer->flags & NHRP_PEER_FLAG_UP) && + nhrp_address_cmp(&peer->next_hop_address, + &shortcut->next_hop_address) == 0) + shortcut->flags |= NHRP_PEER_FLAG_UP; + } + + /* Delete the old peer unconditionally */ + nhrp_peer_remove(peer); + + return 0; +} + +void nhrp_peer_insert(struct nhrp_peer *peer) +{ + struct nhrp_peer_selector sel; + char tmp[NHRP_PEER_FORMAT_LEN]; + + /* First, prune all duplicates */ + memset(&sel, 0, sizeof(sel)); + sel.interface = peer->interface; + sel.protocol_address = peer->protocol_address; + sel.prefix_length = peer->prefix_length; + switch (peer->type) { + case NHRP_PEER_TYPE_SHORTCUT_ROUTE: + /* remove all existing shortcuts with same nexthop */ + sel.flags = NHRP_PEER_FIND_SUBNET; + sel.type_mask |= BIT(NHRP_PEER_TYPE_SHORTCUT_ROUTE); + nhrp_peer_foreach(nhrp_peer_replace_shortcut, peer, &sel); + break; + case NHRP_PEER_TYPE_LOCAL_ROUTE: + sel.type_mask |= BIT(NHRP_PEER_TYPE_LOCAL_ROUTE); + default: + /* remove exact protocol address matches */ + sel.flags = NHRP_PEER_FIND_EXACT; + sel.type_mask |= NHRP_PEER_TYPEMASK_REMOVABLE; + nhrp_peer_foreach(nhrp_peer_remove_matching, NULL, &sel); + break; + } + + /* Keep a reference as long as we are on the list */ + peer = nhrp_peer_get(peer); + nhrp_debug("Adding %s %s", + nhrp_peer_type[peer->type], + nhrp_peer_format(peer, sizeof(tmp), tmp)); + + if (peer->type == NHRP_PEER_TYPE_LOCAL_ADDR) + list_add(&peer->peer_list_entry, &local_peer_list); + else + list_add(&peer->peer_list_entry, &peer->interface->peer_list); + + /* Start peers life */ + if (nhrp_running || peer->type == NHRP_PEER_TYPE_LOCAL_ADDR) + nhrp_peer_insert_cb(&peer->timer, 0); + else + nhrp_peer_schedule(peer, 0, &nhrp_peer_insert_cb); +} + +static void nhrp_peer_script_peer_down_done(union nhrp_peer_event e, + int revents) +{ + struct nhrp_peer *peer = nhrp_peer_from_event(e, revents); + + nhrp_peer_schedule(peer, 5, nhrp_peer_restart_cb); +} + +void nhrp_peer_purge(struct nhrp_peer *peer, const char *purge_reason) +{ + switch (peer->type) { + case NHRP_PEER_TYPE_STATIC: + case NHRP_PEER_TYPE_DYNAMIC_NHS: + peer->purge_reason = purge_reason; + nhrp_peer_run_nhs_down(peer); + nhrp_peer_is_down(peer); + nhrp_peer_cancel_async(peer); + if (peer->flags & NHRP_PEER_FLAG_LOWER_UP) { + nhrp_peer_run_script(peer, "peer-down", + nhrp_peer_script_peer_down_done); + } else { + nhrp_peer_script_peer_down_done(&peer->child, 0); + } + nhrp_address_set_type(&peer->my_nbma_address, PF_UNSPEC); + break; + case NHRP_PEER_TYPE_STATIC_DNS: + nhrp_peer_schedule(peer, 0, nhrp_peer_dnsmap_restart_cb); + break; + default: + peer->purge_reason = purge_reason; + nhrp_peer_remove(peer); + break; + } +} + +int nhrp_peer_purge_matching(void *ctx, struct nhrp_peer *peer) +{ + int *count = (int *) ctx; + nhrp_peer_purge(peer, "user-request"); + if (count != NULL) + (*count)++; + return 0; +} + +int nhrp_peer_lowerdown_matching(void *ctx, struct nhrp_peer *peer) +{ + int *count = (int *) ctx; + nhrp_peer_purge(peer, "lower-down"); + if (count != NULL) + (*count)++; + return 0; +} + +static void nhrp_peer_remove_cb(struct ev_timer *w, int revents) +{ + struct nhrp_peer *peer = container_of(w, struct nhrp_peer, timer); + int type; + + peer->flags |= NHRP_PEER_FLAG_REMOVED; + peer->purge_reason = "expired"; + nhrp_peer_is_down(peer); + list_del(&peer->peer_list_entry); + + type = peer->type; + nhrp_peer_put(peer); + + if (type == NHRP_PEER_TYPE_LOCAL_ADDR) + forward_local_addresses_changed(); +} + +void nhrp_peer_remove(struct nhrp_peer *peer) +{ + char tmp[NHRP_PEER_FORMAT_LEN]; + + if (peer->flags & NHRP_PEER_FLAG_REMOVED) + return; + + nhrp_debug("Removing %s %s", + nhrp_peer_type[peer->type], + nhrp_peer_format(peer, sizeof(tmp), tmp)); + + peer->flags |= NHRP_PEER_FLAG_REMOVED; + nhrp_peer_is_down(peer); + nhrp_peer_cancel_async(peer); + nhrp_peer_schedule(peer, 0, nhrp_peer_remove_cb); +} + +int nhrp_peer_remove_matching(void *ctx, struct nhrp_peer *peer) +{ + int *count = (int *) ctx; + + nhrp_peer_remove(peer); + if (count != NULL) + (*count)++; + + return 0; +} + +int nhrp_peer_set_used_matching(void *ctx, struct nhrp_peer *peer) +{ + int used = (int) (intptr_t) ctx; + + if (used) { + peer->flags |= NHRP_PEER_FLAG_USED; + nhrp_peer_renew(peer); + } else { + peer->flags &= ~NHRP_PEER_FLAG_USED; + } + return 0; +} + +int nhrp_peer_match(struct nhrp_peer *p, struct nhrp_peer_selector *sel) +{ + if (sel->type_mask && !(sel->type_mask & BIT(p->type))) + return FALSE; + + if ((sel->flags & NHRP_PEER_FIND_UP) && + !(p->flags & NHRP_PEER_FLAG_UP)) + return FALSE; + + if ((sel->flags & NHRP_PEER_FIND_MARK) && + !(p->flags & NHRP_PEER_FLAG_MARK)) + return FALSE; + + if (sel->interface != NULL && + p->interface != sel->interface && + !(p->interface->flags & NHRP_INTERFACE_FLAG_SHORTCUT_DEST)) + return FALSE; + + if (sel->hostname != NULL && + (p->nbma_hostname == NULL || + strcmp(sel->hostname, p->nbma_hostname) != 0)) + return FALSE; + + if (sel->parent != NULL && + p->parent != sel->parent) + return FALSE; + + if (sel->protocol_address.type != PF_UNSPEC) { + if (sel->prefix_length == 0) + sel->prefix_length = sel->protocol_address.addr_len * 8; + + if (sel->flags & NHRP_PEER_FIND_EXACT) { + if (nhrp_address_cmp(&p->protocol_address, + &sel->protocol_address) != 0) + return FALSE; + + if (p->prefix_length != sel->prefix_length && + p->type != NHRP_PEER_TYPE_STATIC && + p->type != NHRP_PEER_TYPE_DYNAMIC_NHS) + return FALSE; + } else if (sel->flags & NHRP_PEER_FIND_ROUTE) { + if (nhrp_address_prefix_cmp(&p->protocol_address, + &sel->protocol_address, + p->prefix_length) != 0) + return FALSE; + } else { + if (p->prefix_length < sel->prefix_length) { + if (sel->prefix_length + == sel->protocol_address.addr_len * 8 && + nhrp_address_cmp(&p->protocol_address, + &sel->protocol_address) + == 0) + return TRUE; + + return FALSE; + } + + if (nhrp_address_prefix_cmp(&p->protocol_address, + &sel->protocol_address, + sel->prefix_length) != 0) + return FALSE; + } + } + + if (sel->next_hop_address.type != PF_UNSPEC) { + if (nhrp_address_cmp(&p->next_hop_address, + &sel->next_hop_address) != 0) + return FALSE; + } + + return TRUE; +} + +struct enum_interface_peers_ctx { + nhrp_peer_enumerator enumerator; + void *ctx; + struct nhrp_peer_selector *sel; +}; + +static int enumerate_peer_cache(struct list_head *peer_cache, + nhrp_peer_enumerator e, void *ctx, + struct nhrp_peer_selector *sel) +{ + struct nhrp_peer *p; + int rc = 0; + + list_for_each_entry(p, peer_cache, peer_list_entry) { + if (p->flags & NHRP_PEER_FLAG_REMOVED) + continue; + + if (sel == NULL || nhrp_peer_match(p, sel)) { + rc = e(ctx, p); + if (rc != 0) + break; + } + } + + return rc; +} + +static int enum_interface_peers(void *ctx, struct nhrp_interface *iface) +{ + struct enum_interface_peers_ctx *ectx = + (struct enum_interface_peers_ctx *) ctx; + + return enumerate_peer_cache(&iface->peer_list, + ectx->enumerator, ectx->ctx, + ectx->sel); +} + +int nhrp_peer_foreach(nhrp_peer_enumerator e, void *ctx, + struct nhrp_peer_selector *sel) +{ + struct nhrp_interface *iface = NULL; + struct enum_interface_peers_ctx ectx = { e, ctx, sel }; + int rc; + + if (sel != NULL) + iface = sel->interface; + + rc = enumerate_peer_cache(&local_peer_list, e, ctx, sel); + if (rc != 0) + return rc; + + /* Speed optimization: TYPE_LOCAL peers cannot be found from + * other places */ + if (sel != NULL && + sel->type_mask == BIT(NHRP_PEER_TYPE_LOCAL_ADDR)) + return 0; + + if (iface == NULL) + rc = nhrp_interface_foreach(enum_interface_peers, &ectx); + else + rc = enumerate_peer_cache(&iface->peer_list, e, ctx, sel); + + return rc; +} + +struct route_decision { + struct nhrp_peer_selector sel; + struct list_head *exclude; + struct nhrp_peer *best_found; + struct nhrp_address *src; + int found_exact, found_up; +}; + +static int decide_route(void *ctx, struct nhrp_peer *peer) +{ + struct route_decision *rd = (struct route_decision *) ctx; + int exact; + + if (peer->type != NHRP_PEER_TYPE_SHORTCUT_ROUTE) { + /* Exclude addresses from CIE from routing decision + * to avoid routing loops within NHS clusters. */ + if (rd->exclude != NULL && + nhrp_address_match_cie_list(&peer->next_hop_address, + &peer->protocol_address, + rd->exclude)) + return 0; + + /* Exclude also source address, we don't want to + * forward questions back to who's asking. */ + if (rd->src != NULL && + nhrp_address_cmp(rd->src, &peer->protocol_address) == 0) + return 0; + } else { + /* Exclude routes that point back to the sender + * of the packet */ + if (rd->src != NULL && + nhrp_address_cmp(rd->src, &peer->next_hop_address) == 0) + return 0; + } + + exact = (peer->type >= NHRP_PEER_TYPE_DYNAMIC_NHS) && + (nhrp_address_cmp(&peer->protocol_address, + &rd->sel.protocol_address) == 0); + if (rd->found_exact > exact) + return 0; + + if (rd->found_up && !(peer->flags & NHRP_PEER_FLAG_UP)) + return 0; + + if (rd->best_found != NULL && + rd->found_exact == exact && + rd->found_up == (peer->flags & NHRP_PEER_FLAG_UP)) { + if (rd->best_found->prefix_length > peer->prefix_length) + return 0; + + if (rd->best_found->prefix_length == peer->prefix_length && + rd->best_found->last_used < peer->last_used) + return 0; + } + + rd->best_found = peer; + rd->found_exact = exact; + rd->found_up = peer->flags & NHRP_PEER_FLAG_UP; + return 0; +} + +struct nhrp_peer *nhrp_peer_route_full(struct nhrp_interface *interface, + struct nhrp_address *dst, + int flags, int type_mask, + struct nhrp_address *src, + struct list_head *exclude) +{ + struct route_decision rd; + + memset(&rd, 0, sizeof(rd)); + rd.sel.flags = flags & ~NHRP_PEER_FIND_UP; + if ((flags & (NHRP_PEER_FIND_ROUTE | NHRP_PEER_FIND_EXACT | + NHRP_PEER_FIND_SUBNET)) == 0) + rd.sel.flags |= NHRP_PEER_FIND_ROUTE; + rd.sel.type_mask = type_mask; + rd.sel.interface = interface; + rd.sel.protocol_address = *dst; + rd.exclude = exclude; + rd.src = src; + nhrp_peer_foreach(decide_route, &rd, &rd.sel); + + if (rd.best_found == NULL) + return NULL; + + if ((flags & NHRP_PEER_FIND_UP) && + !(rd.best_found->flags & NHRP_PEER_FLAG_UP)) + return NULL; + + rd.best_found->last_used = ev_now(); + return rd.best_found; +} + +void nhrp_peer_traffic_indication(struct nhrp_interface *iface, + uint16_t afnum, struct nhrp_address *dst) +{ + struct nhrp_peer *peer; + int type; + + /* For off-NBMA destinations, we consider all shortcut routes, + * but NBMA destinations should be exact because we want to drop + * NHS from the path. */ + if (nhrp_address_prefix_cmp(dst, &iface->protocol_address, + iface->protocol_address_prefix) != 0) + type = NHRP_PEER_FIND_ROUTE; + else + type = NHRP_PEER_FIND_EXACT; + + /* Have we done something for this destination already? */ + peer = nhrp_peer_route(iface, dst, type, + ~BIT(NHRP_PEER_TYPE_LOCAL_ROUTE)); + if (peer != NULL) + return; + + /* Initiate resolution */ + peer = nhrp_peer_alloc(iface); + peer->type = NHRP_PEER_TYPE_INCOMPLETE; + peer->afnum = afnum; + peer->protocol_type = nhrp_protocol_from_pf(dst->type); + peer->protocol_address = *dst; + peer->prefix_length = dst->addr_len * 8; + nhrp_peer_insert(peer); + nhrp_peer_put(peer); +} + +static int dump_peer(void *ctx, struct nhrp_peer *peer) +{ + int *num_total = (int *) ctx; + char tmp[NHRP_PEER_FORMAT_LEN]; + + nhrp_info("%s %s", + nhrp_peer_type[peer->type], + nhrp_peer_format(peer, sizeof(tmp), tmp)); + (*num_total)++; + return 0; +} + +void nhrp_peer_dump_cache(void) +{ + int num_total = 0; + + nhrp_info("Peer cache dump:"); + nhrp_peer_foreach(dump_peer, &num_total, NULL); + nhrp_info("Total %d peer cache entries, %d allocated entries", + num_total, nhrp_peer_num_total); +} + +void nhrp_peer_cleanup(void) +{ + ev_tstamp prev = ev_now(); + + nhrp_peer_foreach(nhrp_peer_remove_matching, NULL, NULL); + + while (nhrp_peer_num_total > 0) { + if (ev_now() > prev + 5.0) { + nhrp_info("Waiting for peers to die, %d left", nhrp_peer_num_total); + prev = ev_now(); + } + ev_loop(EVLOOP_ONESHOT); + } +} -- cgit v1.2.3