nhrpd: implement next hop resolution protocol
This provides DMVPN support and integrates to strongSwan. Please read
README.nhrpd and README.kernel for more details.
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;
+}