Logo Search packages:      
Sourcecode: unbound version File versions  Download package

outside_network.c

Go to the documentation of this file.
/*
 * services/outside_network.c - implement sending of queries and wait answer.
 *
 * Copyright (c) 2007, NLnet Labs. All rights reserved.
 *
 * This software is open source.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * 
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * 
 * Neither the name of the NLNET LABS nor the names of its contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * \file
 *
 * This file has functions to send queries to authoritative servers and
 * wait for the pending answer events.
 */

#include "services/outside_network.h"
#include "services/listen_dnsport.h"
#include "services/cache/infra.h"
#include "util/data/msgparse.h"
#include "util/data/msgreply.h"
#include "util/data/msgencode.h"
#include "util/data/dname.h"
#include "util/netevent.h"
#include "util/log.h"
#include "util/net_help.h"
#include "util/random.h"
#include "util/fptr_wlist.h"

#ifdef HAVE_SYS_TYPES_H
#  include <sys/types.h>
#endif
#include <netdb.h>
#include <fcntl.h>

/** number of times to retry making a random ID that is unique. */
00063 #define MAX_ID_RETRY 1000
/** number of times to retry finding interface, port that can be opened. */
00065 #define MAX_PORT_RETRY 10000
/** number of retries on outgoing UDP queries */
00067 #define OUTBOUND_UDP_RETRY 1

/** initiate TCP transaction for serviced query */
static void serviced_tcp_initiate(struct outside_network* outnet, 
      struct serviced_query* sq, ldns_buffer* buff);
/** with a fd available, randomize and send UDP */
static int randomize_and_send_udp(struct outside_network* outnet, 
      struct pending* pend, ldns_buffer* packet, int timeout);

int 
00077 pending_cmp(const void* key1, const void* key2)
{
      struct pending *p1 = (struct pending*)key1;
      struct pending *p2 = (struct pending*)key2;
      if(p1->id < p2->id)
            return -1;
      if(p1->id > p2->id)
            return 1;
      log_assert(p1->id == p2->id);
      return sockaddr_cmp(&p1->addr, p1->addrlen, &p2->addr, p2->addrlen);
}

int 
00090 serviced_cmp(const void* key1, const void* key2)
{
      struct serviced_query* q1 = (struct serviced_query*)key1;
      struct serviced_query* q2 = (struct serviced_query*)key2;
      int r;
      if(q1->qbuflen < q2->qbuflen)
            return -1;
      if(q1->qbuflen > q2->qbuflen)
            return 1;
      log_assert(q1->qbuflen == q2->qbuflen);
      log_assert(q1->qbuflen >= 15 /* 10 header, root, type, class */);
      /* alternate casing of qname is still the same query */
      if((r = memcmp(q1->qbuf, q2->qbuf, 10)) != 0)
            return r;
      if((r = memcmp(q1->qbuf+q1->qbuflen-4, q2->qbuf+q2->qbuflen-4, 4)) != 0)
            return r;
      if(q1->dnssec != q2->dnssec) {
            if(q1->dnssec < q2->dnssec)
                  return -1;
            return 1;
      }
      if((r = query_dname_compare(q1->qbuf+10, q2->qbuf+10)) != 0)
            return r;
      return sockaddr_cmp(&q1->addr, q1->addrlen, &q2->addr, q2->addrlen);
}

/** delete waiting_tcp entry. Does not unlink from waiting list. 
 * @param w: to delete.
 */
static void
00120 waiting_tcp_delete(struct waiting_tcp* w)
{
      if(!w) return;
      if(w->timer)
            comm_timer_delete(w->timer);
      free(w);
}

/** use next free buffer to service a tcp query */
static int
00130 outnet_tcp_take_into_use(struct waiting_tcp* w, uint8_t* pkt, size_t pkt_len)
{
      struct pending_tcp* pend = w->outnet->tcp_free;
      int s;
      log_assert(pend);
      log_assert(pkt);
      log_assert(w->addrlen > 0);
      /* open socket */
#ifdef INET6
      if(addr_is_ip6(&w->addr, w->addrlen))
            s = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP);
      else
#endif
            s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
      if(s == -1) {
            log_err("outgoing tcp: socket: %s", strerror(errno));
            log_addr(0, "failed address", &w->addr, w->addrlen);
            return 0;
      }
      fd_set_nonblock(s);
      if(connect(s, (struct sockaddr*)&w->addr, w->addrlen) == -1) {
            if(errno != EINPROGRESS) {
                  log_err("outgoing tcp: connect: %s", strerror(errno));
                  log_addr(0, "failed address", &w->addr, w->addrlen);
                  close(s);
                  return 0;
            }
      }
      w->pkt = NULL;
      w->next_waiting = (void*)pend;
      pend->id = LDNS_ID_WIRE(pkt);
      w->outnet->tcp_free = pend->next_free;
      pend->next_free = NULL;
      pend->query = w;
      pend->c->repinfo.addrlen = w->addrlen;
      memcpy(&pend->c->repinfo.addr, &w->addr, w->addrlen);
      ldns_buffer_clear(pend->c->buffer);
      ldns_buffer_write(pend->c->buffer, pkt, pkt_len);
      ldns_buffer_flip(pend->c->buffer);
      pend->c->tcp_is_reading = 0;
      pend->c->tcp_byte_count = 0;
      comm_point_start_listening(pend->c, s, -1);
      return 1;
}

/** see if buffers can be used to service TCP queries */
static void
00177 use_free_buffer(struct outside_network* outnet)
{
      struct waiting_tcp* w;
      while(outnet->tcp_free && outnet->tcp_wait_first) {
            w = outnet->tcp_wait_first;
            outnet->tcp_wait_first = w->next_waiting;
            if(outnet->tcp_wait_last == w)
                  outnet->tcp_wait_last = NULL;
            if(!outnet_tcp_take_into_use(w, w->pkt, w->pkt_len)) {
                  comm_point_callback_t* cb = w->cb;
                  void* cb_arg = w->cb_arg;
                  waiting_tcp_delete(w);
                  fptr_ok(fptr_whitelist_pending_tcp(cb));
                  (void)(*cb)(NULL, cb_arg, NETEVENT_CLOSED, NULL);
            }
      }
}

/** decomission a tcp buffer, closes commpoint and frees waiting_tcp entry */
static void
00197 decomission_pending_tcp(struct outside_network* outnet, 
      struct pending_tcp* pend)
{
      comm_point_close(pend->c);
      pend->next_free = outnet->tcp_free;
      outnet->tcp_free = pend;
      waiting_tcp_delete(pend->query);
      pend->query = NULL;
      use_free_buffer(outnet);
}

int 
00209 outnet_tcp_cb(struct comm_point* c, void* arg, int error,
      struct comm_reply *reply_info)
{
      struct pending_tcp* pend = (struct pending_tcp*)arg;
      struct outside_network* outnet = pend->query->outnet;
      verbose(VERB_ALGO, "outnettcp cb");
      if(error != NETEVENT_NOERROR) {
            verbose(VERB_QUERY, "outnettcp got tcp error %d", error);
            /* pass error below and exit */
      } else {
            /* check ID */
            if(ldns_buffer_limit(c->buffer) < sizeof(uint16_t) ||
                  LDNS_ID_WIRE(ldns_buffer_begin(c->buffer))!=pend->id) {
                  log_addr(VERB_QUERY, 
                        "outnettcp: bad ID in reply, from:",
                        &pend->query->addr, pend->query->addrlen);
                  error = NETEVENT_CLOSED;
            }
      }
      fptr_ok(fptr_whitelist_pending_tcp(pend->query->cb));
      (void)(*pend->query->cb)(c, pend->query->cb_arg, error, reply_info);
      decomission_pending_tcp(outnet, pend);
      return 0;
}

/** lower use count on pc, see if it can be closed */
static void
00236 portcomm_loweruse(struct outside_network* outnet, struct port_comm* pc)
{
      struct port_if* pif;
      pc->num_outstanding--;
      if(pc->num_outstanding > 0) {
            return;
      }
      /* close it and replace in unused list */
      verbose(VERB_ALGO, "close of port %d", pc->number);
      comm_point_close(pc->cp);
      pif = pc->pif;
      log_assert(pif->inuse > 0);
      pif->avail_ports[pif->avail_total - pif->inuse] = pc->number;
      pif->inuse--;
      pif->out[pc->index] = pif->out[pif->inuse];
      pif->out[pc->index]->index = pc->index;
      pc->next = outnet->unused_fds;
      outnet->unused_fds = pc;
}

/** try to send waiting UDP queries */
static void
00258 outnet_send_wait_udp(struct outside_network* outnet)
{
      struct pending* pend;
      /* process waiting queries */
      while(outnet->udp_wait_first && outnet->unused_fds) {
            pend = outnet->udp_wait_first;
            outnet->udp_wait_first = pend->next_waiting;
            if(!pend->next_waiting) outnet->udp_wait_last = NULL;
            ldns_buffer_clear(outnet->udp_buff);
            ldns_buffer_write(outnet->udp_buff, pend->pkt, pend->pkt_len);
            ldns_buffer_flip(outnet->udp_buff);
            free(pend->pkt); /* freeing now makes get_mem correct */
            pend->pkt = NULL; 
            pend->pkt_len = 0;
            if(!randomize_and_send_udp(outnet, pend, outnet->udp_buff, 
                  pend->timeout)) {
                  /* callback error on pending */
                  fptr_ok(fptr_whitelist_pending_udp(pend->cb));
                  (void)(*pend->cb)(outnet->unused_fds->cp, pend->cb_arg, 
                        NETEVENT_CLOSED, NULL);
                  pending_delete(outnet, pend);
            }
      }
}

int 
00284 outnet_udp_cb(struct comm_point* c, void* arg, int error,
      struct comm_reply *reply_info)
{
      struct outside_network* outnet = (struct outside_network*)arg;
      struct pending key;
      struct pending* p;
      verbose(VERB_ALGO, "answer cb");

      if(error != NETEVENT_NOERROR) {
            verbose(VERB_QUERY, "outnetudp got udp error %d", error);
            return 0;
      }
      if(ldns_buffer_limit(c->buffer) < LDNS_HEADER_SIZE) {
            verbose(VERB_QUERY, "outnetudp udp too short");
            return 0;
      }
      log_assert(reply_info);

      /* setup lookup key */
      key.id = (unsigned)LDNS_ID_WIRE(ldns_buffer_begin(c->buffer));
      memcpy(&key.addr, &reply_info->addr, reply_info->addrlen);
      key.addrlen = reply_info->addrlen;
      verbose(VERB_ALGO, "Incoming reply id = %4.4x", key.id);
      log_addr(VERB_ALGO, "Incoming reply addr =", 
            &reply_info->addr, reply_info->addrlen);

      /* find it, see if this thing is a valid query response */
      verbose(VERB_ALGO, "lookup size is %d entries", (int)outnet->pending->count);
      p = (struct pending*)rbtree_search(outnet->pending, &key);
      if(!p) {
            verbose(VERB_QUERY, "received unwanted or unsolicited udp reply dropped.");
            log_buf(VERB_ALGO, "dropped message", c->buffer);
            return 0;
      }

      verbose(VERB_ALGO, "received udp reply.");
      log_buf(VERB_ALGO, "udp message", c->buffer);
      if(p->pc->cp != c) {
            verbose(VERB_QUERY, "received reply id,addr on wrong port. "
                  "dropped.");
            return 0;
      }
      comm_timer_disable(p->timer);
      verbose(VERB_ALGO, "outnet handle udp reply");
      /* delete from tree first in case callback creates a retry */
      (void)rbtree_delete(outnet->pending, p->node.key);
      fptr_ok(fptr_whitelist_pending_udp(p->cb));
      (void)(*p->cb)(p->pc->cp, p->cb_arg, NETEVENT_NOERROR, reply_info);
      portcomm_loweruse(outnet, p->pc);
      pending_delete(NULL, p);
      outnet_send_wait_udp(outnet);
      return 0;
}

/** calculate number of ip4 and ip6 interfaces*/
static void 
00340 calc_num46(char** ifs, int num_ifs, int do_ip4, int do_ip6, 
      int* num_ip4, int* num_ip6)
{
      int i;
      *num_ip4 = 0;
      *num_ip6 = 0;
      if(num_ifs <= 0) {
            if(do_ip4)
                  *num_ip4 = 1;
            if(do_ip6)
                  *num_ip6 = 1;
            return;
      }
      for(i=0; i<num_ifs; i++)
      {
            if(str_is_ip6(ifs[i])) {
                  if(do_ip6)
                        (*num_ip6)++;
            } else {
                  if(do_ip4)
                        (*num_ip4)++;
            }
      }

}

void 
00367 pending_udp_timer_cb(void *arg)
{
      struct pending* p = (struct pending*)arg;
      struct outside_network* outnet = p->outnet;
      /* it timed out */
      verbose(VERB_ALGO, "timeout udp");
      fptr_ok(fptr_whitelist_pending_udp(p->cb));
      (void)(*p->cb)(p->pc->cp, p->cb_arg, NETEVENT_TIMEOUT, NULL);
      portcomm_loweruse(outnet, p->pc);
      pending_delete(outnet, p);
      outnet_send_wait_udp(outnet);
}

/** create pending_tcp buffers */
static int
00382 create_pending_tcp(struct outside_network* outnet, size_t bufsize)
{
      size_t i;
      if(outnet->num_tcp == 0)
            return 1; /* no tcp needed, nothing to do */
      if(!(outnet->tcp_conns = (struct pending_tcp **)calloc(
                  outnet->num_tcp, sizeof(struct pending_tcp*))))
            return 0;
      for(i=0; i<outnet->num_tcp; i++) {
            if(!(outnet->tcp_conns[i] = (struct pending_tcp*)calloc(1, 
                  sizeof(struct pending_tcp))))
                  return 0;
            outnet->tcp_conns[i]->next_free = outnet->tcp_free;
            outnet->tcp_free = outnet->tcp_conns[i];
            outnet->tcp_conns[i]->c = comm_point_create_tcp_out(
                  outnet->base, bufsize, outnet_tcp_cb, 
                  outnet->tcp_conns[i]);
            if(!outnet->tcp_conns[i]->c)
                  return 0;
      }
      return 1;
}

/** setup an outgoing interface, ready address */
00406 static int setup_if(struct port_if* pif, const char* addrstr, 
      int* avail, int numavail, size_t numfd)
{
      pif->avail_total = numavail;
      pif->avail_ports = (int*)memdup(avail, (size_t)numavail*sizeof(int));
      if(!pif->avail_ports)
            return 0;
      if(!ipstrtoaddr(addrstr, UNBOUND_DNS_PORT, &pif->addr, &pif->addrlen))
            return 0;
      pif->maxout = (int)numfd;
      pif->inuse = 0;
      pif->out = (struct port_comm**)calloc(numfd, 
            sizeof(struct port_comm*));
      if(!pif->out)
            return 0;
      return 1;
}

struct outside_network* 
00425 outside_network_create(struct comm_base *base, size_t bufsize, 
      size_t num_ports, char** ifs, int num_ifs, int do_ip4, 
      int do_ip6, size_t num_tcp, struct infra_cache* infra,
      struct ub_randstate* rnd, int use_caps_for_id, int* availports, 
      int numavailports)
{
      struct outside_network* outnet = (struct outside_network*)
            calloc(1, sizeof(struct outside_network));
      size_t k;
      if(!outnet) {
            log_err("malloc failed");
            return NULL;
      }
      comm_base_timept(base, &outnet->now_secs, &outnet->now_tv);
      outnet->base = base;
      outnet->num_tcp = num_tcp;
      outnet->infra = infra;
      outnet->rnd = rnd;
      outnet->svcd_overhead = 0;
      outnet->use_caps_for_id = use_caps_for_id;
      if(numavailports == 0) {
            log_err("no outgoing ports available");
            outside_network_delete(outnet);
            return NULL;
      }
#ifndef INET6
      do_ip6 = 0;
#endif
      calc_num46(ifs, num_ifs, do_ip4, do_ip6, 
            &outnet->num_ip4, &outnet->num_ip6);
      if(outnet->num_ip4 != 0) {
            if(!(outnet->ip4_ifs = (struct port_if*)calloc(
                  (size_t)outnet->num_ip4, sizeof(struct port_if)))) {
                  log_err("malloc failed");
                  outside_network_delete(outnet);
                  return NULL;
            }
      }
      if(outnet->num_ip6 != 0) {
            if(!(outnet->ip6_ifs = (struct port_if*)calloc(
                  (size_t)outnet->num_ip6, sizeof(struct port_if)))) {
                  log_err("malloc failed");
                  outside_network_delete(outnet);
                  return NULL;
            }
      }
      if(   !(outnet->udp_buff = ldns_buffer_new(bufsize)) ||
            !(outnet->pending = rbtree_create(pending_cmp)) ||
            !(outnet->serviced = rbtree_create(serviced_cmp)) ||
            !create_pending_tcp(outnet, bufsize)) {
            log_err("malloc failed");
            outside_network_delete(outnet);
            return NULL;
      }

      /* allocate commpoints */
      for(k=0; k<num_ports; k++) {
            struct port_comm* pc;
            pc = (struct port_comm*)calloc(1, sizeof(*pc));
            if(!pc) {
                  log_err("malloc failed");
                  outside_network_delete(outnet);
                  return NULL;
            }
            pc->cp = comm_point_create_udp(outnet->base, -1, 
                  outnet->udp_buff, outnet_udp_cb, outnet);
            if(!pc->cp) {
                  log_err("malloc failed");
                  free(pc);
                  outside_network_delete(outnet);
                  return NULL;
            }
            pc->next = outnet->unused_fds;
            outnet->unused_fds = pc;
      }

      /* allocate interfaces */
      if(num_ifs == 0) {
            if(do_ip4 && !setup_if(&outnet->ip4_ifs[0], "0.0.0.0", 
                  availports, numavailports, num_ports)) {
                  log_err("malloc failed");
                  outside_network_delete(outnet);
                  return NULL;
            }
            if(do_ip6 && !setup_if(&outnet->ip6_ifs[0], "::", 
                  availports, numavailports, num_ports)) {
                  log_err("malloc failed");
                  outside_network_delete(outnet);
                  return NULL;
            }
      } else {
            size_t done_4 = 0, done_6 = 0;
            int i;
            for(i=0; i<num_ifs; i++) {
                  if(str_is_ip6(ifs[i]) && do_ip6) {
                        if(!setup_if(&outnet->ip6_ifs[done_6], ifs[i],
                              availports, numavailports, num_ports)){
                              log_err("malloc failed");
                              outside_network_delete(outnet);
                              return NULL;
                        }
                        done_6++;
                  }
                  if(!str_is_ip6(ifs[i]) && do_ip4) {
                        if(!setup_if(&outnet->ip4_ifs[done_4], ifs[i],
                              availports, numavailports, num_ports)){
                              log_err("malloc failed");
                              outside_network_delete(outnet);
                              return NULL;
                        }
                        done_4++;
                  }
            }
      }
      return outnet;
}

/** helper pending delete */
static void
00544 pending_node_del(rbnode_t* node, void* arg)
{
      struct pending* pend = (struct pending*)node;
      struct outside_network* outnet = (struct outside_network*)arg;
      pending_delete(outnet, pend);
}

/** helper serviced delete */
static void
00553 serviced_node_del(rbnode_t* node, void* ATTR_UNUSED(arg))
{
      struct serviced_query* sq = (struct serviced_query*)node;
      struct service_callback* p = sq->cblist, *np;
      free(sq->qbuf);
      while(p) {
            np = p->next;
            free(p);
            p = np;
      }
      free(sq);
}

void 
00567 outside_network_delete(struct outside_network* outnet)
{
      if(!outnet)
            return;
      /* check every element, since we can be called on malloc error */
      if(outnet->pending) {
            /* free pending elements, but do no unlink from tree. */
            traverse_postorder(outnet->pending, pending_node_del, NULL);
            free(outnet->pending);
      }
      if(outnet->serviced) {
            traverse_postorder(outnet->serviced, serviced_node_del, NULL);
            free(outnet->serviced);
      }
      if(outnet->udp_buff)
            ldns_buffer_free(outnet->udp_buff);
      if(outnet->unused_fds) {
            struct port_comm* p = outnet->unused_fds, *np;
            while(p) {
                  np = p->next;
                  comm_point_delete(p->cp);
                  free(p);
                  p = np;
            }
            outnet->unused_fds = NULL;
      }
      if(outnet->ip4_ifs) {
            int i, k;
            for(i=0; i<outnet->num_ip4; i++) {
                  for(k=0; k<outnet->ip4_ifs[i].inuse; k++) {
                        struct port_comm* pc = outnet->ip4_ifs[i].
                              out[k];
                        comm_point_delete(pc->cp);
                        free(pc);
                  }
                  free(outnet->ip4_ifs[i].avail_ports);
                  free(outnet->ip4_ifs[i].out);
            }
            free(outnet->ip4_ifs);
      }
      if(outnet->ip6_ifs) {
            int i, k;
            for(i=0; i<outnet->num_ip6; i++) {
                  for(k=0; k<outnet->ip6_ifs[i].inuse; k++) {
                        struct port_comm* pc = outnet->ip6_ifs[i].
                              out[k];
                        comm_point_delete(pc->cp);
                        free(pc);
                  }
                  free(outnet->ip6_ifs[i].avail_ports);
                  free(outnet->ip6_ifs[i].out);
            }
            free(outnet->ip6_ifs);
      }
      if(outnet->tcp_conns) {
            size_t i;
            for(i=0; i<outnet->num_tcp; i++)
                  if(outnet->tcp_conns[i]) {
                        comm_point_delete(outnet->tcp_conns[i]->c);
                        waiting_tcp_delete(outnet->tcp_conns[i]->query);
                        free(outnet->tcp_conns[i]);
                  }
            free(outnet->tcp_conns);
      }
      if(outnet->tcp_wait_first) {
            struct waiting_tcp* p = outnet->tcp_wait_first, *np;
            while(p) {
                  np = p->next_waiting;
                  waiting_tcp_delete(p);
                  p = np;
            }
      }
      if(outnet->udp_wait_first) {
            struct pending* p = outnet->udp_wait_first, *np;
            while(p) {
                  np = p->next_waiting;
                  pending_delete(NULL, p);
                  p = np;
            }
      }
      free(outnet);
}

void 
00651 pending_delete(struct outside_network* outnet, struct pending* p)
{
      if(!p)
            return;
      if(outnet) {
            (void)rbtree_delete(outnet->pending, p->node.key);
      }
      if(p->timer)
            comm_timer_delete(p->timer);
      free(p->pkt);
      free(p);
}

/**
 * Try to open a UDP socket for outgoing communication.
 * Sets sockets options as needed.
 * @param addr: socket address.
 * @param addrlen: length of address.
 * @param port: port override for addr.
 * @param inuse: if -1 is returned, this bool means the port was in use.
 * @return fd or -1
 */
static int
00674 udp_sockport(struct sockaddr_storage* addr, socklen_t addrlen, int port, 
      int* inuse)
{
      int fd;
      if(addr_is_ip6(addr, addrlen)) {
            struct sockaddr_in6* sa = (struct sockaddr_in6*)addr;
            sa->sin6_port = (in_port_t)htons((uint16_t)port);
            fd = create_udp_sock(AF_INET6, SOCK_DGRAM, 
                  (struct sockaddr*)addr, addrlen, 1, inuse);
      } else {
            struct sockaddr_in* sa = (struct sockaddr_in*)addr;
            sa->sin_port = (in_port_t)htons((uint16_t)port);
            fd = create_udp_sock(AF_INET, SOCK_DGRAM, 
                  (struct sockaddr*)addr, addrlen, 1, inuse);
      }
      return fd;
}

/** Select random ID */
static int
00694 select_id(struct outside_network* outnet, struct pending* pend,
      ldns_buffer* packet)
{
      int id_tries = 0;
      pend->id = ((unsigned)ub_random(outnet->rnd)>>8) & 0xffff;
      LDNS_ID_SET(ldns_buffer_begin(packet), pend->id);

      /* insert in tree */
      pend->node.key = pend;
      while(!rbtree_insert(outnet->pending, &pend->node)) {
            /* change ID to avoid collision */
            pend->id = ((unsigned)ub_random(outnet->rnd)>>8) & 0xffff;
            LDNS_ID_SET(ldns_buffer_begin(packet), pend->id);
            id_tries++;
            if(id_tries == MAX_ID_RETRY) {
                  pend->id=99999; /* non existant ID */
                  log_err("failed to generate unique ID, drop msg");
                  return 0;
            }
      }
      verbose(VERB_ALGO, "inserted new pending reply id=%4.4x", pend->id);
      return 1;
}

/** Select random interface and port */
static int
00720 select_ifport(struct outside_network* outnet, struct pending* pend,
      int num_if, struct port_if* ifs)
{
      int my_if, my_port, fd, portno, inuse, tries=0;
      struct port_if* pif;
      /* randomly select interface and port */
      if(num_if == 0) {
            verbose(VERB_QUERY, "Need to send query but have no "
                  "outgoing interfaces of that family");
            return 0;
      }
      log_assert(outnet->unused_fds);
      tries = 0;
      while(1) {
            my_if = ub_random(outnet->rnd) % num_if;
            pif = &ifs[my_if];
            my_port = ub_random(outnet->rnd) % pif->avail_total;
            if(my_port < pif->inuse) {
                  /* port already open */
                  pend->pc = pif->out[my_port];
                  verbose(VERB_ALGO, "using UDP if=%d port=%d", 
                        my_if, pend->pc->number);
                  break;
            }
            /* try to open new port, if fails, loop to try again */
            log_assert(pif->inuse < pif->maxout);
            portno = pif->avail_ports[my_port - pif->inuse];
            fd = udp_sockport(&pif->addr, pif->addrlen, portno, &inuse);
            if(fd == -1 && !inuse) {
                  /* nonrecoverable error making socket */
                  return 0;
            }
            if(fd != -1) {
                  verbose(VERB_ALGO, "opened UDP if=%d port=%d", 
                        my_if, portno);
                  /* grab fd */
                  pend->pc = outnet->unused_fds;
                  outnet->unused_fds = pend->pc->next;

                  /* setup portcomm */
                  pend->pc->next = NULL;
                  pend->pc->number = portno;
                  pend->pc->pif = pif;
                  pend->pc->index = pif->inuse;
                  pend->pc->num_outstanding = 0;
                  comm_point_start_listening(pend->pc->cp, fd, -1);

                  /* grab port in interface */
                  pif->out[pif->inuse] = pend->pc;
                  pif->avail_ports[my_port - pif->inuse] =
                        pif->avail_ports[pif->avail_total-pif->inuse-1];
                  pif->inuse++;
                  break;
            }
            /* failed, already in use */
            verbose(VERB_QUERY, "port %d in use, trying another", portno);
            tries++;
            if(tries == MAX_PORT_RETRY) {
                  log_err("failed to find an open port, drop msg");
                  return 0;
            }
      }
      log_assert(pend->pc);
      pend->pc->num_outstanding++;

      return 1;
}

static int
00789 randomize_and_send_udp(struct outside_network* outnet, struct pending* pend,
      ldns_buffer* packet, int timeout)
{
      struct timeval tv;

      /* select id */
      if(!select_id(outnet, pend, packet)) {
            return 0;
      }

      /* select src_if, port */
      if(addr_is_ip6(&pend->addr, pend->addrlen)) {
            if(!select_ifport(outnet, pend, 
                  outnet->num_ip6, outnet->ip6_ifs))
                  return 0;
      } else {
            if(!select_ifport(outnet, pend, 
                  outnet->num_ip4, outnet->ip4_ifs))
                  return 0;
      }
      log_assert(pend->pc && pend->pc->cp);

      /* send it over the commlink */
      if(!comm_point_send_udp_msg(pend->pc->cp, packet, 
            (struct sockaddr*)&pend->addr, pend->addrlen)) {
            portcomm_loweruse(outnet, pend->pc);
            return 0;
      }

      /* system calls to set timeout after sending UDP to make roundtrip
         smaller. */
      tv.tv_sec = timeout/1000;
      tv.tv_usec = (timeout%1000)*1000;
      comm_timer_set(pend->timer, &tv);
      return 1;
}

struct pending* 
00827 pending_udp_query(struct outside_network* outnet, ldns_buffer* packet, 
      struct sockaddr_storage* addr, socklen_t addrlen, int timeout,
      comm_point_callback_t* cb, void* cb_arg)
{
      struct pending* pend = (struct pending*)calloc(1, sizeof(*pend));
      if(!pend) return NULL;
      pend->outnet = outnet;
      pend->addrlen = addrlen;
      memmove(&pend->addr, addr, addrlen);
      pend->cb = cb;
      pend->cb_arg = cb_arg;
      pend->node.key = pend;
      pend->timer = comm_timer_create(outnet->base, pending_udp_timer_cb, 
            pend);
      if(!pend->timer) {
            free(pend);
            return NULL;
      }

      if(outnet->unused_fds == NULL) {
            /* no unused fd, cannot create a new port (randomly) */
            verbose(VERB_ALGO, "no fds available, udp query waiting");
            pend->timeout = timeout;
            pend->pkt_len = ldns_buffer_limit(packet);
            pend->pkt = (uint8_t*)memdup(ldns_buffer_begin(packet),
                  pend->pkt_len);
            if(!pend->pkt) {
                  comm_timer_delete(pend->timer);
                  free(pend);
                  return NULL;
            }
            /* put at end of waiting list */
            if(outnet->udp_wait_last)
                  outnet->udp_wait_last->next_waiting = pend;
            else 
                  outnet->udp_wait_first = pend;
            outnet->udp_wait_last = pend;
            return pend;
      }
      if(!randomize_and_send_udp(outnet, pend, packet, timeout)) {
            pending_delete(outnet, pend);
            return NULL;
      }
      return pend;
}

void
00874 outnet_tcptimer(void* arg)
{
      struct waiting_tcp* w = (struct waiting_tcp*)arg;
      struct outside_network* outnet = w->outnet;
      comm_point_callback_t* cb;
      void* cb_arg;
      if(w->pkt) {
            /* it is on the waiting list */
            struct waiting_tcp* p=outnet->tcp_wait_first, *prev=NULL;
            while(p) {
                  if(p == w) {
                        if(prev) prev->next_waiting = w->next_waiting;
                        else  outnet->tcp_wait_first=w->next_waiting;
                        outnet->tcp_wait_last = prev;
                        break;
                  }
                  prev = p;
                  p=p->next_waiting;
            }
      } else {
            /* it was in use */
            struct pending_tcp* pend=(struct pending_tcp*)w->next_waiting;
            comm_point_close(pend->c);
            pend->query = NULL;
            pend->next_free = outnet->tcp_free;
            outnet->tcp_free = pend;
      }
      cb = w->cb;
      cb_arg = w->cb_arg;
      waiting_tcp_delete(w);
      fptr_ok(fptr_whitelist_pending_tcp(cb));
      (void)(*cb)(NULL, cb_arg, NETEVENT_TIMEOUT, NULL);
      use_free_buffer(outnet);
}

struct waiting_tcp* 
00910 pending_tcp_query(struct outside_network* outnet, ldns_buffer* packet, 
      struct sockaddr_storage* addr, socklen_t addrlen, int timeout,
      comm_point_callback_t* callback, void* callback_arg)
{
      struct pending_tcp* pend = outnet->tcp_free;
      struct waiting_tcp* w;
      struct timeval tv;
      uint16_t id;
      /* if no buffer is free allocate space to store query */
      w = (struct waiting_tcp*)malloc(sizeof(struct waiting_tcp) 
            + (pend?0:ldns_buffer_limit(packet)));
      if(!w) {
            return NULL;
      }
      if(!(w->timer = comm_timer_create(outnet->base, outnet_tcptimer, w))) {
            free(w);
            return NULL;
      }
      w->pkt = NULL;
      w->pkt_len = 0;
      id = ((unsigned)ub_random(outnet->rnd)>>8) & 0xffff;
      LDNS_ID_SET(ldns_buffer_begin(packet), id);
      memcpy(&w->addr, addr, addrlen);
      w->addrlen = addrlen;
      w->outnet = outnet;
      w->cb = callback;
      w->cb_arg = callback_arg;
      tv.tv_sec = timeout;
      tv.tv_usec = 0;
      comm_timer_set(w->timer, &tv);
      if(pend) {
            /* we have a buffer available right now */
            if(!outnet_tcp_take_into_use(w, ldns_buffer_begin(packet),
                  ldns_buffer_limit(packet))) {
                  waiting_tcp_delete(w);
                  return NULL;
            }
      } else {
            /* queue up */
            w->pkt = (uint8_t*)w + sizeof(struct waiting_tcp);
            w->pkt_len = ldns_buffer_limit(packet);
            memmove(w->pkt, ldns_buffer_begin(packet), w->pkt_len);
            w->next_waiting = NULL;
            if(outnet->tcp_wait_last)
                  outnet->tcp_wait_last->next_waiting = w;
            else  outnet->tcp_wait_first = w;
            outnet->tcp_wait_last = w;
      }
      return w;
}

/** create query for serviced queries */
static void
00963 serviced_gen_query(ldns_buffer* buff, uint8_t* qname, size_t qnamelen, 
      uint16_t qtype, uint16_t qclass, uint16_t flags)
{
      ldns_buffer_clear(buff);
      /* skip id */
      ldns_buffer_write_u16(buff, flags);
      ldns_buffer_write_u16(buff, 1); /* qdcount */
      ldns_buffer_write_u16(buff, 0); /* ancount */
      ldns_buffer_write_u16(buff, 0); /* nscount */
      ldns_buffer_write_u16(buff, 0); /* arcount */
      ldns_buffer_write(buff, qname, qnamelen);
      ldns_buffer_write_u16(buff, qtype);
      ldns_buffer_write_u16(buff, qclass);
      ldns_buffer_flip(buff);
}

/** lookup serviced query in serviced query rbtree */
static struct serviced_query*
00981 lookup_serviced(struct outside_network* outnet, ldns_buffer* buff, int dnssec,
      struct sockaddr_storage* addr, socklen_t addrlen)
{
      struct serviced_query key;
      key.node.key = &key;
      key.qbuf = ldns_buffer_begin(buff);
      key.qbuflen = ldns_buffer_limit(buff);
      key.dnssec = dnssec;
      memcpy(&key.addr, addr, addrlen);
      key.addrlen = addrlen;
      key.outnet = outnet;
      return (struct serviced_query*)rbtree_search(outnet->serviced, &key);
}

/** Create new serviced entry */
static struct serviced_query*
00997 serviced_create(struct outside_network* outnet, ldns_buffer* buff, int dnssec,
        struct sockaddr_storage* addr, socklen_t addrlen)
{
      struct serviced_query* sq = (struct serviced_query*)malloc(sizeof(*sq));
      rbnode_t* ins;
      if(!sq) 
            return NULL;
      sq->node.key = sq;
      sq->qbuf = memdup(ldns_buffer_begin(buff), ldns_buffer_limit(buff));
      if(!sq->qbuf) {
            free(sq);
            return NULL;
      }
      sq->qbuflen = ldns_buffer_limit(buff);
      sq->dnssec = dnssec;
      memcpy(&sq->addr, addr, addrlen);
      sq->addrlen = addrlen;
      sq->outnet = outnet;
      sq->cblist = NULL;
      sq->pending = NULL;
      sq->status = serviced_initial;
      sq->retry = 0;
      sq->to_be_deleted = 0;
      ins = rbtree_insert(outnet->serviced, &sq->node);
      log_assert(ins != NULL); /* must not be already present */
      return sq;
}

/** remove waiting tcp from the outnet waiting list */
static void
01027 waiting_list_remove(struct outside_network* outnet, struct waiting_tcp* w)
{
      struct waiting_tcp* p = outnet->tcp_wait_first, *prev = NULL;
      while(p) {
            if(p == w) {
                  /* remove w */
                  if(prev)
                        prev->next_waiting = w->next_waiting;
                  else  outnet->tcp_wait_first = w->next_waiting;
                  if(outnet->tcp_wait_last == w)
                        outnet->tcp_wait_last = prev;
                  return;
            }
            prev = p;
            p = p->next_waiting;
      }
}

/** cleanup serviced query entry */
static void
01047 serviced_delete(struct serviced_query* sq)
{
      if(sq->pending) {
            /* clear up the pending query */
            if(sq->status == serviced_query_UDP_EDNS ||
                  sq->status == serviced_query_UDP) {
                  struct pending* p = (struct pending*)sq->pending;
                  portcomm_loweruse(sq->outnet, p->pc);
                  pending_delete(sq->outnet, p);
                  outnet_send_wait_udp(sq->outnet);
            } else {
                  struct waiting_tcp* p = (struct waiting_tcp*)
                        sq->pending;
                  if(p->pkt == NULL) {
                        decomission_pending_tcp(sq->outnet, 
                              (struct pending_tcp*)p->next_waiting);
                  } else {
                        waiting_list_remove(sq->outnet, p);
                        waiting_tcp_delete(p);
                  }
            }
      }
      /* does not delete from tree, caller has to do that */
      serviced_node_del(&sq->node, NULL);
}

/** perturb a dname capitalization randomly */
static void
01075 serviced_perturb_qname(struct ub_randstate* rnd, uint8_t* qbuf, size_t len)
{
      uint8_t lablen;
      uint8_t* d = qbuf + 10;
      long int random = 0;
      int bits = 0;
      log_assert(len >= 10 + 5 /* offset qname, root, qtype, qclass */);
      lablen = *d++;
      while(lablen) {
            while(lablen--) {
                  /* only perturb A-Z, a-z */
                  if(isalpha((int)*d)) {
                        /* get a random bit */  
                        if(bits == 0) {
                              random = ub_random(rnd);
                              bits = 30;
                        }
                        if(random & 0x1) {
                              *d = (uint8_t)toupper((int)*d);
                        } else {
                              *d = (uint8_t)tolower((int)*d);
                        }
                        random >>= 1;
                        bits--;
                  }
                  d++;
            }
            lablen = *d++;
      }
      if(verbosity >= VERB_ALGO) {
            char buf[LDNS_MAX_DOMAINLEN+1];
            dname_str(qbuf+10, buf);
            verbose(VERB_ALGO, "qname perturbed to %s", buf);
      }
}

/** put serviced query into a buffer */
static void
01113 serviced_encode(struct serviced_query* sq, ldns_buffer* buff, int with_edns)
{
      /* if we are using 0x20 bits for ID randomness, perturb them */
      if(sq->outnet->use_caps_for_id) {
            serviced_perturb_qname(sq->outnet->rnd, sq->qbuf, sq->qbuflen);
      }
      /* generate query */
      ldns_buffer_clear(buff);
      ldns_buffer_write_u16(buff, 0); /* id placeholder */
      ldns_buffer_write(buff, sq->qbuf, sq->qbuflen);
      ldns_buffer_flip(buff);
      if(with_edns) {
            /* add edns section */
            struct edns_data edns;
            edns.edns_present = 1;
            edns.ext_rcode = 0;
            edns.edns_version = EDNS_ADVERTISED_VERSION;
            edns.udp_size = EDNS_ADVERTISED_SIZE;
            edns.bits = 0;
            if(sq->dnssec & EDNS_DO)
                  edns.bits = EDNS_DO;
            if(sq->dnssec & BIT_CD)
                  LDNS_CD_SET(ldns_buffer_begin(buff));
            attach_edns_record(buff, &edns);
      }
}

/**
 * Perform serviced query UDP sending operation.
 * Sends UDP with EDNS, unless infra host marked non EDNS.
 * @param sq: query to send.
 * @param buff: buffer scratch space.
 * @return 0 on error.
 */
static int
01148 serviced_udp_send(struct serviced_query* sq, ldns_buffer* buff)
{
      int rtt, vs;
      uint32_t now = *sq->outnet->now_secs;

      if(!infra_host(sq->outnet->infra, &sq->addr, sq->addrlen, now, &vs,
            &rtt))
            return 0;
      if(sq->status == serviced_initial) {
            if(vs != -1)
                  sq->status = serviced_query_UDP_EDNS;
            else  sq->status = serviced_query_UDP;
      }
      serviced_encode(sq, buff, sq->status == serviced_query_UDP_EDNS);
      sq->last_sent_time = *sq->outnet->now_tv;
      verbose(VERB_ALGO, "serviced query UDP timeout=%d msec", rtt);
      sq->pending = pending_udp_query(sq->outnet, buff, &sq->addr, 
            sq->addrlen, rtt, serviced_udp_callback, sq);
      if(!sq->pending)
            return 0;
      return 1;
}

/** check that perturbed qname is identical */
static int
01173 serviced_check_qname(ldns_buffer* pkt, uint8_t* qbuf, size_t qbuflen)
{
      uint8_t* d1 = ldns_buffer_at(pkt, 12);
      uint8_t* d2 = qbuf+10;
      uint8_t len1, len2;
      int count = 0;
      log_assert(qbuflen >= 15 /* 10 header, root, type, class */);
      len1 = *d1++;
      len2 = *d2++;
      if(ldns_buffer_limit(pkt) < 12+1+4) /* packet too small for qname */
            return 0;
      while(len1 != 0 || len2 != 0) {
            if(LABEL_IS_PTR(len1)) {
                  d1 = ldns_buffer_at(pkt, PTR_OFFSET(len1, *d1));
                  if(d1 >= ldns_buffer_at(pkt, ldns_buffer_limit(pkt)))
                        return 0;
                  len1 = *d1++;
                  if(count++ > MAX_COMPRESS_PTRS)
                        return 0;
                  continue;
            }
            if(d2 > qbuf+qbuflen)
                  return 0;
            if(len1 != len2)
                  return 0;
            if(len1 > LDNS_MAX_LABELLEN)
                  return 0;
            log_assert(len1 <= LDNS_MAX_LABELLEN);
            log_assert(len2 <= LDNS_MAX_LABELLEN);
            log_assert(len1 == len2 && len1 != 0);
            /* compare the labels - bitwise identical */
            if(memcmp(d1, d2, len1) != 0)
                  return 0;
            d1 += len1;
            d2 += len2;
            len1 = *d1++;
            len2 = *d2++;
      }
      return 1;
}

/** call the callbacks for a serviced query */
static void
01216 serviced_callbacks(struct serviced_query* sq, int error, struct comm_point* c,
      struct comm_reply* rep)
{
      struct service_callback* p = sq->cblist, *n;
      int dobackup = (sq->cblist && sq->cblist->next); /* >1 cb*/
      uint8_t *backup_p = NULL;
      size_t backlen = 0;
      rbnode_t* rem;
      /* remove from tree, and schedule for deletion, so that callbacks
       * can safely deregister themselves and even create new serviced
       * queries that are identical to this one. */
      rem = rbtree_delete(sq->outnet->serviced, sq);
      log_assert(rem); /* should have been present */
      sq->to_be_deleted = 1; 
      verbose(VERB_ALGO, "svcd callbacks start");
      if(sq->outnet->use_caps_for_id && error == NETEVENT_NOERROR && c) {
            /* noerror and nxdomain must have a qname in reply */
            if(ldns_buffer_read_u16_at(c->buffer, 4) == 0 &&
                  (LDNS_RCODE_WIRE(ldns_buffer_begin(c->buffer))
                        == LDNS_RCODE_NOERROR || 
                   LDNS_RCODE_WIRE(ldns_buffer_begin(c->buffer))
                        == LDNS_RCODE_NXDOMAIN)) {
                  verbose(VERB_OPS, "no qname in reply to check 0x20ID");
                  log_addr(VERB_OPS, "from server", 
                        &sq->addr, sq->addrlen);
                  log_buf(VERB_OPS, "for packet", c->buffer);
                  error = NETEVENT_CLOSED;
                  c = NULL;
            } else if(ldns_buffer_read_u16_at(c->buffer, 4) > 0 &&
                  !serviced_check_qname(c->buffer, sq->qbuf, 
                  sq->qbuflen)) {
                  verbose(VERB_OPS, "wrong 0x20-ID in reply qname, "
                        "answer dropped");
                  log_addr(VERB_OPS, "from server", 
                        &sq->addr, sq->addrlen);
                  log_buf(VERB_OPS, "for packet", c->buffer);
                  error = NETEVENT_CLOSED;
                  c = NULL;
            } else {
                  verbose(VERB_ALGO, "good 0x20-ID in reply qname");
                  /* cleanup caps, prettier cache contents. */
                  pkt_dname_tolower(c->buffer, 
                        ldns_buffer_at(c->buffer, 12));
            }
      }
      if(dobackup && c) {
            /* make a backup of the query, since the querystate processing
             * may send outgoing queries that overwrite the buffer.
             * use secondary buffer to store the query.
             * This is a data copy, but faster than packet to server */
            backlen = ldns_buffer_limit(c->buffer);
            backup_p = memdup(ldns_buffer_begin(c->buffer), backlen);
            if(!backup_p) {
                  log_err("malloc failure in serviced query callbacks");
                  error = NETEVENT_CLOSED;
                  c = NULL;
            }
            sq->outnet->svcd_overhead = backlen;
      }
      while(p) {
            n = p->next;
            if(dobackup && c) {
                  ldns_buffer_clear(c->buffer);
                  ldns_buffer_write(c->buffer, backup_p, backlen);
                  ldns_buffer_flip(c->buffer);
            }
            fptr_ok(fptr_whitelist_serviced_query(p->cb));
            (void)(*p->cb)(c, p->cb_arg, error, rep);
            p = n;
      }
      if(backup_p) {
            free(backup_p);
            sq->outnet->svcd_overhead = 0;
      }
      verbose(VERB_ALGO, "svcd callbacks end");
      log_assert(sq->cblist == NULL);
      serviced_delete(sq);
}

int 
01296 serviced_tcp_callback(struct comm_point* c, void* arg, int error,
        struct comm_reply* rep)
{
      struct serviced_query* sq = (struct serviced_query*)arg;
      struct comm_reply r2;
      sq->pending = NULL; /* removed after this callback */
      if(error != NETEVENT_NOERROR)
            log_addr(VERB_QUERY, "tcp error for address", 
                  &sq->addr, sq->addrlen);
      if(error==NETEVENT_NOERROR)
            infra_update_tcp_works(sq->outnet->infra, &sq->addr,
                  sq->addrlen);
      if(error==NETEVENT_NOERROR && sq->status == serviced_query_TCP_EDNS &&
            (LDNS_RCODE_WIRE(ldns_buffer_begin(c->buffer)) == 
            LDNS_RCODE_FORMERR || LDNS_RCODE_WIRE(ldns_buffer_begin(
            c->buffer)) == LDNS_RCODE_NOTIMPL) ) {
            if(!infra_edns_update(sq->outnet->infra, &sq->addr, 
                  sq->addrlen, -1, *sq->outnet->now_secs))
                  log_err("Out of memory caching no edns for host");
            sq->status = serviced_query_TCP;
            serviced_tcp_initiate(sq->outnet, sq, c->buffer);
            return 0;
      }
      /* insert address into reply info */
      if(!rep) {
            /* create one if there isn't (on errors) */
            rep = &r2;
            r2.c = c;
      }
      memcpy(&rep->addr, &sq->addr, sq->addrlen);
      rep->addrlen = sq->addrlen;
      serviced_callbacks(sq, error, c, rep);
      return 0;
}

static void
01332 serviced_tcp_initiate(struct outside_network* outnet, 
      struct serviced_query* sq, ldns_buffer* buff)
{
      serviced_encode(sq, buff, sq->status == serviced_query_TCP_EDNS);
      sq->pending = pending_tcp_query(outnet, buff, &sq->addr,
            sq->addrlen, TCP_AUTH_QUERY_TIMEOUT, serviced_tcp_callback, 
            sq);
      if(!sq->pending) {
            /* delete from tree so that a retry by above layer does not
             * clash with this entry */
            log_err("serviced_tcp_initiate: failed to send tcp query");
            serviced_callbacks(sq, NETEVENT_CLOSED, NULL, NULL);
      }
}

int 
01348 serviced_udp_callback(struct comm_point* c, void* arg, int error,
        struct comm_reply* rep)
{
      struct serviced_query* sq = (struct serviced_query*)arg;
      struct outside_network* outnet = sq->outnet;
      struct timeval now = *sq->outnet->now_tv;
      int fallback_tcp = 0;

      sq->pending = NULL; /* removed after callback */
      if(error == NETEVENT_TIMEOUT) {
            int rto = 0;
            sq->retry++;
            if(!(rto=infra_rtt_update(outnet->infra, &sq->addr, sq->addrlen,
                  -1, (uint32_t)now.tv_sec)))
                  log_err("out of memory in UDP exponential backoff");
            if(sq->retry < OUTBOUND_UDP_RETRY) {
                  log_name_addr(VERB_ALGO, "retry query", sq->qbuf+10,
                        &sq->addr, sq->addrlen);
                  if(!serviced_udp_send(sq, c->buffer)) {
                        serviced_callbacks(sq, NETEVENT_CLOSED, c, rep);
                  }
                  return 0;
            }
            if(rto >= RTT_MAX_TIMEOUT) {
                  fallback_tcp = 1;
                  /* UDP does not work, fallback to TCP below */
            } else {
                  serviced_callbacks(sq, NETEVENT_TIMEOUT, c, rep);
                  return 0;
            }
      }
      if(error == NETEVENT_NOERROR && sq->status == serviced_query_UDP_EDNS 
            && (LDNS_RCODE_WIRE(ldns_buffer_begin(c->buffer)) 
                  == LDNS_RCODE_FORMERR || LDNS_RCODE_WIRE(
                  ldns_buffer_begin(c->buffer)) == LDNS_RCODE_NOTIMPL)) {
            /* note no EDNS, fallback without EDNS */
            if(!infra_edns_update(outnet->infra, &sq->addr, sq->addrlen,
                  -1, (uint32_t)now.tv_sec)) {
                  log_err("Out of memory caching no edns for host");
            }
            sq->status = serviced_query_UDP;
            sq->retry = 0;
            if(!serviced_udp_send(sq, c->buffer)) {
                  serviced_callbacks(sq, NETEVENT_CLOSED, c, rep);
            }
            return 0;
      }
      if(LDNS_TC_WIRE(ldns_buffer_begin(c->buffer)) ||
            (error != NETEVENT_NOERROR && fallback_tcp)  ) {
            /* fallback to TCP */
            /* this discards partial UDP contents */
            if(sq->status == serviced_query_UDP_EDNS)
                  sq->status = serviced_query_TCP_EDNS;
            else  sq->status = serviced_query_TCP;
            serviced_tcp_initiate(outnet, sq, c->buffer);
            return 0;
      }
      /* yay! an answer */
      if(now.tv_sec > sq->last_sent_time.tv_sec ||
            (now.tv_sec == sq->last_sent_time.tv_sec &&
            now.tv_usec > sq->last_sent_time.tv_usec)) {
            /* convert from microseconds to milliseconds */
            int roundtime = (now.tv_sec - sq->last_sent_time.tv_sec)*1000
              + ((int)now.tv_usec - (int)sq->last_sent_time.tv_usec)/1000;
            verbose(VERB_ALGO, "measured roundtrip at %d msec", roundtime);
            log_assert(roundtime >= 0);
            if(!infra_rtt_update(outnet->infra, &sq->addr, sq->addrlen, 
                  roundtime, (uint32_t)now.tv_sec))
                  log_err("out of memory noting rtt.");
      }
      serviced_callbacks(sq, error, c, rep);
      return 0;
}

/** find callback in list */
static struct service_callback*
01424 callback_list_find(struct serviced_query* sq, void* cb_arg, 
      int (*arg_compare)(void*,void*))
{
      struct service_callback* p;
      for(p = sq->cblist; p; p = p->next) {
            if(arg_compare(p->cb_arg, cb_arg))
                  return p;
      }
      return NULL;
}

struct serviced_query* 
01436 outnet_serviced_query(struct outside_network* outnet,
      uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass,
      uint16_t flags, int dnssec, struct sockaddr_storage* addr,
      socklen_t addrlen, comm_point_callback_t* callback,
      void* callback_arg, ldns_buffer* buff, 
      int (*arg_compare)(void*,void*))
{
      struct serviced_query* sq;
      struct service_callback* cb;
      serviced_gen_query(buff, qname, qnamelen, qtype, qclass, flags);
      sq = lookup_serviced(outnet, buff, dnssec, addr, addrlen);
      if(sq) {
            /* see if it is a duplicate notification request for cb_arg */
            if(callback_list_find(sq, callback_arg, arg_compare)) {
                  return sq;
            }
      }
      if(!(cb = (struct service_callback*)malloc(sizeof(*cb))))
            return NULL;
      if(!sq) {
            /* make new serviced query entry */
            sq = serviced_create(outnet, buff, dnssec, addr, addrlen);
            if(!sq) {
                  free(cb);
                  return NULL;
            }
            /* perform first network action */
            if(!serviced_udp_send(sq, buff)) {
                  (void)rbtree_delete(outnet->serviced, sq);
                  free(sq->qbuf);
                  free(sq);
                  free(cb);
                  return NULL;
            }
      }
      /* add callback to list of callbacks */
      cb->cb = callback;
      cb->cb_arg = callback_arg;
      cb->next = sq->cblist;
      sq->cblist = cb;
      return sq;
}

/** remove callback from list */
static void
01481 callback_list_remove(struct serviced_query* sq, void* cb_arg)
{
      struct service_callback** pp = &sq->cblist;
      while(*pp) {
            if((*pp)->cb_arg == cb_arg) {
                  struct service_callback* del = *pp;
                  *pp = del->next;
                  free(del);
                  return;
            }
            pp = &(*pp)->next;
      }
}

01495 void outnet_serviced_query_stop(struct serviced_query* sq, void* cb_arg)
{
      if(!sq) 
            return;
      callback_list_remove(sq, cb_arg);
      /* if callbacks() routine scheduled deletion, let it do that */
      if(!sq->cblist && !sq->to_be_deleted) {
            rbnode_t* rem;
            rem = rbtree_delete(sq->outnet->serviced, sq);
            log_assert(rem); /* should be present */
            serviced_delete(sq); 
      }
}

/** get memory used by waiting tcp entry (in use or not) */
static size_t
01511 waiting_tcp_get_mem(struct waiting_tcp* w)
{
      size_t s;
      if(!w) return 0;
      s = sizeof(*w) + w->pkt_len;
      if(w->timer)
            s += comm_timer_get_mem(w->timer);
      return s;
}

/** get memory used by port if */
static size_t
01523 if_get_mem(struct port_if* pif)
{
      size_t s;
      int i;
      s = sizeof(*pif) + sizeof(int)*pif->avail_total +
            sizeof(struct port_comm*)*pif->maxout;
      for(i=0; i<pif->inuse; i++)
            s += sizeof(*pif->out[i]) + 
                  comm_point_get_mem(pif->out[i]->cp);
      return s;
}

/** get memory used by waiting udp */
static size_t
01537 waiting_udp_get_mem(struct pending* w)
{
      size_t s;
      s = sizeof(*w) + comm_timer_get_mem(w->timer) + w->pkt_len;
      return s;
}

01544 size_t outnet_get_mem(struct outside_network* outnet)
{
      size_t i;
      int k;
      struct waiting_tcp* w;
      struct pending* u;
      struct serviced_query* sq;
      struct service_callback* sb;
      struct port_comm* pc;
      size_t s = sizeof(*outnet) + sizeof(*outnet->base) + 
            sizeof(*outnet->udp_buff) + 
            ldns_buffer_capacity(outnet->udp_buff);
      /* second buffer is not ours */
      for(pc = outnet->unused_fds; pc; pc = pc->next) {
            s += sizeof(*pc) + comm_point_get_mem(pc->cp);
      }
      for(k=0; k<outnet->num_ip4; k++)
            s += if_get_mem(&outnet->ip4_ifs[k]);
      for(k=0; k<outnet->num_ip6; k++)
            s += if_get_mem(&outnet->ip6_ifs[k]);
      for(u=outnet->udp_wait_first; u; u=u->next_waiting)
            s += waiting_udp_get_mem(u);
      
      s += sizeof(struct pending_tcp*)*outnet->num_tcp;
      for(i=0; i<outnet->num_tcp; i++) {
            s += sizeof(struct pending_tcp);
            s += comm_point_get_mem(outnet->tcp_conns[i]->c);
            if(outnet->tcp_conns[i]->query)
                  s += waiting_tcp_get_mem(outnet->tcp_conns[i]->query);
      }
      for(w=outnet->tcp_wait_first; w; w = w->next_waiting)
            s += waiting_tcp_get_mem(w);
      s += sizeof(*outnet->pending);
      s += (sizeof(struct pending) + comm_timer_get_mem(NULL)) * 
            outnet->pending->count;
      s += sizeof(*outnet->serviced);
      s += outnet->svcd_overhead;
      RBTREE_FOR(sq, struct serviced_query*, outnet->serviced) {
            s += sizeof(*sq) + sq->qbuflen;
            for(sb = sq->cblist; sb; sb = sb->next)
                  s += sizeof(*sb);
      }
      return s;
}

size_t 
01590 serviced_get_mem(struct serviced_query* sq)
{
      struct service_callback* sb;
      size_t s;
      s = sizeof(*sq) + sq->qbuflen;
      for(sb = sq->cblist; sb; sb = sb->next)
            s += sizeof(*sb);
      if(sq->status == serviced_query_UDP_EDNS ||
            sq->status == serviced_query_UDP) {
            s += sizeof(struct pending);
            s += comm_timer_get_mem(NULL);
      } else {
            /* does not have size of the pkt pointer */
            /* always has a timer except on malloc failures */

            /* these sizes are part of the main outside network mem */
            /*
            s += sizeof(struct waiting_tcp);
            s += comm_timer_get_mem(NULL);
            */
      }
      return s;
}

Generated by  Doxygen 1.6.0   Back to index