summaryrefslogtreecommitdiffstats
path: root/bin/named/lwdgabn.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--bin/named/lwdgabn.c651
1 files changed, 651 insertions, 0 deletions
diff --git a/bin/named/lwdgabn.c b/bin/named/lwdgabn.c
new file mode 100644
index 0000000..01faa26
--- /dev/null
+++ b/bin/named/lwdgabn.c
@@ -0,0 +1,651 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* $Id: lwdgabn.c,v 1.24 2009/09/02 23:48:01 tbox Exp $ */
+
+/*! \file */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include <isc/netaddr.h>
+#include <isc/sockaddr.h>
+#include <isc/socket.h>
+#include <isc/string.h> /* Required for HP/UX (and others?) */
+#include <isc/util.h>
+
+#include <dns/adb.h>
+#include <dns/events.h>
+#include <dns/result.h>
+
+#include <named/types.h>
+#include <named/lwaddr.h>
+#include <named/lwdclient.h>
+#include <named/lwresd.h>
+#include <named/lwsearch.h>
+#include <named/sortlist.h>
+
+#define NEED_V4(c) ((((c)->find_wanted & LWRES_ADDRTYPE_V4) != 0) \
+ && ((c)->v4find == NULL))
+#define NEED_V6(c) ((((c)->find_wanted & LWRES_ADDRTYPE_V6) != 0) \
+ && ((c)->v6find == NULL))
+
+static isc_result_t start_find(ns_lwdclient_t *);
+static void restart_find(ns_lwdclient_t *);
+static void init_gabn(ns_lwdclient_t *);
+
+/*%
+ * Destroy any finds. This can be used to "start over from scratch" and
+ * should only be called when events are _not_ being generated by the finds.
+ */
+static void
+cleanup_gabn(ns_lwdclient_t *client) {
+ ns_lwdclient_log(50, "cleaning up client %p", client);
+
+ if (client->v6find != NULL) {
+ if (client->v6find == client->v4find)
+ client->v6find = NULL;
+ else
+ dns_adb_destroyfind(&client->v6find);
+ }
+ if (client->v4find != NULL)
+ dns_adb_destroyfind(&client->v4find);
+}
+
+static void
+setup_addresses(ns_lwdclient_t *client, dns_adbfind_t *find, unsigned int at) {
+ dns_adbaddrinfo_t *ai;
+ lwres_addr_t *addr;
+ int af;
+ const struct sockaddr *sa;
+ isc_result_t result;
+
+ if (at == DNS_ADBFIND_INET)
+ af = AF_INET;
+ else
+ af = AF_INET6;
+
+ ai = ISC_LIST_HEAD(find->list);
+ while (ai != NULL && client->gabn.naddrs < LWRES_MAX_ADDRS) {
+ sa = &ai->sockaddr.type.sa;
+ if (sa->sa_family != af)
+ goto next;
+
+ addr = &client->addrs[client->gabn.naddrs];
+
+ result = lwaddr_lwresaddr_fromsockaddr(addr, &ai->sockaddr);
+ if (result != ISC_R_SUCCESS)
+ goto next;
+
+ ns_lwdclient_log(50, "adding address %p, family %d, length %d",
+ addr->address, addr->family, addr->length);
+
+ client->gabn.naddrs++;
+ REQUIRE(!LWRES_LINK_LINKED(addr, link));
+ LWRES_LIST_APPEND(client->gabn.addrs, addr, link);
+
+ next:
+ ai = ISC_LIST_NEXT(ai, publink);
+ }
+}
+
+typedef struct {
+ isc_netaddr_t address;
+ int rank;
+} rankedaddress;
+
+static int
+addr_compare(const void *av, const void *bv) {
+ const rankedaddress *a = (const rankedaddress *) av;
+ const rankedaddress *b = (const rankedaddress *) bv;
+ return (a->rank - b->rank);
+}
+
+static void
+sort_addresses(ns_lwdclient_t *client) {
+ unsigned int naddrs;
+ rankedaddress *addrs;
+ isc_netaddr_t remote;
+ dns_addressorderfunc_t order;
+ const void *arg;
+ ns_lwresd_t *lwresd = client->clientmgr->listener->manager;
+ unsigned int i;
+ isc_result_t result;
+
+ naddrs = client->gabn.naddrs;
+
+ if (naddrs <= 1 || lwresd->view->sortlist == NULL)
+ return;
+
+ addrs = isc_mem_get(lwresd->mctx, sizeof(rankedaddress) * naddrs);
+ if (addrs == NULL)
+ return;
+
+ isc_netaddr_fromsockaddr(&remote, &client->address);
+ ns_sortlist_byaddrsetup(lwresd->view->sortlist,
+ &remote, &order, &arg);
+ if (order == NULL) {
+ isc_mem_put(lwresd->mctx, addrs,
+ sizeof(rankedaddress) * naddrs);
+ return;
+ }
+ for (i = 0; i < naddrs; i++) {
+ result = lwaddr_netaddr_fromlwresaddr(&addrs[i].address,
+ &client->addrs[i]);
+ INSIST(result == ISC_R_SUCCESS);
+ addrs[i].rank = (*order)(&addrs[i].address, arg);
+ }
+ qsort(addrs, naddrs, sizeof(rankedaddress), addr_compare);
+ for (i = 0; i < naddrs; i++) {
+ result = lwaddr_lwresaddr_fromnetaddr(&client->addrs[i],
+ &addrs[i].address);
+ INSIST(result == ISC_R_SUCCESS);
+ }
+
+ isc_mem_put(lwresd->mctx, addrs, sizeof(rankedaddress) * naddrs);
+}
+
+static void
+generate_reply(ns_lwdclient_t *client) {
+ isc_result_t result;
+ int lwres;
+ isc_region_t r;
+ lwres_buffer_t lwb;
+ ns_lwdclientmgr_t *cm;
+
+ cm = client->clientmgr;
+ lwb.base = NULL;
+
+ ns_lwdclient_log(50, "generating gabn reply for client %p", client);
+
+ /*
+ * We must make certain the client->find is not still active.
+ * If it is either the v4 or v6 answer, just set it to NULL and
+ * let the cleanup code destroy it. Otherwise, destroy it now.
+ */
+ if (client->find == client->v4find || client->find == client->v6find)
+ client->find = NULL;
+ else
+ if (client->find != NULL)
+ dns_adb_destroyfind(&client->find);
+
+ /*
+ * perhaps there are some here?
+ */
+ if (NEED_V6(client) && client->v4find != NULL)
+ client->v6find = client->v4find;
+
+ /*
+ * Run through the finds we have and wire them up to the gabn
+ * structure.
+ */
+ LWRES_LIST_INIT(client->gabn.addrs);
+ if (client->v4find != NULL)
+ setup_addresses(client, client->v4find, DNS_ADBFIND_INET);
+ if (client->v6find != NULL)
+ setup_addresses(client, client->v6find, DNS_ADBFIND_INET6);
+
+ /*
+ * If there are no addresses, try the next element in the search
+ * path, if there are any more. Otherwise, fall through into
+ * the error handling code below.
+ */
+ if (client->gabn.naddrs == 0) {
+ do {
+ result = ns_lwsearchctx_next(&client->searchctx);
+ if (result == ISC_R_SUCCESS) {
+ cleanup_gabn(client);
+ result = start_find(client);
+ if (result == ISC_R_SUCCESS)
+ return;
+ }
+ } while (result == ISC_R_SUCCESS);
+ }
+
+ /*
+ * Render the packet.
+ */
+ client->pkt.recvlength = LWRES_RECVLENGTH;
+ client->pkt.authtype = 0; /* XXXMLG */
+ client->pkt.authlength = 0;
+
+ /*
+ * If there are no addresses, return failure.
+ */
+ if (client->gabn.naddrs != 0)
+ client->pkt.result = LWRES_R_SUCCESS;
+ else
+ client->pkt.result = LWRES_R_NOTFOUND;
+
+ sort_addresses(client);
+
+ lwres = lwres_gabnresponse_render(cm->lwctx, &client->gabn,
+ &client->pkt, &lwb);
+ if (lwres != LWRES_R_SUCCESS)
+ goto out;
+
+ r.base = lwb.base;
+ r.length = lwb.used;
+ client->sendbuf = r.base;
+ client->sendlength = r.length;
+ result = ns_lwdclient_sendreply(client, &r);
+ if (result != ISC_R_SUCCESS)
+ goto out;
+
+ NS_LWDCLIENT_SETSEND(client);
+
+ /*
+ * All done!
+ */
+ cleanup_gabn(client);
+
+ return;
+
+ out:
+ cleanup_gabn(client);
+
+ if (lwb.base != NULL)
+ lwres_context_freemem(client->clientmgr->lwctx,
+ lwb.base, lwb.length);
+
+ ns_lwdclient_errorpktsend(client, LWRES_R_FAILURE);
+}
+
+/*
+ * Take the current real name, move it to an alias slot (if any are
+ * open) then put this new name in as the real name for the target.
+ *
+ * Return success if it can be rendered, otherwise failure. Note that
+ * not having enough alias slots open is NOT a failure.
+ */
+static isc_result_t
+add_alias(ns_lwdclient_t *client) {
+ isc_buffer_t b;
+ isc_result_t result;
+ uint16_t naliases;
+
+ b = client->recv_buffer;
+
+ /*
+ * Render the new name to the buffer.
+ */
+ result = dns_name_totext(dns_fixedname_name(&client->target_name),
+ true, &client->recv_buffer);
+ if (result != ISC_R_SUCCESS)
+ return (result);
+
+ /*
+ * Are there any open slots?
+ */
+ naliases = client->gabn.naliases;
+ if (naliases < LWRES_MAX_ALIASES) {
+ client->gabn.aliases[naliases] = client->gabn.realname;
+ client->gabn.aliaslen[naliases] = client->gabn.realnamelen;
+ client->gabn.naliases++;
+ }
+
+ /*
+ * Save this name away as the current real name.
+ */
+ client->gabn.realname = (char *)(b.base) + b.used;
+ client->gabn.realnamelen = client->recv_buffer.used - b.used;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+store_realname(ns_lwdclient_t *client) {
+ isc_buffer_t b;
+ isc_result_t result;
+ dns_name_t *tname;
+
+ b = client->recv_buffer;
+
+ tname = dns_fixedname_name(&client->target_name);
+ result = ns_lwsearchctx_current(&client->searchctx, tname);
+ if (result != ISC_R_SUCCESS)
+ return (result);
+
+ /*
+ * Render the new name to the buffer.
+ */
+ result = dns_name_totext(tname, true, &client->recv_buffer);
+ if (result != ISC_R_SUCCESS)
+ return (result);
+
+ /*
+ * Save this name away as the current real name.
+ */
+ client->gabn.realname = (char *) b.base + b.used;
+ client->gabn.realnamelen = client->recv_buffer.used - b.used;
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+process_gabn_finddone(isc_task_t *task, isc_event_t *ev) {
+ ns_lwdclient_t *client = ev->ev_arg;
+ isc_eventtype_t evtype;
+ bool claimed;
+
+ ns_lwdclient_log(50, "find done for task %p, client %p", task, client);
+
+ evtype = ev->ev_type;
+ isc_event_free(&ev);
+
+ /*
+ * No more info to be had? If so, we have all the good stuff
+ * right now, so we can render things.
+ */
+ claimed = false;
+ if (evtype == DNS_EVENT_ADBNOMOREADDRESSES) {
+ if (NEED_V4(client)) {
+ client->v4find = client->find;
+ claimed = true;
+ }
+ if (NEED_V6(client)) {
+ client->v6find = client->find;
+ claimed = true;
+ }
+ if (client->find != NULL) {
+ if (claimed)
+ client->find = NULL;
+ else
+ dns_adb_destroyfind(&client->find);
+
+ }
+ generate_reply(client);
+ return;
+ }
+
+ /*
+ * We probably don't need this find anymore. We're either going to
+ * reissue it, or an error occurred. Either way, we're done with
+ * it.
+ */
+ if ((client->find != client->v4find)
+ && (client->find != client->v6find)) {
+ dns_adb_destroyfind(&client->find);
+ } else {
+ client->find = NULL;
+ }
+
+ /*
+ * We have some new information we can gather. Run off and fetch
+ * it.
+ */
+ if (evtype == DNS_EVENT_ADBMOREADDRESSES) {
+ restart_find(client);
+ return;
+ }
+
+ /*
+ * An error or other strangeness happened. Drop this query.
+ */
+ cleanup_gabn(client);
+ ns_lwdclient_errorpktsend(client, LWRES_R_FAILURE);
+}
+
+static void
+restart_find(ns_lwdclient_t *client) {
+ unsigned int options;
+ isc_result_t result;
+ bool claimed;
+
+ ns_lwdclient_log(50, "starting find for client %p", client);
+
+ /*
+ * Issue a find for the name contained in the request. We won't
+ * set the bit that says "anything is good enough" -- we want it
+ * all.
+ */
+ options = 0;
+ options |= DNS_ADBFIND_WANTEVENT;
+ options |= DNS_ADBFIND_RETURNLAME;
+
+ /*
+ * Set the bits up here to mark that we want this address family
+ * and that we do not currently have a find pending. We will
+ * set that bit again below if it turns out we will get an event.
+ */
+ if (NEED_V4(client))
+ options |= DNS_ADBFIND_INET;
+ if (NEED_V6(client))
+ options |= DNS_ADBFIND_INET6;
+
+ find_again:
+ INSIST(client->find == NULL);
+ result = dns_adb_createfind(client->clientmgr->view->adb,
+ client->clientmgr->task,
+ process_gabn_finddone, client,
+ dns_fixedname_name(&client->target_name),
+ dns_rootname, 0, options, 0,
+ dns_fixedname_name(&client->target_name),
+ client->clientmgr->view->dstport,
+ &client->find);
+
+ /*
+ * Did we get an alias? If so, save it and re-issue the query.
+ */
+ if (result == DNS_R_ALIAS) {
+ ns_lwdclient_log(50, "found alias, restarting query");
+ dns_adb_destroyfind(&client->find);
+ cleanup_gabn(client);
+ result = add_alias(client);
+ if (result != ISC_R_SUCCESS) {
+ ns_lwdclient_log(50,
+ "out of buffer space adding alias");
+ ns_lwdclient_errorpktsend(client, LWRES_R_FAILURE);
+ return;
+ }
+ goto find_again;
+ }
+
+ ns_lwdclient_log(50, "find returned %d (%s)", result,
+ isc_result_totext(result));
+
+ /*
+ * Did we get an error?
+ */
+ if (result != ISC_R_SUCCESS) {
+ if (client->find != NULL)
+ dns_adb_destroyfind(&client->find);
+ cleanup_gabn(client);
+ ns_lwdclient_errorpktsend(client, LWRES_R_FAILURE);
+ return;
+ }
+
+ claimed = false;
+
+ /*
+ * Did we get our answer to V4 addresses?
+ */
+ if (NEED_V4(client)
+ && ((client->find->query_pending & DNS_ADBFIND_INET) == 0)) {
+ ns_lwdclient_log(50, "client %p ipv4 satisfied by find %p",
+ client, client->find);
+ claimed = true;
+ client->v4find = client->find;
+ }
+
+ /*
+ * Did we get our answer to V6 addresses?
+ */
+ if (NEED_V6(client)
+ && ((client->find->query_pending & DNS_ADBFIND_INET6) == 0)) {
+ ns_lwdclient_log(50, "client %p ipv6 satisfied by find %p",
+ client, client->find);
+ claimed = true;
+ client->v6find = client->find;
+ }
+
+ /*
+ * If we're going to get an event, set our internal pending flag
+ * and return. When we get an event back we'll do the right
+ * thing, basically by calling this function again, perhaps with a
+ * new target name.
+ *
+ * If we have both v4 and v6, and we are still getting an event,
+ * we have a programming error, so die hard.
+ */
+ if ((client->find->options & DNS_ADBFIND_WANTEVENT) != 0) {
+ ns_lwdclient_log(50, "event will be sent");
+ INSIST(client->v4find == NULL || client->v6find == NULL);
+ return;
+ }
+ ns_lwdclient_log(50, "no event will be sent");
+ if (claimed)
+ client->find = NULL;
+ else
+ dns_adb_destroyfind(&client->find);
+
+ /*
+ * We seem to have everything we asked for, or at least we are
+ * able to respond with things we've learned.
+ */
+
+ generate_reply(client);
+}
+
+static isc_result_t
+start_find(ns_lwdclient_t *client) {
+ isc_result_t result;
+
+ /*
+ * Initialize the real name and alias arrays in the reply we're
+ * going to build up.
+ */
+ init_gabn(client);
+
+ result = store_realname(client);
+ if (result != ISC_R_SUCCESS)
+ return (result);
+ restart_find(client);
+ return (ISC_R_SUCCESS);
+
+}
+
+static void
+init_gabn(ns_lwdclient_t *client) {
+ int i;
+
+ /*
+ * Initialize the real name and alias arrays in the reply we're
+ * going to build up.
+ */
+ for (i = 0; i < LWRES_MAX_ALIASES; i++) {
+ client->aliases[i] = NULL;
+ client->aliaslen[i] = 0;
+ }
+ for (i = 0; i < LWRES_MAX_ADDRS; i++) {
+ client->addrs[i].family = 0;
+ client->addrs[i].length = 0;
+ memset(client->addrs[i].address, 0, LWRES_ADDR_MAXLEN);
+ LWRES_LINK_INIT(&client->addrs[i], link);
+ }
+
+ client->gabn.naliases = 0;
+ client->gabn.naddrs = 0;
+ client->gabn.realname = NULL;
+ client->gabn.aliases = client->aliases;
+ client->gabn.realnamelen = 0;
+ client->gabn.aliaslen = client->aliaslen;
+ LWRES_LIST_INIT(client->gabn.addrs);
+ client->gabn.base = NULL;
+ client->gabn.baselen = 0;
+
+ /*
+ * Set up the internal buffer to point to the receive region.
+ */
+ isc_buffer_init(&client->recv_buffer, client->buffer, LWRES_RECVLENGTH);
+}
+
+/*
+ * When we are called, we can be assured that:
+ *
+ * client->sockaddr contains the address we need to reply to,
+ *
+ * client->pkt contains the packet header data,
+ *
+ * the packet "checks out" overall -- any MD5 hashes or crypto
+ * bits have been verified,
+ *
+ * "b" points to the remaining data after the packet header
+ * was parsed off.
+ *
+ * We are in a the RECVDONE state.
+ *
+ * From this state we will enter the SEND state if we happen to have
+ * everything we need or we need to return an error packet, or to the
+ * FINDWAIT state if we need to look things up.
+ */
+void
+ns_lwdclient_processgabn(ns_lwdclient_t *client, lwres_buffer_t *b) {
+ isc_result_t result;
+ lwres_gabnrequest_t *req;
+ ns_lwdclientmgr_t *cm;
+ isc_buffer_t namebuf;
+
+ REQUIRE(NS_LWDCLIENT_ISRECVDONE(client));
+
+ cm = client->clientmgr;
+ req = NULL;
+
+ result = lwres_gabnrequest_parse(client->clientmgr->lwctx,
+ b, &client->pkt, &req);
+ if (result != LWRES_R_SUCCESS)
+ goto out;
+ if (req->name == NULL)
+ goto out;
+
+ isc_buffer_init(&namebuf, req->name, req->namelen);
+ isc_buffer_add(&namebuf, req->namelen);
+
+ dns_fixedname_init(&client->target_name);
+ dns_fixedname_init(&client->query_name);
+ result = dns_name_fromtext(dns_fixedname_name(&client->query_name),
+ &namebuf, NULL, 0, NULL);
+ if (result != ISC_R_SUCCESS)
+ goto out;
+ ns_lwsearchctx_init(&client->searchctx,
+ cm->listener->manager->search,
+ dns_fixedname_name(&client->query_name),
+ cm->listener->manager->ndots);
+ ns_lwsearchctx_first(&client->searchctx);
+
+ client->find_wanted = req->addrtypes;
+ ns_lwdclient_log(50, "client %p looking for addrtypes %08x",
+ client, client->find_wanted);
+
+ /*
+ * We no longer need to keep this around.
+ */
+ lwres_gabnrequest_free(client->clientmgr->lwctx, &req);
+
+ /*
+ * Start the find.
+ */
+ result = start_find(client);
+ if (result != ISC_R_SUCCESS)
+ goto out;
+
+ return;
+
+ /*
+ * We're screwed. Return an error packet to our caller.
+ */
+ out:
+ if (req != NULL)
+ lwres_gabnrequest_free(client->clientmgr->lwctx, &req);
+
+ ns_lwdclient_errorpktsend(client, LWRES_R_FAILURE);
+}