summaryrefslogtreecommitdiffstats
path: root/nhrpd/nhrp_peer.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 13:16:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 13:16:35 +0000
commite2bbf175a2184bd76f6c54ccf8456babeb1a46fc (patch)
treef0b76550d6e6f500ada964a3a4ee933a45e5a6f1 /nhrpd/nhrp_peer.c
parentInitial commit. (diff)
downloadfrr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.tar.xz
frr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.zip
Adding upstream version 9.1.upstream/9.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'nhrpd/nhrp_peer.c')
-rw-r--r--nhrpd/nhrp_peer.c1244
1 files changed, 1244 insertions, 0 deletions
diff --git a/nhrpd/nhrp_peer.c b/nhrpd/nhrp_peer.c
new file mode 100644
index 0000000..ffb6cf7
--- /dev/null
+++ b/nhrpd/nhrp_peer.c
@@ -0,0 +1,1244 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* NHRP peer functions
+ * Copyright (c) 2014-2015 Timo Teräs
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <netinet/if_ether.h>
+
+#include "zebra.h"
+#include "memory.h"
+#include "frrevent.h"
+#include "hash.h"
+#include "network.h"
+
+#include "nhrpd.h"
+#include "nhrp_protocol.h"
+#include "os.h"
+
+DEFINE_MTYPE_STATIC(NHRPD, NHRP_PEER, "NHRP peer entry");
+
+struct ipv6hdr {
+ uint8_t priority_version;
+ uint8_t flow_lbl[3];
+ uint16_t payload_len;
+ uint8_t nexthdr;
+ uint8_t hop_limit;
+ struct in6_addr saddr;
+ struct in6_addr daddr;
+};
+
+static void nhrp_packet_debug(struct zbuf *zb, const char *dir);
+
+static void nhrp_peer_check_delete(struct nhrp_peer *p)
+{
+ struct nhrp_interface *nifp = p->ifp->info;
+
+ if (p->ref || notifier_active(&p->notifier_list))
+ return;
+
+ debugf(NHRP_DEBUG_COMMON, "Deleting peer ref:%d remote:%pSU local:%pSU",
+ p->ref, &p->vc->remote.nbma, &p->vc->local.nbma);
+
+ EVENT_OFF(p->t_fallback);
+ EVENT_OFF(p->t_timer);
+ hash_release(nifp->peer_hash, p);
+ nhrp_interface_notify_del(p->ifp, &p->ifp_notifier);
+ nhrp_vc_notify_del(p->vc, &p->vc_notifier);
+ XFREE(MTYPE_NHRP_PEER, p);
+}
+
+static void nhrp_peer_notify_up(struct event *t)
+{
+ struct nhrp_peer *p = EVENT_ARG(t);
+ struct nhrp_vc *vc = p->vc;
+ struct interface *ifp = p->ifp;
+ struct nhrp_interface *nifp = ifp->info;
+
+ p->t_fallback = NULL;
+ if (nifp->enabled && (!nifp->ipsec_profile || vc->ipsec)) {
+ p->online = 1;
+ nhrp_peer_ref(p);
+ notifier_call(&p->notifier_list, NOTIFY_PEER_UP);
+ nhrp_peer_unref(p);
+ }
+}
+
+static void __nhrp_peer_check(struct nhrp_peer *p)
+{
+ struct nhrp_vc *vc = p->vc;
+ struct interface *ifp = p->ifp;
+ struct nhrp_interface *nifp = ifp->info;
+ unsigned online;
+
+ online = nifp->enabled && (!nifp->ipsec_profile || vc->ipsec);
+ if (p->online != online) {
+ EVENT_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. */
+ event_add_timer_msec(master, nhrp_peer_notify_up, p, 50,
+ &p->t_fallback);
+ } else {
+ nhrp_peer_ref(p);
+ p->online = online;
+ if (online) {
+ notifier_call(&p->notifier_list,
+ NOTIFY_PEER_UP);
+ } else {
+ p->requested = p->fallback_requested = 0;
+ notifier_call(&p->notifier_list,
+ NOTIFY_PEER_DOWN);
+ }
+ nhrp_peer_unref(p);
+ }
+ }
+}
+
+static void nhrp_peer_vc_notify(struct notifier_block *n, unsigned long cmd)
+{
+ struct nhrp_peer *p = container_of(n, struct nhrp_peer, vc_notifier);
+
+ switch (cmd) {
+ case NOTIFY_VC_IPSEC_CHANGED:
+ __nhrp_peer_check(p);
+ break;
+ case NOTIFY_VC_IPSEC_UPDATE_NBMA:
+ nhrp_peer_ref(p);
+ notifier_call(&p->notifier_list, NOTIFY_PEER_NBMA_CHANGING);
+ nhrp_peer_unref(p);
+ break;
+ }
+}
+
+static void nhrp_peer_ifp_notify(struct notifier_block *n, unsigned long cmd)
+{
+ struct nhrp_peer *p = container_of(n, struct nhrp_peer, ifp_notifier);
+ struct nhrp_interface *nifp;
+ struct nhrp_vc *vc;
+
+ nhrp_peer_ref(p);
+ switch (cmd) {
+ case NOTIFY_INTERFACE_UP:
+ case NOTIFY_INTERFACE_DOWN:
+ __nhrp_peer_check(p);
+ break;
+ case NOTIFY_INTERFACE_NBMA_CHANGED:
+ /* Source NBMA changed, rebind to new VC */
+ nifp = p->ifp->info;
+ vc = nhrp_vc_get(&nifp->nbma, &p->vc->remote.nbma, 1);
+ if (vc && p->vc != vc) {
+ nhrp_vc_notify_del(p->vc, &p->vc_notifier);
+ p->vc = vc;
+ nhrp_vc_notify_add(p->vc, &p->vc_notifier,
+ nhrp_peer_vc_notify);
+ __nhrp_peer_check(p);
+ }
+ /* fallthru */ /* to post config update */
+ case NOTIFY_INTERFACE_ADDRESS_CHANGED:
+ notifier_call(&p->notifier_list, NOTIFY_PEER_IFCONFIG_CHANGED);
+ break;
+ case NOTIFY_INTERFACE_IPSEC_CHANGED:
+ __nhrp_peer_check(p);
+ notifier_call(&p->notifier_list, NOTIFY_PEER_IFCONFIG_CHANGED);
+ break;
+ case NOTIFY_INTERFACE_MTU_CHANGED:
+ notifier_call(&p->notifier_list, NOTIFY_PEER_MTU_CHANGED);
+ break;
+ }
+ nhrp_peer_unref(p);
+}
+
+static unsigned int nhrp_peer_key(const void *peer_data)
+{
+ const struct nhrp_peer *p = peer_data;
+ return sockunion_hash(&p->vc->remote.nbma);
+}
+
+static bool nhrp_peer_cmp(const void *cache_data, const void *key_data)
+{
+ const struct nhrp_peer *a = cache_data;
+ const struct nhrp_peer *b = key_data;
+
+ return a->ifp == b->ifp && a->vc == b->vc;
+}
+
+static void *nhrp_peer_create(void *data)
+{
+ struct nhrp_peer *p, *key = data;
+
+ p = XMALLOC(MTYPE_NHRP_PEER, sizeof(*p));
+
+ *p = (struct nhrp_peer){
+ .ref = 0,
+ .ifp = key->ifp,
+ .vc = key->vc,
+ .notifier_list = NOTIFIER_LIST_INITIALIZER(&p->notifier_list),
+ };
+ nhrp_vc_notify_add(p->vc, &p->vc_notifier, nhrp_peer_vc_notify);
+ nhrp_interface_notify_add(p->ifp, &p->ifp_notifier,
+ nhrp_peer_ifp_notify);
+
+ return p;
+}
+
+static void do_peer_hash_free(void *hb_data)
+{
+ struct nhrp_peer *p = (struct nhrp_peer *)hb_data;
+
+ nhrp_peer_check_delete(p);
+}
+
+void nhrp_peer_interface_del(struct interface *ifp)
+{
+ struct nhrp_interface *nifp = ifp->info;
+
+ debugf(NHRP_DEBUG_COMMON, "Cleaning up undeleted peer entries (%lu)",
+ nifp->peer_hash ? nifp->peer_hash->count : 0);
+
+ hash_clean_and_free(&nifp->peer_hash, do_peer_hash_free);
+}
+
+struct nhrp_peer *nhrp_peer_get(struct interface *ifp,
+ const union sockunion *remote_nbma)
+{
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_peer key, *p;
+ struct nhrp_vc *vc;
+
+ if (!nifp->peer_hash) {
+ nifp->peer_hash = hash_create(nhrp_peer_key, nhrp_peer_cmp,
+ "NHRP Peer Hash");
+ if (!nifp->peer_hash)
+ return NULL;
+ }
+
+ vc = nhrp_vc_get(&nifp->nbma, remote_nbma, 1);
+ if (!vc)
+ return NULL;
+
+ key.ifp = ifp;
+ key.vc = vc;
+
+ p = hash_get(nifp->peer_hash, &key, nhrp_peer_create);
+ nhrp_peer_ref(p);
+ if (p->ref == 1)
+ __nhrp_peer_check(p);
+
+ return p;
+}
+
+struct nhrp_peer *nhrp_peer_ref(struct nhrp_peer *p)
+{
+ if (p)
+ p->ref++;
+ return p;
+}
+
+void nhrp_peer_unref(struct nhrp_peer *p)
+{
+ if (p) {
+ p->ref--;
+ nhrp_peer_check_delete(p);
+ }
+}
+
+static void nhrp_peer_request_timeout(struct event *t)
+{
+ struct nhrp_peer *p = EVENT_ARG(t);
+ struct nhrp_vc *vc = p->vc;
+ struct interface *ifp = p->ifp;
+ struct nhrp_interface *nifp = ifp->info;
+
+
+ if (p->online)
+ return;
+
+ if (nifp->ipsec_fallback_profile && !p->prio
+ && !p->fallback_requested) {
+ p->fallback_requested = 1;
+ vici_request_vc(nifp->ipsec_fallback_profile, &vc->local.nbma,
+ &vc->remote.nbma, p->prio);
+ event_add_timer(master, nhrp_peer_request_timeout, p, 30,
+ &p->t_fallback);
+ } else {
+ p->requested = p->fallback_requested = 0;
+ }
+}
+
+static void nhrp_peer_defer_vici_request(struct event *t)
+{
+ struct nhrp_peer *p = EVENT_ARG(t);
+ struct nhrp_vc *vc = p->vc;
+ struct interface *ifp = p->ifp;
+ struct nhrp_interface *nifp = ifp->info;
+
+ EVENT_OFF(p->t_timer);
+
+ if (p->online) {
+ debugf(NHRP_DEBUG_COMMON,
+ "IPsec connection to %pSU already established",
+ &vc->remote.nbma);
+ } else {
+ vici_request_vc(nifp->ipsec_profile, &vc->local.nbma,
+ &vc->remote.nbma, p->prio);
+ event_add_timer(master, nhrp_peer_request_timeout, p,
+ (nifp->ipsec_fallback_profile && !p->prio) ? 15
+ : 30,
+ &p->t_fallback);
+ }
+}
+
+int nhrp_peer_check(struct nhrp_peer *p, int establish)
+{
+ struct nhrp_vc *vc = p->vc;
+ struct interface *ifp = p->ifp;
+ struct nhrp_interface *nifp = ifp->info;
+
+ if (p->online)
+ return 1;
+ if (!establish)
+ return 0;
+ if (p->requested)
+ return 0;
+ if (!nifp->ipsec_profile)
+ return 0;
+ if (sockunion_family(&vc->local.nbma) == AF_UNSPEC)
+ return 0;
+ if (vc->ipsec)
+ return 1;
+
+ p->prio = establish > 1;
+ p->requested = 1;
+
+ /* All NHRP registration requests are prioritized */
+ if (p->prio) {
+ vici_request_vc(nifp->ipsec_profile, &vc->local.nbma,
+ &vc->remote.nbma, p->prio);
+ event_add_timer(master, nhrp_peer_request_timeout, p,
+ (nifp->ipsec_fallback_profile && !p->prio) ? 15
+ : 30,
+ &p->t_fallback);
+ } else {
+ /* Maximum timeout is 1 second */
+ int r_time_ms = frr_weak_random() % 1000;
+
+ debugf(NHRP_DEBUG_COMMON,
+ "Initiating IPsec connection request to %pSU after %d ms:",
+ &vc->remote.nbma, r_time_ms);
+ event_add_timer_msec(master, nhrp_peer_defer_vici_request, p,
+ r_time_ms, &p->t_timer);
+ }
+
+ return 0;
+}
+
+void nhrp_peer_notify_add(struct nhrp_peer *p, struct notifier_block *n,
+ notifier_fn_t fn)
+{
+ notifier_add(n, &p->notifier_list, fn);
+}
+
+void nhrp_peer_notify_del(struct nhrp_peer *p, struct notifier_block *n)
+{
+ notifier_del(n, &p->notifier_list);
+ nhrp_peer_check_delete(p);
+}
+
+void nhrp_peer_send(struct nhrp_peer *p, struct zbuf *zb)
+{
+ nhrp_packet_debug(zb, "Send");
+
+ if (!p->online)
+ return;
+
+ debugf(NHRP_DEBUG_KERNEL, "PACKET: Send %pSU -> %pSU",
+ &p->vc->local.nbma, &p->vc->remote.nbma);
+
+ os_sendmsg(zb->head, zbuf_used(zb), p->ifp->ifindex,
+ sockunion_get_addr(&p->vc->remote.nbma),
+ sockunion_get_addrlen(&p->vc->remote.nbma), ETH_P_NHRP);
+ zbuf_reset(zb);
+}
+
+static void nhrp_process_nat_extension(struct nhrp_packet_parser *pp,
+ union sockunion *proto,
+ union sockunion *cie_nbma)
+{
+ union sockunion cie_proto;
+ struct zbuf payload;
+ struct nhrp_extension_header *ext;
+ struct zbuf *extensions;
+
+ if (!cie_nbma)
+ return;
+
+ sockunion_family(cie_nbma) = AF_UNSPEC;
+
+ if (!proto || sockunion_family(proto) == AF_UNSPEC)
+ return;
+
+ /* Handle extensions */
+ extensions = zbuf_alloc(zbuf_used(&pp->extensions));
+ if (extensions) {
+ zbuf_copy_peek(extensions, &pp->extensions,
+ zbuf_used(&pp->extensions));
+ while ((ext = nhrp_ext_pull(extensions, &payload)) != NULL) {
+ switch (htons(ext->type)
+ & ~NHRP_EXTENSION_FLAG_COMPULSORY) {
+ case NHRP_EXTENSION_NAT_ADDRESS:
+ /* Process the NBMA and proto address in NAT
+ * extension and update the cache without which
+ * the neighbor table in the kernel contains the
+ * source NBMA address which is not reachable
+ * since it is behind a NAT device
+ */
+ debugf(NHRP_DEBUG_COMMON,
+ "shortcut res_resp: Processing NAT Extension for %pSU",
+ proto);
+ while (nhrp_cie_pull(&payload, pp->hdr,
+ cie_nbma, &cie_proto)) {
+ if (sockunion_family(&cie_proto)
+ == AF_UNSPEC)
+ continue;
+
+ if (!sockunion_cmp(proto, &cie_proto)) {
+ debugf(NHRP_DEBUG_COMMON,
+ "cie_nbma for proto %pSU is %pSU",
+ proto, cie_nbma);
+ break;
+ }
+ }
+ }
+ }
+ zbuf_free(extensions);
+ }
+}
+
+static void nhrp_handle_resolution_req(struct nhrp_packet_parser *pp)
+{
+ struct interface *ifp = pp->ifp;
+ struct zbuf *zb, payload;
+ struct nhrp_packet_header *hdr;
+ struct nhrp_cie_header *cie;
+ struct nhrp_extension_header *ext;
+ struct nhrp_cache *c;
+ union sockunion cie_nbma, cie_nbma_nat, cie_proto, *proto_addr,
+ *nbma_addr, *claimed_nbma_addr;
+ int holdtime, prefix_len, hostprefix_len;
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_peer *peer;
+ size_t paylen;
+
+ if (!(pp->if_ad->flags & NHRP_IFF_SHORTCUT)) {
+ debugf(NHRP_DEBUG_COMMON, "Shortcuts disabled");
+ /* FIXME: Send error indication? */
+ return;
+ }
+
+ if (pp->if_ad->network_id && pp->route_type == NHRP_ROUTE_OFF_NBMA
+ && pp->route_prefix.prefixlen < 8) {
+ debugf(NHRP_DEBUG_COMMON,
+ "Shortcut to more generic than /8 dropped");
+ return;
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "Parsing and replying to Resolution Req");
+
+ if (nhrp_route_address(ifp, &pp->src_proto, NULL, &peer)
+ != NHRP_ROUTE_NBMA_NEXTHOP)
+ return;
+
+ /* Copy payload CIE */
+ hostprefix_len = 8 * sockunion_get_addrlen(&pp->if_ad->addr);
+ paylen = zbuf_used(&pp->payload);
+ debugf(NHRP_DEBUG_COMMON, "shortcut res_rep: paylen %zu", paylen);
+
+ while ((cie = nhrp_cie_pull(&pp->payload, pp->hdr, &cie_nbma,
+ &cie_proto))
+ != NULL) {
+ prefix_len = cie->prefix_length;
+ debugf(NHRP_DEBUG_COMMON,
+ "shortcut res_rep: parsing CIE with prefixlen=%u",
+ prefix_len);
+ if (prefix_len == 0 || prefix_len >= hostprefix_len)
+ prefix_len = hostprefix_len;
+
+ if (prefix_len != hostprefix_len
+ && !(pp->hdr->flags
+ & htons(NHRP_FLAG_REGISTRATION_UNIQUE))) {
+ cie->code = NHRP_CODE_BINDING_NON_UNIQUE;
+ continue;
+ }
+
+ /* We currently support only unique prefix registrations */
+ if (prefix_len != hostprefix_len) {
+ cie->code = NHRP_CODE_ADMINISTRATIVELY_PROHIBITED;
+ continue;
+ }
+
+ proto_addr = (sockunion_family(&cie_proto) == AF_UNSPEC)
+ ? &pp->src_proto
+ : &cie_proto;
+
+ /* Check for this proto_addr in NHRP_NAT_EXTENSION */
+ nhrp_process_nat_extension(pp, proto_addr, &cie_nbma_nat);
+
+ if (sockunion_family(&cie_nbma_nat) == AF_UNSPEC) {
+ /* It may be possible that this resolution reply is
+ * coming directly from NATTED Spoke and there is not
+ * NAT Extension present
+ */
+ debugf(NHRP_DEBUG_COMMON,
+ "shortcut res_rep: No NAT Extension for %pSU",
+ proto_addr);
+
+ if (!sockunion_same(&pp->src_nbma,
+ &pp->peer->vc->remote.nbma)
+ && !nhrp_nhs_match_ip(&pp->peer->vc->remote.nbma,
+ nifp)) {
+ cie_nbma_nat = pp->peer->vc->remote.nbma;
+ debugf(NHRP_DEBUG_COMMON,
+ "shortcut res_rep: NAT detected using %pSU as cie_nbma",
+ &cie_nbma_nat);
+ }
+ }
+
+ if (sockunion_family(&cie_nbma_nat) != AF_UNSPEC)
+ nbma_addr = &cie_nbma_nat;
+ else if (sockunion_family(&cie_nbma) != AF_UNSPEC)
+ nbma_addr = &cie_nbma;
+ else
+ nbma_addr = &pp->src_nbma;
+
+ if (sockunion_family(&cie_nbma) != AF_UNSPEC)
+ claimed_nbma_addr = &cie_nbma;
+ else
+ claimed_nbma_addr = &pp->src_nbma;
+
+ holdtime = htons(cie->holding_time);
+ debugf(NHRP_DEBUG_COMMON,
+ "shortcut res_rep: holdtime is %u (if 0, using %u)",
+ holdtime, pp->if_ad->holdtime);
+ if (!holdtime)
+ holdtime = pp->if_ad->holdtime;
+
+ c = nhrp_cache_get(ifp, proto_addr, 1);
+ if (!c) {
+ debugf(NHRP_DEBUG_COMMON,
+ "shortcut res_rep: no cache found");
+ cie->code = NHRP_CODE_INSUFFICIENT_RESOURCES;
+ continue;
+ }
+
+ debugf(NHRP_DEBUG_COMMON,
+ "shortcut res_rep: updating binding for nmba addr %pSU",
+ nbma_addr);
+ if (!nhrp_cache_update_binding(
+ c, NHRP_CACHE_DYNAMIC, holdtime,
+ nhrp_peer_get(pp->ifp, nbma_addr), htons(cie->mtu),
+ nbma_addr, claimed_nbma_addr)) {
+ cie->code = NHRP_CODE_ADMINISTRATIVELY_PROHIBITED;
+ continue;
+ }
+
+ cie->code = NHRP_CODE_SUCCESS;
+ }
+
+ /* Create reply */
+ zb = zbuf_alloc(1500);
+ hdr = nhrp_packet_push(zb, NHRP_PACKET_RESOLUTION_REPLY, &pp->src_nbma,
+ &pp->src_proto, &pp->dst_proto);
+
+ /* Copied information from request */
+ hdr->flags = pp->hdr->flags
+ & htons(NHRP_FLAG_RESOLUTION_SOURCE_IS_ROUTER
+ | NHRP_FLAG_RESOLUTION_SOURCE_STABLE);
+ hdr->flags |= htons(NHRP_FLAG_RESOLUTION_DESTINATION_STABLE
+ | NHRP_FLAG_RESOLUTION_AUTHORATIVE);
+ hdr->u.request_id = pp->hdr->u.request_id;
+
+ /* CIE payload for the reply packet */
+ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nbma,
+ &pp->if_ad->addr);
+ cie->holding_time = htons(pp->if_ad->holdtime);
+ cie->mtu = htons(pp->if_ad->mtu);
+ if (pp->if_ad->network_id && pp->route_type == NHRP_ROUTE_OFF_NBMA)
+ cie->prefix_length = pp->route_prefix.prefixlen;
+ else
+ cie->prefix_length =
+ 8 * sockunion_get_addrlen(&pp->if_ad->addr);
+
+ /* Handle extensions */
+ while ((ext = nhrp_ext_pull(&pp->extensions, &payload)) != NULL) {
+ switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) {
+ case NHRP_EXTENSION_NAT_ADDRESS:
+ ext = nhrp_ext_push(zb, hdr,
+ NHRP_EXTENSION_NAT_ADDRESS);
+ if (!ext)
+ goto err;
+ if (sockunion_family(&nifp->nat_nbma) != AF_UNSPEC) {
+ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS,
+ &nifp->nat_nbma,
+ &pp->if_ad->addr);
+ if (!cie)
+ goto err;
+ cie->prefix_length =
+ 8 * sockunion_get_addrlen(
+ &pp->if_ad->addr);
+
+ cie->mtu = htons(pp->if_ad->mtu);
+ nhrp_ext_complete(zb, ext);
+ }
+ break;
+ default:
+ if (nhrp_ext_reply(zb, hdr, ifp, ext, &payload) < 0)
+ goto err;
+ break;
+ }
+ }
+ nhrp_packet_complete(zb, hdr);
+ nhrp_peer_send(peer, zb);
+err:
+ nhrp_peer_unref(peer);
+ zbuf_free(zb);
+}
+
+static void nhrp_handle_registration_request(struct nhrp_packet_parser *p)
+{
+ struct interface *ifp = p->ifp;
+ struct zbuf *zb, payload;
+ struct nhrp_packet_header *hdr;
+ struct nhrp_cie_header *cie;
+ struct nhrp_extension_header *ext;
+ struct nhrp_cache *c;
+ union sockunion cie_nbma, cie_proto, *proto_addr, *nbma_addr,
+ *nbma_natoa;
+ int holdtime, prefix_len, hostprefix_len, natted = 0;
+ size_t paylen;
+ void *pay;
+
+ debugf(NHRP_DEBUG_COMMON, "Parsing and replying to Registration Req");
+ hostprefix_len = 8 * sockunion_get_addrlen(&p->if_ad->addr);
+
+ if (!sockunion_same(&p->src_nbma, &p->peer->vc->remote.nbma))
+ natted = 1;
+
+ /* Create reply */
+ zb = zbuf_alloc(1500);
+ hdr = nhrp_packet_push(zb, NHRP_PACKET_REGISTRATION_REPLY, &p->src_nbma,
+ &p->src_proto, &p->if_ad->addr);
+
+ /* Copied information from request */
+ hdr->flags = p->hdr->flags & htons(NHRP_FLAG_REGISTRATION_UNIQUE
+ | NHRP_FLAG_REGISTRATION_NAT);
+ hdr->u.request_id = p->hdr->u.request_id;
+
+ /* Copy payload CIEs */
+ paylen = zbuf_used(&p->payload);
+ pay = zbuf_pushn(zb, paylen);
+ if (!pay)
+ goto err;
+ memcpy(pay, zbuf_pulln(&p->payload, paylen), paylen);
+ zbuf_init(&payload, pay, paylen, paylen);
+
+ while ((cie = nhrp_cie_pull(&payload, hdr, &cie_nbma, &cie_proto))
+ != NULL) {
+ prefix_len = cie->prefix_length;
+ if (prefix_len == 0 || prefix_len >= hostprefix_len)
+ prefix_len = hostprefix_len;
+
+ if (prefix_len != hostprefix_len
+ && !(p->hdr->flags
+ & htons(NHRP_FLAG_REGISTRATION_UNIQUE))) {
+ cie->code = NHRP_CODE_BINDING_NON_UNIQUE;
+ continue;
+ }
+
+ /* We currently support only unique prefix registrations */
+ if (prefix_len != hostprefix_len) {
+ cie->code = NHRP_CODE_ADMINISTRATIVELY_PROHIBITED;
+ continue;
+ }
+
+ proto_addr = (sockunion_family(&cie_proto) == AF_UNSPEC)
+ ? &p->src_proto
+ : &cie_proto;
+ nbma_addr = (sockunion_family(&cie_nbma) == AF_UNSPEC)
+ ? &p->src_nbma
+ : &cie_nbma;
+ nbma_natoa = NULL;
+ if (natted) {
+ nbma_natoa =
+ (sockunion_family(&p->peer->vc->remote.nbma)
+ == AF_UNSPEC)
+ ? nbma_addr
+ : &p->peer->vc->remote.nbma;
+ }
+
+ holdtime = htons(cie->holding_time);
+ if (!holdtime)
+ holdtime = p->if_ad->holdtime;
+
+ c = nhrp_cache_get(ifp, proto_addr, 1);
+ if (!c) {
+ cie->code = NHRP_CODE_INSUFFICIENT_RESOURCES;
+ continue;
+ }
+
+ if (!nhrp_cache_update_binding(c, NHRP_CACHE_DYNAMIC, holdtime,
+ nhrp_peer_ref(p->peer),
+ htons(cie->mtu), nbma_natoa,
+ nbma_addr)) {
+ cie->code = NHRP_CODE_ADMINISTRATIVELY_PROHIBITED;
+ continue;
+ }
+
+ cie->code = NHRP_CODE_SUCCESS;
+ }
+
+ /* Handle extensions */
+ while ((ext = nhrp_ext_pull(&p->extensions, &payload)) != NULL) {
+ switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) {
+ case NHRP_EXTENSION_NAT_ADDRESS:
+ ext = nhrp_ext_push(zb, hdr,
+ NHRP_EXTENSION_NAT_ADDRESS);
+ if (!ext)
+ goto err;
+ zbuf_copy(zb, &payload, zbuf_used(&payload));
+ if (natted) {
+ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS,
+ &p->peer->vc->remote.nbma,
+ &p->src_proto);
+ cie->prefix_length =
+ 8 * sockunion_get_addrlen(
+ &p->if_ad->addr);
+ cie->mtu = htons(p->if_ad->mtu);
+ }
+ nhrp_ext_complete(zb, ext);
+ break;
+ default:
+ if (nhrp_ext_reply(zb, hdr, ifp, ext, &payload) < 0)
+ goto err;
+ break;
+ }
+ }
+
+ nhrp_packet_complete(zb, hdr);
+ nhrp_peer_send(p->peer, zb);
+err:
+ zbuf_free(zb);
+}
+
+static int parse_ether_packet(struct zbuf *zb, uint16_t protocol_type,
+ union sockunion *src, union sockunion *dst)
+{
+ switch (protocol_type) {
+ case ETH_P_IP: {
+ struct iphdr *iph = zbuf_pull(zb, struct iphdr);
+ if (iph) {
+ if (src)
+ sockunion_set(src, AF_INET,
+ (uint8_t *)&iph->saddr,
+ sizeof(iph->saddr));
+ if (dst)
+ sockunion_set(dst, AF_INET,
+ (uint8_t *)&iph->daddr,
+ sizeof(iph->daddr));
+ }
+ } break;
+ case ETH_P_IPV6: {
+ struct ipv6hdr *iph = zbuf_pull(zb, struct ipv6hdr);
+ if (iph) {
+ if (src)
+ sockunion_set(src, AF_INET6,
+ (uint8_t *)&iph->saddr,
+ sizeof(iph->saddr));
+ if (dst)
+ sockunion_set(dst, AF_INET6,
+ (uint8_t *)&iph->daddr,
+ sizeof(iph->daddr));
+ }
+ } break;
+ default:
+ return 0;
+ }
+ return 1;
+}
+
+void nhrp_peer_send_indication(struct interface *ifp, uint16_t protocol_type,
+ struct zbuf *pkt)
+{
+ union sockunion dst;
+ struct zbuf *zb, payload;
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_afi_data *if_ad;
+ struct nhrp_packet_header *hdr;
+ struct nhrp_peer *p;
+
+ if (!nifp->enabled)
+ return;
+
+ payload = *pkt;
+ if (!parse_ether_packet(&payload, protocol_type, &dst, NULL))
+ return;
+
+ if (nhrp_route_address(ifp, &dst, NULL, &p) != NHRP_ROUTE_NBMA_NEXTHOP)
+ return;
+
+ if_ad = &nifp->afi[family2afi(sockunion_family(&dst))];
+ if (!(if_ad->flags & NHRP_IFF_REDIRECT)) {
+ debugf(NHRP_DEBUG_COMMON,
+ "Send Traffic Indication to %pSU about packet to %pSU ignored",
+ &p->vc->remote.nbma, &dst);
+ return;
+ }
+
+ debugf(NHRP_DEBUG_COMMON,
+ "Send Traffic Indication to %pSU (online=%d) about packet to %pSU",
+ &p->vc->remote.nbma, p->online, &dst);
+
+ /* Create reply */
+ zb = zbuf_alloc(1500);
+ hdr = nhrp_packet_push(zb, NHRP_PACKET_TRAFFIC_INDICATION, &nifp->nbma,
+ &if_ad->addr, &dst);
+ hdr->hop_count = 1;
+
+ /* Payload is the packet causing indication */
+ zbuf_copy(zb, pkt, zbuf_used(pkt));
+ nhrp_packet_complete(zb, hdr);
+ nhrp_peer_send(p, zb);
+ nhrp_peer_unref(p);
+ zbuf_free(zb);
+}
+
+static void nhrp_handle_error_ind(struct nhrp_packet_parser *pp)
+{
+ struct zbuf origmsg = pp->payload;
+ struct nhrp_packet_header *hdr;
+ struct nhrp_reqid *reqid;
+ union sockunion src_nbma, src_proto, dst_proto;
+
+ hdr = nhrp_packet_pull(&origmsg, &src_nbma, &src_proto, &dst_proto);
+ if (!hdr)
+ return;
+
+ debugf(NHRP_DEBUG_COMMON,
+ "Error Indication from %pSU about packet to %pSU ignored",
+ &pp->src_proto, &dst_proto);
+
+ reqid = nhrp_reqid_lookup(&nhrp_packet_reqid, htonl(hdr->u.request_id));
+ if (reqid)
+ reqid->cb(reqid, pp);
+}
+
+static void nhrp_handle_traffic_ind(struct nhrp_packet_parser *p)
+{
+ union sockunion dst;
+
+ if (!parse_ether_packet(&p->payload, htons(p->hdr->protocol_type), NULL,
+ &dst))
+ return;
+
+ debugf(NHRP_DEBUG_COMMON,
+ "Traffic Indication from %pSU about packet to %pSU: %s",
+ &p->src_proto, &dst,
+ (p->if_ad->flags & NHRP_IFF_SHORTCUT) ? "trying shortcut"
+ : "ignored");
+
+ if (p->if_ad->flags & NHRP_IFF_SHORTCUT)
+ nhrp_shortcut_initiate(&dst);
+}
+
+enum packet_type_t {
+ PACKET_UNKNOWN = 0,
+ PACKET_REQUEST,
+ PACKET_REPLY,
+ PACKET_INDICATION,
+};
+
+static struct {
+ enum packet_type_t type;
+ const char *name;
+ void (*handler)(struct nhrp_packet_parser *);
+} packet_types[] = {[0] =
+ {
+ .type = PACKET_UNKNOWN,
+ .name = "UNKNOWN",
+ },
+ [NHRP_PACKET_RESOLUTION_REQUEST] =
+ {
+ .type = PACKET_REQUEST,
+ .name = "Resolution-Request",
+ .handler = nhrp_handle_resolution_req,
+ },
+ [NHRP_PACKET_RESOLUTION_REPLY] =
+ {
+ .type = PACKET_REPLY,
+ .name = "Resolution-Reply",
+ },
+ [NHRP_PACKET_REGISTRATION_REQUEST] =
+ {
+ .type = PACKET_REQUEST,
+ .name = "Registration-Request",
+ .handler = nhrp_handle_registration_request,
+ },
+ [NHRP_PACKET_REGISTRATION_REPLY] =
+ {
+ .type = PACKET_REPLY,
+ .name = "Registration-Reply",
+ },
+ [NHRP_PACKET_PURGE_REQUEST] =
+ {
+ .type = PACKET_REQUEST,
+ .name = "Purge-Request",
+ },
+ [NHRP_PACKET_PURGE_REPLY] =
+ {
+ .type = PACKET_REPLY,
+ .name = "Purge-Reply",
+ },
+ [NHRP_PACKET_ERROR_INDICATION] =
+ {
+ .type = PACKET_INDICATION,
+ .name = "Error-Indication",
+ .handler = nhrp_handle_error_ind,
+ },
+ [NHRP_PACKET_TRAFFIC_INDICATION] = {
+ .type = PACKET_INDICATION,
+ .name = "Traffic-Indication",
+ .handler = nhrp_handle_traffic_ind,
+ }};
+
+static void nhrp_peer_forward(struct nhrp_peer *p,
+ struct nhrp_packet_parser *pp)
+{
+ struct zbuf *zb, *zb_copy, extpl;
+ struct nhrp_packet_header *hdr;
+ struct nhrp_extension_header *ext, *dst;
+ struct nhrp_cie_header *cie;
+ struct nhrp_interface *nifp = pp->ifp->info;
+ struct nhrp_afi_data *if_ad = pp->if_ad;
+ union sockunion cie_nbma, cie_protocol, cie_protocol_mandatory, *proto;
+ uint16_t type, len;
+ struct nhrp_cache *c;
+
+ if (pp->hdr->hop_count == 0)
+ return;
+
+ /* Create forward packet - copy header */
+ zb = zbuf_alloc(1500);
+ zb_copy = zbuf_alloc(1500);
+
+ hdr = nhrp_packet_push(zb, pp->hdr->type, &pp->src_nbma, &pp->src_proto,
+ &pp->dst_proto);
+ hdr->flags = pp->hdr->flags;
+ hdr->hop_count = pp->hdr->hop_count - 1;
+ hdr->u.request_id = pp->hdr->u.request_id;
+
+ /* Copy payload */
+ zbuf_copy_peek(zb_copy, &pp->payload, zbuf_used(&pp->payload));
+ zbuf_copy(zb, &pp->payload, zbuf_used(&pp->payload));
+
+ /* Get CIE Extension from Mandatory part */
+ sockunion_family(&cie_protocol_mandatory) = AF_UNSPEC;
+ nhrp_cie_pull(zb_copy, pp->hdr, &cie_nbma, &cie_protocol_mandatory);
+
+ /* Copy extensions */
+ while ((ext = nhrp_ext_pull(&pp->extensions, &extpl)) != NULL) {
+ type = htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY;
+ len = htons(ext->length);
+
+ if (type == NHRP_EXTENSION_END)
+ break;
+
+ dst = nhrp_ext_push(zb, hdr, htons(ext->type));
+ if (!dst)
+ goto err;
+
+ switch (type) {
+ case NHRP_EXTENSION_FORWARD_TRANSIT_NHS:
+ case NHRP_EXTENSION_REVERSE_TRANSIT_NHS:
+ zbuf_put(zb, extpl.head, len);
+ if ((type == NHRP_EXTENSION_REVERSE_TRANSIT_NHS)
+ == (packet_types[hdr->type].type == PACKET_REPLY)) {
+ /* Check NHS list for forwarding loop */
+ while (nhrp_cie_pull(&extpl, pp->hdr,
+ &cie_nbma,
+ &cie_protocol) != NULL) {
+ if (sockunion_same(&p->vc->remote.nbma,
+ &cie_nbma))
+ goto err;
+ }
+ /* Append our selves to the list */
+ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS,
+ &nifp->nbma, &if_ad->addr);
+ if (!cie)
+ goto err;
+ cie->mtu = htons(if_ad->mtu);
+ cie->holding_time = htons(if_ad->holdtime);
+ }
+ break;
+ case NHRP_EXTENSION_NAT_ADDRESS:
+ c = NULL;
+ proto = NULL;
+
+ /* If NAT extension is empty then attempt to populate
+ * it with cached NBMA information
+ */
+ if (len == 0) {
+ if (packet_types[hdr->type].type
+ == PACKET_REQUEST) {
+ debugf(NHRP_DEBUG_COMMON,
+ "Processing NHRP_EXTENSION_NAT_ADDRESS while forwarding the request packet");
+ proto = &pp->src_proto;
+ } else if (packet_types[hdr->type].type
+ == PACKET_REPLY) {
+ debugf(NHRP_DEBUG_COMMON,
+ "Processing NHRP_EXTENSION_NAT_ADDRESS while forwarding the reply packet");
+ /* For reply packet use protocol
+ * specified in CIE of mandatory part
+ * for cache lookup
+ */
+ if (sockunion_family(
+ &cie_protocol_mandatory)
+ != AF_UNSPEC)
+ proto = &cie_protocol_mandatory;
+ }
+ }
+
+ if (proto) {
+ debugf(NHRP_DEBUG_COMMON, "Proto is %pSU",
+ proto);
+ c = nhrp_cache_get(nifp->ifp, proto, 0);
+ }
+
+ if (c) {
+ debugf(NHRP_DEBUG_COMMON,
+ "c->cur.remote_nbma_natoa is %pSU",
+ &c->cur.remote_nbma_natoa);
+ if (sockunion_family(&c->cur.remote_nbma_natoa)
+ != AF_UNSPEC) {
+ cie = nhrp_cie_push(
+ zb,
+ NHRP_CODE_SUCCESS,
+ &c->cur.remote_nbma_natoa,
+ proto);
+ if (!cie)
+ goto err;
+ }
+ } else {
+ if (proto)
+ debugf(NHRP_DEBUG_COMMON,
+ "No cache entry for proto %pSU",
+ proto);
+ /* Copy existing NAT extension to new packet if
+ * either it was already not-empty, or we do not
+ * have valid cache information
+ */
+ zbuf_put(zb, extpl.head, len);
+ }
+ break;
+ default:
+ if (htons(ext->type) & NHRP_EXTENSION_FLAG_COMPULSORY)
+ /* FIXME: RFC says to just copy, but not
+ * append our selves to the transit NHS list
+ */
+ goto err;
+ /* fallthru */
+ case NHRP_EXTENSION_RESPONDER_ADDRESS:
+ /* Supported compulsory extensions, and any
+ * non-compulsory that is not explicitly handled,
+ * should be just copied.
+ */
+ zbuf_copy(zb, &extpl, len);
+ break;
+ }
+ nhrp_ext_complete(zb, dst);
+ }
+
+ nhrp_packet_complete(zb, hdr);
+ nhrp_peer_send(p, zb);
+ zbuf_free(zb);
+ zbuf_free(zb_copy);
+ return;
+err:
+ nhrp_packet_debug(pp->pkt, "FWD-FAIL");
+ zbuf_free(zb);
+ zbuf_free(zb_copy);
+}
+
+static void nhrp_packet_debug(struct zbuf *zb, const char *dir)
+{
+ union sockunion src_nbma, src_proto, dst_proto;
+ struct nhrp_packet_header *hdr;
+ struct zbuf zhdr;
+ int reply;
+
+ if (likely(!(debug_flags & NHRP_DEBUG_COMMON)))
+ return;
+
+ zbuf_init(&zhdr, zb->buf, zb->tail - zb->buf, zb->tail - zb->buf);
+ hdr = nhrp_packet_pull(&zhdr, &src_nbma, &src_proto, &dst_proto);
+
+ reply = packet_types[hdr->type].type == PACKET_REPLY;
+ debugf(NHRP_DEBUG_COMMON, "%s %s(%d) %pSU -> %pSU", dir,
+ (packet_types[hdr->type].name ? packet_types[hdr->type].name
+ : "Unknown"),
+ hdr->type, reply ? &dst_proto : &src_proto,
+ reply ? &src_proto : &dst_proto);
+}
+
+static int proto2afi(uint16_t proto)
+{
+ switch (proto) {
+ case ETH_P_IP:
+ return AFI_IP;
+ case ETH_P_IPV6:
+ return AFI_IP6;
+ }
+ return AF_UNSPEC;
+}
+
+struct nhrp_route_info {
+ int local;
+ struct interface *ifp;
+ struct nhrp_vc *vc;
+};
+
+void nhrp_peer_recv(struct nhrp_peer *p, struct zbuf *zb)
+{
+ struct nhrp_packet_header *hdr;
+ struct nhrp_vc *vc = p->vc;
+ struct interface *ifp = p->ifp;
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_packet_parser pp;
+ struct nhrp_peer *peer = NULL;
+ struct nhrp_reqid *reqid;
+ const char *info = NULL;
+ union sockunion *target_addr;
+ unsigned paylen, extoff, extlen, realsize;
+ afi_t nbma_afi, proto_afi;
+
+ debugf(NHRP_DEBUG_KERNEL, "PACKET: Recv %pSU -> %pSU", &vc->remote.nbma,
+ &vc->local.nbma);
+
+ if (!p->online) {
+ info = "peer not online";
+ goto drop;
+ }
+
+ if (nhrp_packet_calculate_checksum(zb->head, zbuf_used(zb)) != 0) {
+ info = "bad checksum";
+ goto drop;
+ }
+
+ realsize = zbuf_used(zb);
+ hdr = nhrp_packet_pull(zb, &pp.src_nbma, &pp.src_proto, &pp.dst_proto);
+ if (!hdr) {
+ info = "corrupt header";
+ goto drop;
+ }
+
+ pp.ifp = ifp;
+ pp.pkt = zb;
+ pp.hdr = hdr;
+ pp.peer = p;
+
+ nbma_afi = htons(hdr->afnum);
+ proto_afi = proto2afi(htons(hdr->protocol_type));
+ if (hdr->type > NHRP_PACKET_MAX || hdr->version != NHRP_VERSION_RFC2332
+ || nbma_afi >= AFI_MAX || proto_afi == AF_UNSPEC
+ || packet_types[hdr->type].type == PACKET_UNKNOWN
+ || htons(hdr->packet_size) > realsize) {
+ zlog_info(
+ "From %pSU: error: packet type %d, version %d, AFI %d, proto %x, size %d (real size %d)",
+ &vc->remote.nbma, (int)hdr->type, (int)hdr->version,
+ (int)nbma_afi, (int)htons(hdr->protocol_type),
+ (int)htons(hdr->packet_size), (int)realsize);
+ goto drop;
+ }
+ pp.if_ad = &((struct nhrp_interface *)ifp->info)->afi[proto_afi];
+
+ extoff = htons(hdr->extension_offset);
+ if (extoff) {
+ assert(zb->head > zb->buf);
+ uint32_t header_offset = zb->head - zb->buf;
+ if (extoff >= realsize) {
+ info = "extoff larger than packet";
+ goto drop;
+ }
+ if (extoff < header_offset) {
+ info = "extoff smaller than header offset";
+ goto drop;
+ }
+ paylen = extoff - header_offset;
+ } else {
+ paylen = zbuf_used(zb);
+ }
+ zbuf_init(&pp.payload, zbuf_pulln(zb, paylen), paylen, paylen);
+ extlen = zbuf_used(zb);
+ zbuf_init(&pp.extensions, zbuf_pulln(zb, extlen), extlen, extlen);
+
+ if (!nifp->afi[proto_afi].network_id) {
+ info = "nhrp not enabled";
+ goto drop;
+ }
+
+ nhrp_packet_debug(zb, "Recv");
+
+ /* FIXME: Check authentication here. This extension needs to be
+ * pre-handled. */
+
+ /* Figure out if this is local */
+ target_addr = (packet_types[hdr->type].type == PACKET_REPLY)
+ ? &pp.src_proto
+ : &pp.dst_proto;
+
+ if (sockunion_same(&pp.src_proto, &pp.dst_proto))
+ pp.route_type = NHRP_ROUTE_LOCAL;
+ else
+ pp.route_type = nhrp_route_address(pp.ifp, target_addr,
+ &pp.route_prefix, &peer);
+
+ switch (pp.route_type) {
+ case NHRP_ROUTE_LOCAL:
+ nhrp_packet_debug(zb, "!LOCAL");
+ if (packet_types[hdr->type].type == PACKET_REPLY) {
+ reqid = nhrp_reqid_lookup(&nhrp_packet_reqid,
+ htonl(hdr->u.request_id));
+ if (reqid) {
+ reqid->cb(reqid, &pp);
+ break;
+ } else {
+ nhrp_packet_debug(zb, "!UNKNOWN-REQID");
+ /* FIXME: send error-indication */
+ }
+ }
+ /* fallthru */ /* FIXME: double check, is this correct? */
+ case NHRP_ROUTE_OFF_NBMA:
+ if (packet_types[hdr->type].handler) {
+ packet_types[hdr->type].handler(&pp);
+ break;
+ }
+ break;
+ case NHRP_ROUTE_NBMA_NEXTHOP:
+ nhrp_peer_forward(peer, &pp);
+ break;
+ case NHRP_ROUTE_BLACKHOLE:
+ break;
+ }
+
+drop:
+ if (info) {
+ zlog_info("From %pSU: error: %s", &vc->remote.nbma, info);
+ }
+ if (peer)
+ nhrp_peer_unref(peer);
+ zbuf_free(zb);
+}