Logo Search packages:      
Sourcecode: kadu version File versions

jdns.c

/*
 * Copyright (C) 2005-2008  Justin Karneges
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include "jdns_p.h"

#include <time.h>

#include "jdns_packet.h"
#include "jdns_mdnsd.h"

#define JDNS_UDP_UNI_OUT_MAX  512
#define JDNS_UDP_UNI_IN_MAX   16384
#define JDNS_UDP_MUL_OUT_MAX  9000
#define JDNS_UDP_MUL_IN_MAX   16384

// cache no more than 7 days
#define JDNS_TTL_MAX          (86400 * 7)
#define JDNS_CACHE_MAX        16384
#define JDNS_CNAME_MAX        16
#define JDNS_QUERY_MAX        4096

//----------------------------------------------------------------------------
// util
//----------------------------------------------------------------------------

// declare this here, but implement it later after we define jdns_session_t
static void _debug_line(jdns_session_t *s, const char *format, ...);

static unsigned char _hex_nibble(unsigned char c)
{
      if(c <= 9)
            return '0' + c;
      else if(c <= 15)
            return 'a' + (c - 10);
      else
            return '?';
}

static void _hex_byte(unsigned char c, unsigned char *dest)
{
      dest[0] = _hex_nibble((unsigned char)(c >> 4));
      dest[1] = _hex_nibble((unsigned char)(c & 0x0f));
}

static jdns_string_t *_make_printable(const unsigned char *str, int size)
{
      unsigned char *buf;
      int n, i;
      jdns_string_t *out;

      if(size == 0)
      {
            out = jdns_string_new();
            jdns_string_set_cstr(out, "");
            return out;
      }

      // make room for the largest possible result
      buf = (unsigned char *)malloc(size * 4);
      i = 0;
      for(n = 0; n < size; ++n)
      {
            unsigned char c = str[n];
            if(c == '\\')
            {
                  buf[i++] = '\\';
                  buf[i++] = '\\';
            }
            else if(c >= 0x20 && c < 0x7f)
            {
                  buf[i++] = c;
            }
            else
            {
                  buf[i++] = '\\';
                  buf[i++] = 'x';
                  _hex_byte(c, buf + i);
                  i += 2;
            }
      }

      out = jdns_string_new();
      jdns_string_set(out, buf, i);
      free(buf);
      return out;
}

static jdns_string_t *_make_printable_str(const jdns_string_t *str)
{
      return _make_printable(str->data, str->size);
}

static jdns_string_t *_make_printable_cstr(const char *str)
{
      return _make_printable((const unsigned char *)str, strlen(str));
}

static unsigned char *_fix_input(const unsigned char *in)
{
      unsigned char *out;
      int len;

      // truncate
      len = _ustrlen(in);
      if(len > 254)
            len = 254;

      // add a dot to the end if needed
      if(in[len - 1] != '.' && len < 254)
      {
            out = (unsigned char *)malloc(len + 2); // a dot and a zero
            memcpy(out, in, len);
            out[len] = '.';
            out[len+1] = 0;
            ++len;
      }
      else
      {
            out = (unsigned char *)malloc(len + 1); // a zero
            memcpy(out, in, len);
            out[len] = 0;
      }

      return out;
}

static const char *_qtype2str(int qtype)
{
      const char *str;
      switch(qtype)
      {
            case JDNS_RTYPE_A:     str = "A";     break;
            case JDNS_RTYPE_AAAA:  str = "AAAA";  break;
            case JDNS_RTYPE_MX:    str = "MX";    break;
            case JDNS_RTYPE_SRV:   str = "SRV";   break;
            case JDNS_RTYPE_CNAME: str = "CNAME"; break;
            case JDNS_RTYPE_PTR:   str = "PTR";   break;
            case JDNS_RTYPE_TXT:   str = "TXT";   break;
            case JDNS_RTYPE_HINFO: str = "HINFO"; break;
            case JDNS_RTYPE_NS:    str = "NS";    break;
            case JDNS_RTYPE_ANY:   str = "ANY";   break;
            default:               str = "";      break;
      }
      return str;
}

static int _cmp_rdata(const jdns_rr_t *a, const jdns_rr_t *b)
{
      if(a->rdlength != b->rdlength)
            return 0;
      if(memcmp(a->rdata, b->rdata, a->rdlength) != 0)
            return 0;
      return 1;
}

static int _cmp_rr(const jdns_rr_t *a, const jdns_rr_t *b)
{
      if(a->type != b->type)
            return 0;
      if(!jdns_domain_cmp(a->owner, b->owner))
            return 0;
      switch(a->type)
      {
            case JDNS_RTYPE_A:
                  if(!jdns_address_cmp(a->data.address, b->data.address))
                        return 0;
                  break;
            case JDNS_RTYPE_AAAA:
                  if(!_cmp_rdata(a, b))
                        return 0;
                  break;
            case JDNS_RTYPE_MX:
                  // unsupported
                  return 0;
            case JDNS_RTYPE_SRV:
                  if(a->data.server->port != b->data.server->port
                        || a->data.server->priority != b->data.server->priority
                        || a->data.server->weight != b->data.server->weight
                        || !jdns_domain_cmp(a->data.server->name, b->data.server->name)
                  )
                        return 0;
                  break;
            case JDNS_RTYPE_CNAME:
                  if(!jdns_domain_cmp(a->data.name, b->data.name))
                        return 0;
                  break;
            case JDNS_RTYPE_PTR:
                  if(!jdns_domain_cmp(a->data.name, b->data.name))
                        return 0;
                  break;
            case JDNS_RTYPE_TXT:
                  if(!_cmp_rdata(a, b))
                        return 0;
                  break;
            case JDNS_RTYPE_HINFO:
                  if(!_cmp_rdata(a, b))
                        return 0;
                  break;
            case JDNS_RTYPE_NS:
                  // unsupported
                  return 0;
            default:
                  if(!_cmp_rdata(a, b))
                        return 0;
                  break;
      }
      return 1;
}

static jdns_response_t *_packet2response(const jdns_packet_t *packet, const unsigned char *qname, int qtype, int classmask)
{
      int n;
      jdns_response_t *r;

      r = jdns_response_new();
      for(n = 0; n < packet->answerRecords->count; ++n)
      {
            jdns_packet_resource_t *res = (jdns_packet_resource_t *)packet->answerRecords->item[n];
            jdns_rr_t *rr;
            int put_in_answer;
            if((res->qclass & classmask) != 0x0001)
                  continue;
            rr = jdns_rr_from_resource(res, packet);
            if(!rr)
                  continue;
            // if qname is set, restrict answers to those that match
            //  the question
            put_in_answer = 1;
            if(qname)
            {
                  // name must match. type must either match or be CNAME,
                  //   unless the query was for any type
                  if((qtype != JDNS_RTYPE_ANY && res->qtype != qtype && res->qtype != JDNS_RTYPE_CNAME) || !jdns_domain_cmp(res->qname->data, qname))
                  {
                        // put unusable records in additional section
                        put_in_answer = 0;
                  }
            }
            if(put_in_answer)
                  jdns_response_append_answer(r, rr);
            else
                  jdns_response_append_additional(r, rr);
            jdns_rr_delete(rr);
      }
      for(n = 0; n < packet->authorityRecords->count; ++n)
      {
            jdns_packet_resource_t *res = (jdns_packet_resource_t *)packet->authorityRecords->item[n];
            jdns_rr_t *rr;
            if((res->qclass & classmask) != 0x0001)
                  continue;
            rr = jdns_rr_from_resource(res, packet);
            if(!rr)
                  continue;
            jdns_response_append_authority(r, rr);
            jdns_rr_delete(rr);
      }
      for(n = 0; n < packet->additionalRecords->count; ++n)
      {
            jdns_packet_resource_t *res = (jdns_packet_resource_t *)packet->additionalRecords->item[n];
            jdns_rr_t *rr;
            if((res->qclass & classmask) != 0x0001)
                  continue;
            rr = jdns_rr_from_resource(res, packet);
            if(!rr)
                  continue;
            jdns_response_append_additional(r, rr);
            jdns_rr_delete(rr);
      }
      return r;
}

// size must be 1 to 16
static void _print_hexdump_line(jdns_session_t *s, const unsigned char *buf, int size)
{
      char line[67]; // 3 * 16 + 2 + 16 + zero byte
      int n;

      memset(line, ' ', 66);
      line[66] = 0;
      if(size > 16)
            size = 16;
      for(n = 0; n < size; ++n)
      {
            unsigned char c = buf[n];
            _hex_byte(c, ((unsigned char *)line) + n * 3);
            line[n * 3 + 2] = ' ';
            if(c >= 0x20 && c < 0x7f)
                  line[50 + n] = c;
            else
                  line[50 + n] = '.';
      }
      _debug_line(s, "  %s", line);
}

static void _print_hexdump(jdns_session_t *s, const unsigned char *buf, int size)
{
      int n;
      int lines;
      int at, len;

      lines = size / 16;
      if(size % 16 != 0)
            ++lines;
      for(n = 0; n < lines; ++n)
      {
            at = n * 16;
            if(at + 16 <= size)
                  len = 16;
            else
                  len = size - at;
            _print_hexdump_line(s, buf + at, len);
      }
}

static void _print_packet_resources(jdns_session_t *s, const jdns_list_t *reslist)
{
      int n;
      for(n = 0; n < reslist->count; ++n)
      {
            jdns_packet_resource_t *r;
            jdns_string_t *str;
            r = (jdns_packet_resource_t *)reslist->item[n];
            str = _make_printable_str(r->qname);
            _debug_line(s, "    %04x/%04x [%s] ttl=%ld size=%d", r->qclass, r->qtype, str->data, r->ttl, r->rdlength);
            jdns_string_delete(str);
      }
}

static void _print_packet(jdns_session_t *s, const jdns_packet_t *packet)
{
      int n;
      _debug_line(s, "Packet:");
      _debug_line(s, "  id:   %d", packet->id);
      _debug_line(s, "  opts: qr:%d, opcode:%d, aa:%d, tc:%d, rd:%d, ra:%d, z:%d, rcode:%d",
            packet->opts.qr, packet->opts.opcode, packet->opts.aa, packet->opts.tc,
            packet->opts.rd, packet->opts.ra, packet->opts.z, packet->opts.rcode);
      _debug_line(s, "  qdcount=%d, ancount=%d, nscount=%d, arcount=%d",
            packet->qdcount, packet->ancount, packet->nscount, packet->arcount);
      if(packet->questions->count > 0)
      {
            _debug_line(s, "  questions: (class/type name)");
            for(n = 0; n < packet->questions->count; ++n)
            {
                  jdns_packet_question_t *q;
                  jdns_string_t *str;
                  q = (jdns_packet_question_t *)packet->questions->item[n];
                  str = _make_printable_str(q->qname);
                  _debug_line(s, "    %04x/%04x [%s]", q->qclass, q->qtype, str->data);
                  jdns_string_delete(str);
            }
      }
      if(packet->answerRecords->count > 0)
      {
            _debug_line(s, "  answerRecords: (class/type owner ttl size)");
            _print_packet_resources(s, packet->answerRecords);
      }
      if(packet->authorityRecords->count > 0)
      {
            _debug_line(s, "  authorityRecords: (class/type owner ttl size)");
            _print_packet_resources(s, packet->authorityRecords);
      }
      if(packet->additionalRecords->count > 0)
      {
            _debug_line(s, "  additionalRecords: (class/type owner ttl size)");
            _print_packet_resources(s, packet->additionalRecords);
      }
}

static void _print_rr(jdns_session_t *s, const jdns_rr_t *rr, const unsigned char *owner)
{
      int n;
      jdns_string_t *ownerstr;

      ownerstr = jdns_string_new();

      // not the expected owner?
      if(!owner || !jdns_domain_cmp(owner, rr->owner))
      {
            unsigned char *buf;
            jdns_string_t *str = _make_printable_cstr((const char *)rr->owner);
            buf = (unsigned char *)malloc(str->size + 3); // " [%s]"
            buf[0] = ' ';
            buf[1] = '[';
            memcpy(buf + 2, str->data, str->size);
            buf[str->size + 2] = ']';
            jdns_string_set(ownerstr, buf, str->size + 3);
            jdns_string_delete(str);
            free(buf);
      }
      else
            jdns_string_set_cstr(ownerstr, "");

      switch(rr->type)
      {
            case JDNS_RTYPE_A:
            {
                  _debug_line(s, "    A: [%s] (ttl=%d)%s", rr->data.address->c_str, rr->ttl, ownerstr->data);
                  break;
            }
            case JDNS_RTYPE_AAAA:
            {
                  _debug_line(s, "    AAAA: [%s] (ttl=%d)%s", rr->data.address->c_str, rr->ttl, ownerstr->data);
                  break;
            }
            case JDNS_RTYPE_MX:
            {
                  jdns_string_t *str = _make_printable_cstr((const char *)rr->data.server->name);
                  _debug_line(s, "    MX: [%s] priority=%d (ttl=%d)%s", str->data, rr->data.server->priority, rr->ttl, ownerstr->data);
                  jdns_string_delete(str);
                  break;
            }
            case JDNS_RTYPE_SRV:
            {
                  jdns_string_t *str = _make_printable_cstr((const char *)rr->data.server->name);
                  _debug_line(s, "    SRV: [%s] port=%d priority=%d weight=%d (ttl=%d)%s", str->data, rr->data.server->port, rr->data.server->priority, rr->data.server->weight, rr->ttl, ownerstr->data);
                  jdns_string_delete(str);
                  break;
            }
            case JDNS_RTYPE_CNAME:
            {
                  jdns_string_t *str = _make_printable_cstr((const char *)rr->data.name);
                  _debug_line(s, "    CNAME: [%s] (ttl=%d)%s", str->data, rr->ttl, ownerstr->data);
                  jdns_string_delete(str);
                  break;
            }
            case JDNS_RTYPE_PTR:
            {
                  jdns_string_t *str = _make_printable_cstr((const char *)rr->data.name);
                  _debug_line(s, "    PTR: [%s] (ttl=%d)%s", str->data, rr->ttl, ownerstr->data);
                  jdns_string_delete(str);
                  break;
            }
            case JDNS_RTYPE_TXT:
            {
                  _debug_line(s, "    TXT: count=%d (ttl=%d)%s", rr->data.texts->count, rr->ttl, ownerstr->data);
                  for(n = 0; n < rr->data.texts->count; ++n)
                  {
                        jdns_string_t *str, *pstr;
                        str = rr->data.texts->item[n];
                        pstr = _make_printable_str(str);
                        _debug_line(s, "      len=%d [%s]", str->size, pstr->data);
                        jdns_string_delete(pstr);
                  }
                  break;
            }
            case JDNS_RTYPE_HINFO:
            {
                  jdns_string_t *cpu, *os;
                  cpu = _make_printable_str(rr->data.hinfo.cpu);
                  os = _make_printable_str(rr->data.hinfo.os);
                  _debug_line(s, "    HINFO: [%s] [%s] (ttl=%d)%s", cpu->data, os->data, rr->ttl, ownerstr->data);
                  jdns_string_delete(cpu);
                  jdns_string_delete(os);
                  break;
            }
            case JDNS_RTYPE_NS:
            {
                  jdns_string_t *str = _make_printable_cstr((const char *)rr->data.name);
                  _debug_line(s, "    NS: [%s] (ttl=%d)%s", str->data, rr->ttl, ownerstr->data);
                  jdns_string_delete(str);
                  break;
            }
            default:
            {
                  _debug_line(s, "    Unknown (%d): %d bytes (ttl=%d)%s", rr->type, rr->rdlength, rr->ttl, ownerstr->data);
                  break;
            }
      }
      jdns_string_delete(ownerstr);
}

static void _print_records(jdns_session_t *s, const jdns_response_t *r, const unsigned char *owner)
{
      int n;
      _debug_line(s, "Records:");
      _debug_line(s, "  Answer Records: %d", r->answerCount);
      for(n = 0; n < r->answerCount; ++n)
            _print_rr(s, r->answerRecords[n], owner);
      _debug_line(s, "  Authority Records: %d", r->authorityCount);
      for(n = 0; n < r->authorityCount; ++n)
            _print_rr(s, r->authorityRecords[n], owner);
      _debug_line(s, "  Additional Records: %d", r->additionalCount);
      for(n = 0; n < r->additionalCount; ++n)
            _print_rr(s, r->additionalRecords[n], owner);
}

static int _min(int a, int b)
{
      return (a < b) ? a : b;
}

//----------------------------------------------------------------------------
// jdns_event
//----------------------------------------------------------------------------
jdns_event_t *jdns_event_new()
{
      jdns_event_t *e = alloc_type(jdns_event_t);
      e->response = 0;
      return e;
}

void jdns_event_delete(jdns_event_t *e)
{
      if(!e)
            return;
      jdns_response_delete(e->response);
      jdns_free(e);
}

//----------------------------------------------------------------------------
// jdns - internal types
//----------------------------------------------------------------------------
00533 typedef struct list_item
{
      void (*dtor)(void *);
} list_item_t;

00538 typedef struct list
{
      int count;
      list_item_t **item;
} list_t;

static list_t *list_new()
{
      list_t *l = alloc_type(list_t);
      l->count = 0;
      l->item = 0;
      return l;
}

static void list_delete(list_t *l)
{
      int n;
      if(!l)
            return;
      for(n = 0; n < l->count; ++n)
            l->item[n]->dtor(l->item[n]);
      if(l->item)
            free(l->item);
      jdns_free(l);
}

static void list_insert(list_t *l, void *item, int pos)
{
      list_item_t *i = (list_item_t *)item;
      if(!l->item)
            l->item = (list_item_t **)malloc(sizeof(list_item_t *));
      else
            l->item = (list_item_t **)realloc(l->item, sizeof(list_item_t *) * (l->count + 1));
      if(pos != -1)
            memmove(l->item + pos + 1, l->item + pos, (l->count - pos) * sizeof(list_item_t *));
      else
            pos = l->count;
      l->item[pos] = i;
      ++l->count;
}

static void list_remove(list_t *l, void *item)
{
      int n;
      list_item_t *i = (list_item_t *)item;
      int pos = -1;
      for(n = 0; n < l->count; ++n)
      {
            if(l->item[n] == i)
            {
                  pos = n;
                  break;
            }
      }
      if(pos == -1)
            return;

      i->dtor(i);
      if(l->count > 1)
      {
            memmove(l->item + pos, l->item + pos + 1, (l->count - pos - 1) * sizeof(list_item_t *));
            --l->count;
      }
      else
      {
            free(l->item);
            l->item = 0;
            l->count = 0;
      }
}

00609 typedef struct name_server
{
      void (*dtor)(struct name_server *);
      int id;
      jdns_address_t *address;
      int port;
} name_server_t;

static void name_server_delete(name_server_t *ns);

static name_server_t *name_server_new()
{
      name_server_t *ns = alloc_type(name_server_t);
      ns->dtor = name_server_delete;
      ns->address = 0;
      return ns;
}

void name_server_delete(name_server_t *ns)
{
      if(!ns)
            return;
      jdns_address_delete(ns->address);
      jdns_free(ns);
}

int _intarray_indexOf(int *array, int count, int val)
{
      int n;
      for(n = 0; n < count; ++n)
      {
            if(array[n] == val)
                  return n;
      }
      return -1;
}

int _intarray_add(int **array, int *count, int val)
{
      int *p;
      if(!*array)
            p = (int *)malloc(sizeof(int));
      else
            p = (int *)realloc(*array, sizeof(int) * (*count + 1));
      if(!p)
            return 0;
      *array = p;
      (*array)[*count] = val;
      ++(*count);
      return 1;
}

void _intarray_remove(int **array, int *count, int pos)
{
      int *p;
      if(*count > 1)
      {
            memmove(*array + pos, *array + pos + 1, (*count - pos - 1) * sizeof(int));
            --(*count);
            p = (int *)realloc(*array, sizeof(int) * (*count));
            if(p)
                  *array = p;
      }
      else
      {
            free(*array);
            *array = 0;
            *count = 0;
      }
}

00680 typedef struct query
{
      void (*dtor)(struct query *);

      int id;

      // user request ids
      int req_ids_count;
      int *req_ids;

      // packet id
      int dns_id;

      // what we are looking up
      unsigned char *qname;
      int qtype;

      // how many transmission attempts we have done.  note this
      //  is not actually how many packets have been sent, since
      //  it is possible for the first transmission to send many
      //  at once.  this variable lets us decide when to give up.
      //  (idea taken from qdns).
      // set to -1 to deactivate (stop sending packets)
      int step;

      // which nameservers we've tried (stored as a list of ids)
      int servers_tried_count;
      int *servers_tried;

      // which servers we shouldn't try again
      int servers_failed_count;
      int *servers_failed;

      // flag to indicate whether or not we've tried all available
      //  nameservers already.  this means that all future
      //  transmissions are likely repeats, and should be slowed
      //  down.
      int retrying;

      // flag to indicate if we've received nxdomain as an error so far
      int nxdomain;

      // holds a timeout for the next step (time_start == -1 means no timer)
      int time_start;
      int time_next;

      // whether or not to look in the cache for this query
      int trycache;

      // cname subquerying.  only cname_parent or cname_child may be set,
      //  never both.
      int cname_chain_count;
      struct query *cname_parent;
      struct query *cname_child;

      // accumulates known multicast records to prevent duplicates
      jdns_response_t *mul_known;
} query_t;

void query_delete(query_t *q);

query_t *query_new()
{
      query_t *q = alloc_type(query_t);
      q->dtor = query_delete;
      q->req_ids_count = 0;
      q->req_ids = 0;
      q->qname = 0;
      q->servers_tried_count = 0;
      q->servers_tried = 0;
      q->servers_failed_count = 0;
      q->servers_failed = 0;
      q->nxdomain = 0;
      q->cname_chain_count = 0;
      q->cname_parent = 0;
      q->cname_child = 0;
      q->mul_known = 0;
      return q;
}

void query_delete(query_t *q)
{
      if(!q)
            return;
      if(q->req_ids)
            free(q->req_ids);
      if(q->qname)
            free(q->qname);
      if(q->servers_tried)
            free(q->servers_tried);
      if(q->servers_failed)
            free(q->servers_failed);
      jdns_response_delete(q->mul_known);
      jdns_free(q);
}

int query_have_req_id(const query_t *q, int req_id)
{
      if(_intarray_indexOf(q->req_ids, q->req_ids_count, req_id) != -1)
            return 1;
      return 0;
}

void query_add_req_id(query_t *q, int req_id)
{
      _intarray_add(&q->req_ids, &q->req_ids_count, req_id);
}

void query_remove_req_id(query_t *q, int req_id)
{
      int pos;

      pos = _intarray_indexOf(q->req_ids, q->req_ids_count, req_id);
      if(pos != -1)
            _intarray_remove(&q->req_ids, &q->req_ids_count, pos);
}

int query_server_tried(const query_t *q, int ns_id)
{
      if(_intarray_indexOf(q->servers_tried, q->servers_tried_count, ns_id) != -1)
            return 1;
      return 0;
}

void query_add_server_tried(query_t *q, int ns_id)
{
      _intarray_add(&q->servers_tried, &q->servers_tried_count, ns_id);
}

int query_server_failed(const query_t *q, int ns_id);

void query_clear_servers_tried(query_t *q)
{
      int n;

      // all failed servers must continue to be considered tried servers, so
      //   only clear tried servers that haven't failed
      for(n = 0; n < q->servers_tried_count; ++n)
      {
            if(!query_server_failed(q, q->servers_tried[n]))
            {
                  _intarray_remove(&q->servers_tried, &q->servers_tried_count, n);
                  --n; // adjust position
            }
      }
}

int query_server_failed(const query_t *q, int ns_id)
{
      if(_intarray_indexOf(q->servers_failed, q->servers_failed_count, ns_id) != -1)
            return 1;
      return 0;
}

void query_add_server_failed(query_t *q, int ns_id)
{
      _intarray_add(&q->servers_failed, &q->servers_failed_count, ns_id);
}

void query_name_server_gone(query_t *q, int ns_id)
{
      int pos;

      pos = _intarray_indexOf(q->servers_tried, q->servers_tried_count, ns_id);
      if(pos != -1)
            _intarray_remove(&q->servers_tried, &q->servers_tried_count, pos);

      pos = _intarray_indexOf(q->servers_failed, q->servers_failed_count, ns_id);
      if(pos != -1)
            _intarray_remove(&q->servers_failed, &q->servers_failed_count, pos);
}

00852 typedef struct datagram
{
      void (*dtor)(struct datagram *);
      int handle;
      jdns_address_t *dest_address;
      int dest_port;
      unsigned char *data;
      int size;

      // query association
      query_t *query;
      int query_send_type; // 0 == normal, 1 == first step send-all

      // name server association
      int ns_id;
} datagram_t;

void datagram_delete(datagram_t *a);

datagram_t *datagram_new()
{
      datagram_t *a = alloc_type(datagram_t);
      a->dtor = datagram_delete;
      a->dest_address = 0;
      a->data = 0;
      a->size = 0;
      a->query = 0;
      return a;
}

void datagram_delete(datagram_t *a)
{
      if(!a)
            return;
      jdns_address_delete(a->dest_address);
      if(a->data)
            free(a->data);
      jdns_free(a);
}

00892 typedef struct cache_item
{
      void (*dtor)(struct cache_item *);
      unsigned char *qname;
      int qtype;
      int time_start;
      int ttl;
      jdns_rr_t *record; // if zero, nxdomain is assumed
} cache_item_t;

void cache_item_delete(cache_item_t *e);

cache_item_t *cache_item_new()
{
      cache_item_t *a = alloc_type(cache_item_t);
      a->dtor = cache_item_delete;
      a->qname = 0;
      a->record = 0;
      return a;
}

void cache_item_delete(cache_item_t *a)
{
      if(!a)
            return;
      if(a->qname)
            free(a->qname);
      jdns_rr_delete(a->record);
      jdns_free(a);
}

00923 typedef struct event
{
      void (*dtor)(struct event *);
      jdns_event_t *event;
} event_t;

void event_delete(event_t *e);

event_t *event_new()
{
      event_t *e = alloc_type(event_t);
      e->dtor = event_delete;
      e->event = 0;
      return e;
}

void event_delete(event_t *e)
{
      if(!e)
            return;
      jdns_event_delete(e->event);
      jdns_free(e);
}

00947 typedef struct published_item
{
      void (*dtor)(struct published_item *);
      int id;
      int mode;
      unsigned char *qname;
      int qtype;
      mdnsdr rec;
      jdns_rr_t *rr;
} published_item_t;

void published_item_delete(published_item_t *a);

published_item_t *published_item_new()
{
      published_item_t *a = alloc_type(published_item_t);
      a->dtor = published_item_delete;
      a->qname = 0;
      a->rec = 0;
      a->rr = 0;
      return a;
}

void published_item_delete(published_item_t *a)
{
      if(!a)
            return;
      if(a->qname)
            free(a->qname);
      jdns_rr_delete(a->rr);
      jdns_free(a);
}

//----------------------------------------------------------------------------
// jdns
//----------------------------------------------------------------------------
00983 struct jdns_session
{
      jdns_callbacks_t cb;
      int mode;
      int shutdown;
      int next_qid;
      int next_req_id;
      int last_time;
      int next_timer;
      int next_name_server_id;
      int handle;
      int handle_readable, handle_writable;
      int port;
      list_t *name_servers;
      list_t *queries;
      list_t *outgoing;
      list_t *events;
      list_t *cache;

      // for blocking req_ids from reuse until user explicitly releases
      int do_hold_req_ids;
      int held_req_ids_count;
      int *held_req_ids;

      // mdns
      mdnsd mdns;
      list_t *published;
      jdns_address_t *maddr;
};

jdns_session_t *jdns_session_new(jdns_callbacks_t *callbacks)
{
      jdns_session_t *s = alloc_type(jdns_session_t);
      memcpy(&s->cb, callbacks, sizeof(jdns_callbacks_t));
      s->shutdown = 0;
      s->next_qid = 0;
      s->next_req_id = 1;
      s->last_time = 0;
      s->next_timer = 0;
      s->next_name_server_id = 0;
      s->handle = 0;
      s->handle_readable = 0;
      s->handle_writable = 1;
      s->port = 0;
      s->name_servers = list_new();
      s->queries = list_new();
      s->outgoing = list_new();
      s->events = list_new();
      s->cache = list_new();

      s->do_hold_req_ids = 0;
      s->held_req_ids_count = 0;
      s->held_req_ids = 0;

      s->mdns = 0;
      s->published = list_new();
      s->maddr = 0;

      return s;
}

void jdns_session_delete(jdns_session_t *s)
{
      if(!s)
            return;
      if(s->handle)
            s->cb.udp_unbind(s, s->cb.app, s->handle);
      list_delete(s->name_servers);
      list_delete(s->queries);
      list_delete(s->outgoing);
      list_delete(s->events);
      list_delete(s->cache);

      if(s->held_req_ids)
            free(s->held_req_ids);

      if(s->mdns)
            mdnsd_free(s->mdns);

      list_delete(s->published);
      jdns_address_delete(s->maddr);

      free(s);
}

// declare some internal functions
static int _callback_time_now(mdnsd d, void *arg);
static int _callback_rand_int(mdnsd d, void *arg);

static void _append_event(jdns_session_t *s, jdns_event_t *event);
static void _append_event_and_hold_id(jdns_session_t *s, jdns_event_t *event);
static void _remove_name_server_datagrams(jdns_session_t *s, int ns_id);
static void _remove_query_datagrams(jdns_session_t *s, const query_t *q);

static int _unicast_query(jdns_session_t *s, const unsigned char *name, int qtype);
static void _unicast_cancel(jdns_session_t *s, query_t *q);
static int _multicast_query(jdns_session_t *s, const unsigned char *name, int qtype);
static void _multicast_cancel(jdns_session_t *s, int req_id);
static int _multicast_publish(jdns_session_t *s, int mode, const jdns_rr_t *rr);
static void _multicast_update_publish(jdns_session_t *s, int id, const jdns_rr_t *rr);
static void _multicast_cancel_publish(jdns_session_t *s, int id);
static void _multicast_flush(jdns_session_t *s);

static int jdns_step_unicast(jdns_session_t *s, int now);
static int jdns_step_multicast(jdns_session_t *s, int now);

static void _hold_req_id(jdns_session_t *s, int req_id)
{
      int pos;

      // make sure we don't hold an id twice
      pos = _intarray_indexOf(s->held_req_ids, s->held_req_ids_count, req_id);
      if(pos != -1)
            return;

      _intarray_add(&s->held_req_ids, &s->held_req_ids_count, req_id);
}

static void _unhold_req_id(jdns_session_t *s, int req_id)
{
      int pos;

      pos = _intarray_indexOf(s->held_req_ids, s->held_req_ids_count, req_id);
      if(pos != -1)
            _intarray_remove(&s->held_req_ids, &s->held_req_ids_count, pos);
}

static void _set_hold_ids_enabled(jdns_session_t *s, int enabled)
{
      if(enabled && !s->do_hold_req_ids)
      {
            s->do_hold_req_ids = 1;
      }
      else if(!enabled && s->do_hold_req_ids)
      {
            s->do_hold_req_ids = 0;

            if(s->held_req_ids)
                  free(s->held_req_ids);
            s->held_req_ids = 0;
            s->held_req_ids_count = 0;
      }
}

static int _int_wrap(int *src, int start)
{
      int x;
      x = (*src)++;
      if(*src < start)
            *src = start;
      return x;
}

// starts at 0
static int get_next_qid(jdns_session_t *s)
{
      int n, id;
      id = -1;
      while(id == -1)
      {
            id = _int_wrap(&s->next_qid, 0);
            for(n = 0; n < s->queries->count; ++n)
            {
                  if(((query_t *)s->queries->item[n])->id == id)
                  {
                        id = -1;
                        break;
                  }
            }
      }
      return id;
}

// starts at 1
static int get_next_req_id(jdns_session_t *s)
{
      int n, k, id;
      id = -1;
      while(id == -1)
      {
            id = _int_wrap(&s->next_req_id, 1);

            // no query using this?
            for(n = 0; n < s->queries->count; ++n)
            {
                  query_t *q = (query_t *)s->queries->item[n];
                  for(k = 0; k < q->req_ids_count; ++k)
                  {
                        if(q->req_ids[k] == id)
                        {
                              id = -1;
                              break;
                        }
                  }
                  if(id == -1)
                        break;
            }

            // no publish using this?
            for(n = 0; n < s->published->count; ++n)
            {
                  if(((published_item_t *)s->published->item[n])->id == id)
                  {
                        id = -1;
                        break;
                  }
            }

            // successful unicast queries or any kind of error result in
            //   events for actions that are no longer active.  we need
            //   to make sure ids for these actions are not reassigned
            //   until the user explicitly releases them
            for(n = 0; n < s->held_req_ids_count; ++n)
            {
                  if(s->held_req_ids[n] == id)
                  {
                        id = -1;
                        break;
                  }
            }
      }
      return id;
}

// random number fitting in 16 bits
static int get_next_dns_id(jdns_session_t *s)
{
      int n, id, active_ids;
      active_ids = 0;
      id = -1;
      while(id == -1)
      {
            // use random number for dns id
            id = s->cb.rand_int(s, s->cb.app) & 0xffff;

            for(n = 0; n < s->queries->count; ++n)
            {
                  query_t *q = (query_t *)s->queries->item[n];
                  if(q->dns_id != -1)
                  {
                        ++active_ids;
                        if(active_ids >= JDNS_QUERY_MAX)
                              return -1;

                        if(q->dns_id == id)
                        {
                              id = -1;
                              break;
                        }
                  }
            }
      }
      return id;
}

// starts at 0
static int get_next_name_server_id(jdns_session_t *s)
{
      int n, id;
      id = -1;
      while(id == -1)
      {
            id = _int_wrap(&s->next_name_server_id, 0);
            for(n = 0; n < s->name_servers->count; ++n)
            {
                  if(((name_server_t *)s->name_servers->item[n])->id == id)
                  {
                        id = -1;
                        break;
                  }
            }
      }
      return id;
}

int jdns_init_unicast(jdns_session_t *s, const jdns_address_t *addr, int port)
{
      int ret;
      s->mode = 0;
      ret = s->cb.udp_bind(s, s->cb.app, addr, port, 0);
      if(ret <= 0)
            return 0;
      s->handle = ret;
      s->port = port;
      return 1;
}

int jdns_init_multicast(jdns_session_t *s, const jdns_address_t *addr, int port, const jdns_address_t *maddr)
{
      int ret;
      s->mode = 1;
      ret = s->cb.udp_bind(s, s->cb.app, addr, port, maddr);
      if(ret <= 0)
            return 0;
      s->handle = ret;
      s->port = port;
      s->maddr = jdns_address_copy(maddr);

      // class 1.  note: frame size is ignored by the jdns version of mdnsd
      s->mdns = mdnsd_new(0x0001, 1000, s->port, _callback_time_now, _callback_rand_int, s);
      return 1;
}

void jdns_shutdown(jdns_session_t *s)
{
      if(s->shutdown == 0)
            s->shutdown = 1; // request shutdown
}

void jdns_set_nameservers(jdns_session_t *s, const jdns_nameserverlist_t *nslist)
{
      int n, k;

      // removed?
      for(k = 0; k < s->name_servers->count; ++k)
      {
            name_server_t *ns = (name_server_t *)(s->name_servers->item[k]);
            int found = 0;
            for(n = 0; n < nslist->count; ++n)
            {
                  jdns_nameserver_t *i = (jdns_nameserver_t *)nslist->item[n];
                  if(jdns_address_cmp(ns->address, i->address) && ns->port == i->port)
                  {
                        found = 1;
                        break;
                  }
            }
            if(!found)
            {
                  int i;
                  int ns_id;

                  // remove any pending packets to this nameserver
                  _remove_name_server_datagrams(s, ns->id);

                  _debug_line(s, "ns [%s:%d] (id=%d) removed", ns->address->c_str, ns->port, ns->id);
                  ns_id = ns->id;
                  list_remove(s->name_servers, ns);
                  --k; // adjust position
                  for(i = 0; i < s->queries->count; ++i)
                        query_name_server_gone((query_t *)s->queries->item[i], ns_id);
            }
      }

      // added?
      for(n = 0; n < nslist->count; ++n)
      {
            name_server_t *ns;
            jdns_nameserver_t *i;
            int found;

            i = (jdns_nameserver_t *)nslist->item[n];
            found = 0;
            for(k = 0; k < s->name_servers->count; ++k)
            {
                  ns = (name_server_t *)(s->name_servers->item[k]);
                  if(jdns_address_cmp(ns->address, i->address) && ns->port == i->port)
                  {
                        found = 1;
                        break;
                  }
            }
            if(found)
            {
                  _debug_line(s, "ns [%s:%d] (id=%d) still present", ns->address->c_str, ns->port, ns->id);
            }
            else
            {
                  ns = name_server_new();
                  ns->id = get_next_name_server_id(s);
                  ns->address = jdns_address_copy(i->address);
                  ns->port = i->port;
                  list_insert(s->name_servers, ns, -1);
                  _debug_line(s, "ns [%s:%d] (id=%d) added", ns->address->c_str, ns->port, ns->id);
            }
      }

      // no nameservers?
      if(nslist->count == 0)
      {
            _debug_line(s, "nameserver count is zero, invalidating any queries");

            // invalidate all of the queries!
            for(n = 0; n < s->queries->count; ++n)
            {
                  query_t *q = (query_t *)s->queries->item[n];

                  // report event to any requests listening
                  for(k = 0; k < q->req_ids_count; ++k)
                  {
                        jdns_event_t *event = jdns_event_new();
                        event->type = JDNS_EVENT_RESPONSE;
                        event->id = q->req_ids[k];
                        event->status = JDNS_STATUS_TIMEOUT;
                        _append_event_and_hold_id(s, event);
                  }

                  // this line is probably redundant, but just for
                  //  consistency we'll do it...
                  _remove_query_datagrams(s, q);

                  list_remove(s->queries, q);
                  --n; // adjust position
            }
      }
}

void jdns_probe(jdns_session_t *s)
{
      if(s->mode != 1)
            return;

      _multicast_flush(s);
}

int jdns_query(jdns_session_t *s, const unsigned char *name, int rtype)
{
      if(s->mode == 0)
            return _unicast_query(s, name, rtype);
      else
            return _multicast_query(s, name, rtype);
}

static void _remove_events(jdns_session_t *s, int event_type, int id)
{
      int n;
      for(n = 0; n < s->events->count; ++n)
      {
            event_t *e = (event_t *)s->events->item[n];
            if(e->event->type == event_type && e->event->id == id)
            {
                  list_remove(s->events, e);
                  --n; // adjust position
            }
      }
}

void jdns_cancel_query(jdns_session_t *s, int id)
{
      int n;

      _unhold_req_id(s, id);

      // remove any events associated with the query.  this avoids any
      //   possibility that stale events from one query are mistaken to be
      //   events resulting from a later query that happened to reuse the
      //   id.  it also means we don't deliver events for cancelled queries,
      //   which can simplify application logic.
      _remove_events(s, JDNS_EVENT_RESPONSE, id);

      // multicast
      if(s->mode == 1)
      {
            _multicast_cancel(s, id);
            return;
      }

      // unicast
      for(n = 0; n < s->queries->count; ++n)
      {
            query_t *q = (query_t *)s->queries->item[n];
            if(query_have_req_id(q, id))
            {
                  query_remove_req_id(q, id);

                  // note: calling _unicast_cancel might remove an item
                  //  from s->queries, thereby screwing up our iterator
                  //  position, but that's ok because we just break
                  //  anyway.

                  // if no one else is depending on this request, then take action
                  if(q->req_ids_count == 0 && !q->cname_parent)
                  {
                        // remove a possible cname child
                        if(q->cname_child && q->cname_child->req_ids_count == 0)
                        {
                              q->cname_child->cname_parent = 0;
                              _unicast_cancel(s, q->cname_child);
                              q->cname_child = 0;
                        }

                        _unicast_cancel(s, q);
                  }
                  break;
            }
      }
}

int jdns_publish(jdns_session_t *s, int mode, const jdns_rr_t *rr)
{
      return _multicast_publish(s, mode, rr);
}

void jdns_update_publish(jdns_session_t *s, int id, const jdns_rr_t *rr)
{
      _multicast_update_publish(s, id, rr);
}

void jdns_cancel_publish(jdns_session_t *s, int id)
{
      _unhold_req_id(s, id);

      _remove_events(s, JDNS_EVENT_PUBLISH, id);

      _multicast_cancel_publish(s, id);
}

int jdns_step(jdns_session_t *s)
{
      int now, passed;
      int ret;

      // session is shut down
      if(s->shutdown == 2)
            return 0;

      now = s->cb.time_now(s, s->cb.app);
      passed = now - s->last_time;

      _debug_line(s, "passed: %d", passed);

      if(s->mode == 0)
            ret = jdns_step_unicast(s, now);
      else
            ret = jdns_step_multicast(s, now);

      s->last_time = now;
      return ret;
}

int jdns_next_timer(jdns_session_t *s)
{
      return s->next_timer;
}

void jdns_set_handle_readable(jdns_session_t *s, int handle)
{
      (void)handle;
      s->handle_readable = 1;
}

void jdns_set_handle_writable(jdns_session_t *s, int handle)
{
      (void)handle;
      s->handle_writable = 1;
}

jdns_event_t *jdns_next_event(jdns_session_t *s)
{
      jdns_event_t *event = 0;
      if(s->events->count > 0)
      {
            event_t *e = (event_t *)s->events->item[0];
            event = e->event;
            e->event = 0;
            list_remove(s->events, e);
      }
      return event;
}

void jdns_set_hold_ids_enabled(jdns_session_t *s, int enabled)
{
      _set_hold_ids_enabled(s, enabled);
}

//----------------------------------------------------------------------------
// jdns - internal functions
//----------------------------------------------------------------------------

// we don't have vsnprintf on windows, so don't pass anything enormous to
//   this function.  the plan is that no line should exceed 1000 bytes,
//   although _print_rr() might get close.  a 2048 byte buffer should be
//   plenty then.
void _debug_line(jdns_session_t *s, const char *format, ...)
{
      char *buf = (char *)malloc(2048);
      va_list ap;
      va_start(ap, format);
      jdns_vsprintf_s(buf, 2048, format, ap);
      va_end(ap);
      s->cb.debug_line(s, s->cb.app, buf);
      free(buf);
}

int _callback_time_now(mdnsd d, void *arg)
{
      jdns_session_t *s = (jdns_session_t *)arg;
      (void)d;
      // offset the time, mdnsd doesn't like starting at 0
      return s->cb.time_now(s, s->cb.app) + 120 * 1000;
}

int _callback_rand_int(mdnsd d, void *arg)
{
      jdns_session_t *s = (jdns_session_t *)arg;
      (void)d;
      return s->cb.rand_int(s, s->cb.app);
}

void _append_event(jdns_session_t *s, jdns_event_t *event)
{
      event_t *e = event_new();
      e->event = event;
      list_insert(s->events, e, -1);
}

void _append_event_and_hold_id(jdns_session_t *s, jdns_event_t *event)
{
      if(s->do_hold_req_ids)
            _hold_req_id(s, event->id);
      _append_event(s, event);
}

void _remove_name_server_datagrams(jdns_session_t *s, int ns_id)
{
      int n;
      for(n = 0; n < s->outgoing->count; ++n)
      {
            datagram_t *a = (datagram_t *)s->outgoing->item[n];
            if(a->ns_id == ns_id)
            {
                  list_remove(s->outgoing, a);
                  --n; // adjust position
            }
      }
}

void _remove_query_datagrams(jdns_session_t *s, const query_t *q)
{
      int n;
      for(n = 0; n < s->outgoing->count; ++n)
      {
            datagram_t *a = (datagram_t *)s->outgoing->item[n];
            if(a->query == q)
            {
                  list_remove(s->outgoing, a);
                  --n; // adjust position
            }
      }
}

void _process_message(jdns_session_t *s, jdns_packet_t *p, int now, query_t *q, name_server_t *ns);

// return 1 if 'q' should be deleted, 0 if not
int _process_response(jdns_session_t *s, jdns_response_t *r, int nxdomain, int now, query_t *q);

jdns_response_t *_cache_get_response(jdns_session_t *s, const unsigned char *qname, int qtype, int *_lowest_timeleft)
{
      int n;
      int lowest_timeleft = -1;
      int now = s->cb.time_now(s, s->cb.app);
      jdns_response_t *r = 0;
      for(n = 0; n < s->cache->count; ++n)
      {
            cache_item_t *i = (cache_item_t *)s->cache->item[n];
            if(jdns_domain_cmp(i->qname, qname) && i->qtype == qtype)
            {
                  int passed, timeleft;

                  if(!r)
                        r = jdns_response_new();

                  if(i->record)
                        jdns_response_append_answer(r, i->record);

                  passed = now - i->time_start;
                  timeleft = (i->ttl * 1000) - passed;
                  if(lowest_timeleft == -1 || timeleft < lowest_timeleft)
                        lowest_timeleft = timeleft;
            }
      }
      if(_lowest_timeleft)
            *_lowest_timeleft = lowest_timeleft;
      return r;
}

query_t *_find_first_active_query(jdns_session_t *s, const unsigned char *qname, int qtype)
{
      int n;
      query_t *q;

      for(n = 0; n < s->queries->count; ++n)
      {
            q = (query_t *)s->queries->item[n];
            if(jdns_domain_cmp(q->qname, qname) && q->qtype == qtype && q->step != -1)
                  return q;
      }

      return 0;
}

query_t *_get_query(jdns_session_t *s, const unsigned char *qname, int qtype, int unique)
{
      query_t *q;
      jdns_string_t *str;

      if(!unique)
      {
            q = _find_first_active_query(s, qname, qtype);
            if(q)
            {
                  str = _make_printable_cstr((const char *)q->qname);
                  _debug_line(s, "[%d] reusing query for: [%s] [%s]", q->id, _qtype2str(qtype), str->data);
                  jdns_string_delete(str);
                  return q;
            }
      }

      q = query_new();
      q->id = get_next_qid(s);
      q->qname = _ustrdup(qname);
      q->qtype = qtype;
      q->step = 0;
      q->dns_id = -1;
      q->time_start = 0;
      q->time_next = 0;
      q->trycache = 1;
      q->retrying = 0;
      list_insert(s->queries, q, -1);

      str = _make_printable_cstr((const char *)q->qname);
      _debug_line(s, "[%d] querying: [%s] [%s]", q->id, _qtype2str(qtype), str->data);
      jdns_string_delete(str);
      return q;
}

int _unicast_query(jdns_session_t *s, const unsigned char *name, int qtype)
{
      unsigned char *qname;
      query_t *q;
      int req_id;
      jdns_string_t *str;

      str = _make_printable_cstr((const char *)name);
      _debug_line(s, "query input: [%s]", str->data);
      jdns_string_delete(str);

      qname = _fix_input(name);

      q = _get_query(s, qname, qtype, 0);
      req_id = get_next_req_id(s);
      query_add_req_id(q, req_id);
      free(qname);
      return req_id;
}

void _unicast_cancel(jdns_session_t *s, query_t *q)
{
      // didn't even do a step yet?  just remove it
      if(q->step == 0)
      {
            _remove_query_datagrams(s, q);
            list_remove(s->queries, q);
      }
      // otherwise, just deactivate
      else
      {
            // deactivate and remain in the background for
            //  1 minute.  this will allow us to cache a
            //  reply, even if the user is not currently
            //  interested.
            q->step = -1;
            q->time_start = s->cb.time_now(s, s->cb.app);
            q->time_next = 60000;
      }
}

void _queue_packet(jdns_session_t *s, query_t *q, const name_server_t *ns, int recurse, int query_send_type)
{
      jdns_packet_t *packet;
      datagram_t *a;

      packet = jdns_packet_new();
      packet->id = q->dns_id;
      packet->opts.rd = recurse; // recursion desired
      {
            jdns_packet_question_t *question = jdns_packet_question_new();
            question->qname = jdns_string_new();
            jdns_string_set_cstr(question->qname, (const char *)q->qname);
            question->qtype = q->qtype;
            question->qclass = 0x0001;
            jdns_list_insert(packet->questions, question, -1);
            jdns_packet_question_delete(question);
      }
      if(!jdns_packet_export(packet, JDNS_UDP_UNI_OUT_MAX))
      {
            _debug_line(s, "outgoing packet export error, not sending");
            jdns_packet_delete(packet);
            return;
      }

      a = datagram_new();
      a->handle = s->handle;
      a->dest_address = jdns_address_copy(ns->address);
      a->dest_port = ns->port;
      a->data = jdns_copy_array(packet->raw_data, packet->raw_size);
      a->size = packet->raw_size;
      a->query = q;
      a->query_send_type = query_send_type;
      a->ns_id = ns->id;

      jdns_packet_delete(packet);

      list_insert(s->outgoing, a, -1);
}

// return 1 if packets still need to be written
int _unicast_do_writes(jdns_session_t *s, int now);

// return 1 if packets still need to be read
int _unicast_do_reads(jdns_session_t *s, int now);

int jdns_step_unicast(jdns_session_t *s, int now)
{
      int n;
      int need_read = 0;
      int need_write = 0;
      int smallest_time = -1;
      int flags;

      if(s->shutdown == 1)
      {
            jdns_event_t *event = jdns_event_new();
            event->type = JDNS_EVENT_SHUTDOWN;
            _append_event(s, event);
            s->shutdown = 2;
            return 0;
      }

      // expire cached items
      for(n = 0; n < s->cache->count; ++n)
      {
            cache_item_t *i = (cache_item_t *)s->cache->item[n];
            if(now >= i->time_start + (i->ttl * 1000))
            {
                  jdns_string_t *str = _make_printable_cstr((const char *)i->qname);
                  _debug_line(s, "cache exp [%s]", str->data);
                  jdns_string_delete(str);
                  list_remove(s->cache, i);
                  --n; // adjust position
            }
      }

      need_write = _unicast_do_writes(s, now);
      need_read = _unicast_do_reads(s, now);

      // calculate next timer (based on queries and cache)
      for(n = 0; n < s->queries->count; ++n)
      {
            query_t *q = (query_t *)(s->queries->item[n]);
            if(q->time_start != -1)
            {
                  int qpassed = now - q->time_start;
                  int timeleft = q->time_next - qpassed;
                  if(timeleft < 0)
                        timeleft = 0;

                  if(smallest_time == -1 || timeleft < smallest_time)
                        smallest_time = timeleft;
            }
      }
      for(n = 0; n < s->cache->count; ++n)
      {
            cache_item_t *i = (cache_item_t *)(s->cache->item[n]);
            int passed = now - i->time_start;
            int timeleft = (i->ttl * 1000) - passed;
            if(timeleft < 0)
                  timeleft = 0;

            if(smallest_time == -1 || timeleft < smallest_time)
                  smallest_time = timeleft;
      }

      flags = 0;
      if(smallest_time != -1)
      {
            flags |= JDNS_STEP_TIMER;
            s->next_timer = smallest_time;

            // offset it a little bit, so that the user doesn't call
            //  us too early, resulting in a no-op and another timer
            //  of 1 millisecond.
            s->next_timer += 2;
      }
      if(need_read || need_write)
            flags |= JDNS_STEP_HANDLE;
      return flags;
}

int _unicast_do_writes(jdns_session_t *s, int now)
{
      int need_write = 0;
      int n, k;

      for(n = 0; n < s->queries->count; ++n)
      {
            query_t *q;
            int qpassed, timeleft;
            int giveup;
            name_server_t *ns;
            int already_sending;

            q = (query_t *)s->queries->item[n];

            // nothing to do
            if(q->time_start == -1)
                  continue;

            qpassed = now - q->time_start;
            timeleft = q->time_next - qpassed;
            if(timeleft < 0)
                  timeleft = 0;
            _debug_line(s, "[%d] time_start/next=%d/%d (left=%d)", q->id, q->time_start, q->time_next, timeleft);
            if(timeleft > 0)
                  continue;

            if(q->trycache)
            {
                  // is it cached?
                  int lowest_timeleft;
                  int qtype = q->qtype;
                  jdns_response_t *r;

                  r = _cache_get_response(s, q->qname, qtype, &lowest_timeleft);

                  // not found?  try cname
                  if(!r)
                  {
                        qtype = JDNS_RTYPE_CNAME;
                        r = _cache_get_response(s, q->qname, qtype, &lowest_timeleft);
                  }

                  if(r)
                  {
                        int nxdomain;

                        _debug_line(s, "[%d] using cached answer", q->id);

                        // are any of the records about to expire in 3 minutes?
                        //  assume the client is interested in this record and
                        //  query it again "in the background" (but only
                        //  if we are not already doing so)
                        if(lowest_timeleft < (3 * 60 * 1000) && !_find_first_active_query(s, q->qname, q->qtype))
                        {
                              query_t *new_q;

                              _debug_line(s, "requerying for cached item about to expire");

                              new_q = _get_query(s, q->qname, q->qtype, 1);
                              new_q->retrying = 1; // slow it down
                              new_q->trycache = 0; // don't use the cache for this
                        }

                        nxdomain = r->answerCount == 0 ? 1 : 0;
                        if(_process_response(s, r, nxdomain, -1, q))
                        {
                              _remove_query_datagrams(s, q);
                              list_remove(s->queries, q);
                              --n; // adjust position
                        }

                        jdns_response_delete(r);
                        continue;
                  }
            }

            // inactive
            if(q->step == -1)
            {
                  // time up on an inactive query?  remove it
                  _debug_line(s, "removing inactive query");
                  _remove_query_datagrams(s, q);
                  list_remove(s->queries, q);
                  --n; // adjust position
                  continue;
            }

            giveup = 0;

            // too many tries, give up
            if(q->step == 8)
                  giveup = 1;

            // no nameservers, give up
            //  (this would happen if someone removed all nameservers
            //   during a query)
            if(s->name_servers->count == 0)
                  giveup = 1;

            if(giveup)
            {
                  // report event to any requests listening
                  for(k = 0; k < q->req_ids_count; ++k)
                  {
                        jdns_event_t *event = jdns_event_new();
                        event->type = JDNS_EVENT_RESPONSE;
                        event->id = q->req_ids[k];
                        event->status = JDNS_STATUS_TIMEOUT;
                        _append_event_and_hold_id(s, event);
                  }

                  // report error to parent
                  if(q->cname_parent)
                  {
                        // report event to any requests listening
                        query_t *cq = q->cname_parent;
                        for(k = 0; k < cq->req_ids_count; ++k)
                        {
                              jdns_event_t *event = jdns_event_new();
                              event->type = JDNS_EVENT_RESPONSE;
                              event->id = cq->req_ids[k];
                              event->status = JDNS_STATUS_TIMEOUT;
                              _append_event_and_hold_id(s, event);
                        }
                        list_remove(s->queries, cq);
                  }

                  _remove_query_datagrams(s, q);
                  list_remove(s->queries, q);
                  --n; // adjust position
                  continue;
            }

            // assign a packet id if we don't have one yet
            if(q->dns_id == -1)
            {
                  q->dns_id = get_next_dns_id(s);

                  // couldn't get an id?
                  if(q->dns_id == -1)
                  {
                        _debug_line(s, "unable to reserve packet id");

                        // report event to any requests listening
                        for(k = 0; k < q->req_ids_count; ++k)
                        {
                              jdns_event_t *event = jdns_event_new();
                              event->type = JDNS_EVENT_RESPONSE;
                              event->id = q->req_ids[k];
                              event->status = JDNS_STATUS_ERROR;
                              _append_event_and_hold_id(s, event);
                        }

                        // report error to parent
                        if(q->cname_parent)
                        {
                              // report event to any requests listening
                              query_t *cq = q->cname_parent;
                              for(k = 0; k < cq->req_ids_count; ++k)
                              {
                                    jdns_event_t *event = jdns_event_new();
                                    event->type = JDNS_EVENT_RESPONSE;
                                    event->id = cq->req_ids[k];
                                    event->status = JDNS_STATUS_ERROR;
                                    _append_event_and_hold_id(s, event);
                              }
                              list_remove(s->queries, cq);
                        }

                        _remove_query_datagrams(s, q);
                        list_remove(s->queries, q);
                        --n; // adjust position
                        continue;
                  }
            }

            // out of name servers?
            if(q->servers_tried_count == s->name_servers->count)
            {
                  // clear the 'tried' list, and start over in retry mode
                  query_clear_servers_tried(q);
                  q->retrying = 1;
            }

            // find a nameserver that has not been tried
            ns = 0;
            for(k = 0; k < s->name_servers->count; ++k)
            {
                  name_server_t *i = (name_server_t *)s->name_servers->item[k];
                  if(!query_server_tried(q, i->id))
                  {
                        ns = i;
                        break;
                  }
            }

            // in theory, it is not possible for 'ns' to be null here

            // don't send the packet if there is already one in the queue
            already_sending = 0;
            for(k = 0; k < s->outgoing->count; ++k)
            {
                  datagram_t *a = (datagram_t *)s->outgoing->item[k];
                  if(a->query == q && a->query_send_type == 0)
                  {
                        already_sending = 1;
                        break;
                  }
            }

            // send the query, with recursion desired, normal query_send_type
            if(!already_sending)
                  _queue_packet(s, q, ns, 1, 0);

            query_add_server_tried(q, ns->id);

            // if there is one query, then do a trick on the first step
            /*if(s->queries->count == 1 && q->step == 0 && !q->retrying)
            {
                  // query all other servers non-recursively
                  // note: if sending fails, there is no retry
                  for(k = 0; k < s->name_servers->count; ++k)
                  {
                        name_server_t *i = (name_server_t *)s->name_servers->item[k];
                        if(!query_server_tried(q, i->id))
                        {
                              // last arg sets first-step query_send_type
                              _queue_packet(s, q, i, 0, 1);
                        }
                  }
            }*/

            // out of name servers?
            if(q->servers_tried_count == s->name_servers->count)
            {
                  // clear the 'tried' list, and start over in retry mode
                  query_clear_servers_tried(q);
                  q->retrying = 1;
            }

            q->time_start = now;
            q->time_next = q->retrying ? 1500 : 800;
            ++q->step;
      }

      // try to send queued outgoing packets
      for(n = 0; n < s->outgoing->count; ++n)
      {
            datagram_t *a = (datagram_t *)s->outgoing->item[n];
            int ret;

            if(!s->handle_writable)
            {
                  need_write = 1;
                  break;
            }

            _debug_line(s, "SEND %s:%d (size=%d)", a->dest_address->c_str, a->dest_port, a->size);
            _print_hexdump(s, a->data, a->size);

            ret = s->cb.udp_write(s, s->cb.app, a->handle, a->dest_address, a->dest_port, a->data, a->size);
            if(ret == 0)
            {
                  s->handle_writable = 0;
                  need_write = 1;
                  break;
            }

            list_remove(s->outgoing, a);
            --n; // adjust position
      }

      return need_write;
}

void _cache_add(jdns_session_t *s, const unsigned char *qname, int qtype, int time_start, int ttl, const jdns_rr_t *record)
{
      cache_item_t *i;
      jdns_string_t *str;
      if(ttl == 0)
            return;
      if(s->cache->count >= JDNS_CACHE_MAX)
            return;
      i = cache_item_new();
      i->qname = _ustrdup(qname);
      i->qtype = qtype;
      i->time_start = time_start;
      i->ttl = ttl;
      if(record)
            i->record = jdns_rr_copy(record);
      list_insert(s->cache, i, -1);

      str = _make_printable_cstr((const char *)i->qname);
      _debug_line(s, "cache add [%s] for %d seconds", str->data, i->ttl);
      jdns_string_delete(str);
}

void _cache_remove_all_of_kind(jdns_session_t *s, const unsigned char *qname, int qtype)
{
      int n;
      for(n = 0; n < s->cache->count; ++n)
      {
            cache_item_t *i = (cache_item_t *)s->cache->item[n];
            if(jdns_domain_cmp(i->qname, qname) && i->qtype == qtype)
            {
                  jdns_string_t *str = _make_printable_cstr((const char *)i->qname);
                  _debug_line(s, "cache del [%s]", str->data);
                  jdns_string_delete(str);
                  list_remove(s->cache, i);
                  --n; // adjust position
            }
      }
}

void _cache_remove_all_of_record(jdns_session_t *s, const jdns_rr_t *record)
{
      int n;
      for(n = 0; n < s->cache->count; ++n)
      {
            cache_item_t *i = (cache_item_t *)s->cache->item[n];
            if(i->record && _cmp_rr(i->record, record))
            {
                  jdns_string_t *str = _make_printable_cstr((const char *)i->qname);
                  _debug_line(s, "cache del [%s]", str->data);
                  jdns_string_delete(str);
                  list_remove(s->cache, i);
                  --n; // adjust position
            }
      }
}

// same as _cache_add, but make sure the exact same record (name AND value)
//   isn't stored twice, and make sure no more than one cname record per name
//   is stored.
void _cache_add_no_dups(jdns_session_t *s, const unsigned char *qname, int qtype, int time_start, int ttl, const jdns_rr_t *record)
{
      if(qtype == JDNS_RTYPE_CNAME)
            _cache_remove_all_of_kind(s, qname, qtype);
      else
            _cache_remove_all_of_record(s, record);

      _cache_add(s, qname, qtype, time_start, ttl, record);
}

int _unicast_do_reads(jdns_session_t *s, int now)
{
      int need_read;
      int n, k;

      // let's always ask for reads, just so the user doesn't have to
      //  worry about what should happen to incoming packets otherwise
      need_read = 1;

      if(!s->handle_readable)
            return need_read;

      while(1)
      {
            unsigned char buf[JDNS_UDP_UNI_IN_MAX];
            int bufsize = JDNS_UDP_UNI_IN_MAX;
            int ret;
            jdns_packet_t *packet;
            jdns_address_t *addr;
            int port;
            query_t *q;
            name_server_t *ns;

            addr = jdns_address_new();
            ret = s->cb.udp_read(s, s->cb.app, s->handle, addr, &port, buf, &bufsize);

            // no packet?
            if(ret == 0)
            {
                  s->handle_readable = 0;
                  jdns_address_delete(addr);
                  break;
            }

            _debug_line(s, "RECV %s:%d (size=%d)", addr->c_str, port, bufsize);
            _print_hexdump(s, buf, bufsize);

            if(!jdns_packet_import(&packet, buf, bufsize))
            {
                  _debug_line(s, "error parsing packet / too large");

                  jdns_address_delete(addr);
                  continue;
            }

            _print_packet(s, packet);

            if(s->queries->count == 0)
            {
                  _debug_line(s, "we have no queries");

                  jdns_address_delete(addr);
                  jdns_packet_delete(packet);
                  continue;
            }

            // who does it belong to?
            q = 0;
            ns = 0;
            for(n = 0; n < s->queries->count; ++n)
            {
                  query_t *i = (query_t *)s->queries->item[n];
                  if(i->dns_id == -1)
                        continue;

                  if(i->dns_id == packet->id)
                  {
                        q = i;
                        break;
                  }
            }

            if(q)
            {
                  // what name server did it come from?
                  for(k = 0; k < s->name_servers->count; ++k)
                  {
                        name_server_t *i = (name_server_t *)s->name_servers->item[k];
                        if(jdns_address_cmp(i->address, addr) && i->port == port)
                        {
                              ns = i;
                              break;
                        }
                  }

                  // none? maybe that's because we're using unicast
                  //   over multicast, where responses always come
                  //   from an unexpected address
                  if(!ns && s->name_servers->count > 0)
                  {
                        name_server_t *i;
                        jdns_address_t *m4, *m6;

                        i = (name_server_t *)s->name_servers->item[0];
                        m4 = jdns_address_multicast4_new();
                        m6 = jdns_address_multicast6_new();
                        if(jdns_address_cmp(i->address, m4) || jdns_address_cmp(i->address, m6))
                              ns = i;
                        jdns_address_delete(m4);
                        jdns_address_delete(m6);
                  }

                  // no suitable name server
                  if(!ns)
                  {
                        // setting q = 0 causes the response to be
                        //   ignored.  earlier versions of jdns would
                        //   do this, but now we comment it out because
                        //   the behavior is too strict.
                        //q = 0;

                        // instead we'll just print a warning
                        _debug_line(s, "warning: response from unexpected nameserver");
                  }
            }

            jdns_address_delete(addr);

            // no queries?  eat the packet
            if(!q)
            {
                  _debug_line(s, "no such query for packet");
                  jdns_packet_delete(packet);
                  continue;
            }

            _process_message(s, packet, now, q, ns);
            jdns_packet_delete(packet);
      }

      return need_read;
}

void _process_message(jdns_session_t *s, jdns_packet_t *packet, int now, query_t *q, name_server_t *ns)
{
      int n;
      int authoritative;
      int truncated;
      int recursion_desired;
      int answer_section_ok;
      jdns_response_t *r;

      if(packet->opts.opcode != 0)
      {
            _debug_line(s, "opcode != 0, discarding");
            return;
      }

      // we don't test RA (recursion available)
      // we don't test the extra Z fields

      authoritative = packet->opts.aa;
      truncated = packet->opts.tc;
      recursion_desired = packet->opts.rd;
      answer_section_ok = 0;
      if(packet->qdcount == packet->questions->count && packet->ancount == packet->answerRecords->count)
            answer_section_ok = 1;

      r = 0;

      // nxdomain
      if(packet->opts.rcode == 3)
      {
            // treat nxdomain as a generic error, but at the same time flag
            //   the fact that it was received.  this ensures that
            //   resolving keeps going, in case the user has multiple dns
            //   servers and one of them reports nxdomain when a later one
            //   would succeed.  if all of the servers fail then this flag
            //   can be used at the end to report nxdomain instead of a
            //   generic error.
            q->nxdomain = 1;
      }
      // normal
      else if(packet->opts.rcode == 0)
      {
            int at_least_something;
            int success;

            r = _packet2response(packet, q->qname, q->qtype, 0xffff);
            at_least_something = 0;
            if(r->answerCount > 0)
                  at_least_something = 1;
            _print_records(s, r, q->qname);

            success = 0;
            if(at_least_something)
            {
                  success = 1;
            }
            else
            {
                  // note: why does qdns care about recursion_desired here?
                  if(authoritative && recursion_desired)
                        success = 1;
            }

            if(!success)
            {
                  jdns_response_delete(r);
                  r = 0;
            }
      }

      // caching
      if(r)
      {
            int cache_answers;
            int cache_additional;

            // clear past items
            _cache_remove_all_of_kind(s, q->qname, q->qtype);

            cache_answers = 1;
            cache_additional = 1;

            // if truncated, we may not want to cache
            if(truncated)
            {
                  cache_additional = 0;
                  if(!answer_section_ok)
                        cache_answers = 0;
            }

            if(cache_answers)
            {
                  for(n = 0; n < r->answerCount; ++n)
                  {
                        jdns_rr_t *record = r->answerRecords[n];
                        _cache_add_no_dups(s, q->qname, record->type, now, _min(record->ttl, JDNS_TTL_MAX), record);
                  }
            }

            if(cache_additional)
            {
                  for(n = 0; n < r->additionalCount; ++n)
                  {
                        jdns_rr_t *record = r->additionalRecords[n];
                        _cache_add_no_dups(s, record->owner, record->type, now, _min(record->ttl, JDNS_TTL_MAX), record);
                  }
            }
      }

      // don't pass authority/additional records upwards
      if(r)
            jdns_response_remove_extra(r);

      // this server returned an error?
      if(!r && ns)
      {
            // all failed servers must also be considered tried servers,
            //   so mark as tried if necessary.  this can happen if the
            //   tried list is cleared (to perform retrying) and then an
            //   error is received
            if(!query_server_tried(q, ns->id))
                  query_add_server_tried(q, ns->id);

            query_add_server_failed(q, ns->id);
      }

      if(_process_response(s, r, 0, now, q))
      {
            _remove_query_datagrams(s, q);
            list_remove(s->queries, q);
      }

      jdns_response_delete(r);
}

// 'r' can be null, for processing an error
// 'now' can be -1, if processing a cached response ('r' always non-null)
int _process_response(jdns_session_t *s, jdns_response_t *r, int nxdomain, int now, query_t *q)
{
      int k;
      int do_error = 0;
      int do_nxdomain = 0;

      // error
      if(!r)
      {
            int all_errored;

            // if not all servers have errored, ignore error
            all_errored = 1;
            for(k = 0; k < s->name_servers->count; ++k)
            {
                  name_server_t *ns = (name_server_t *)s->name_servers->item[k];
                  if(!query_server_failed(q, ns->id))
                  {
                        all_errored = 0;
                        break;
                  }
            }
            if(!all_errored)
                  return 0;

            do_error = 1;

            // if we picked up an nxdomain along the way, act on it now
            if(q->nxdomain)
            {
                  do_nxdomain = 1;

                  // cache nxdomain for 1 minute
                  if(q->qtype != JDNS_RTYPE_ANY && now != -1)
                  {
                        _cache_remove_all_of_kind(s, q->qname, q->qtype);
                        _cache_add(s, q->qname, q->qtype, now, 60, 0);
                  }
            }
      }
      else if(nxdomain)
      {
            do_error = 1;
            do_nxdomain = 1;
      }

      if(do_error)
      {
            // report event to any requests listening
            for(k = 0; k < q->req_ids_count; ++k)
            {
                  jdns_event_t *event = jdns_event_new();
                  event->type = JDNS_EVENT_RESPONSE;
                  event->id = q->req_ids[k];
                  if(do_nxdomain)
                        event->status = JDNS_STATUS_NXDOMAIN;
                  else
                        event->status = JDNS_STATUS_ERROR;
                  _append_event_and_hold_id(s, event);
            }

            // report error to parent
            if(q->cname_parent)
            {
                  // report event to any requests listening
                  query_t *cq = q->cname_parent;
                  for(k = 0; k < cq->req_ids_count; ++k)
                  {
                        jdns_event_t *event = jdns_event_new();
                        event->type = JDNS_EVENT_RESPONSE;
                        event->id = cq->req_ids[k];
                        event->status = JDNS_STATUS_ERROR;
                        _append_event_and_hold_id(s, event);
                  }
                  list_remove(s->queries, cq);
            }

            return 1;
      }

      // all we got was a cname that we didn't ask for?
      if(r->answerCount == 1 && r->answerRecords[0]->type == JDNS_RTYPE_CNAME && q->qtype != JDNS_RTYPE_CNAME)
      {
            query_t *new_q;

            _debug_line(s, "all we got was a cname, following the chain ...");

            // max chain count, bail
            if(q->cname_chain_count >= JDNS_CNAME_MAX)
            {
                  // report event to any requests listening
                  for(k = 0; k < q->req_ids_count; ++k)
                  {
                        jdns_event_t *event = jdns_event_new();
                        event->type = JDNS_EVENT_RESPONSE;
                        event->id = q->req_ids[k];
                        event->status = JDNS_STATUS_ERROR;
                        _append_event_and_hold_id(s, event);
                  }

                  // report error to parent
                  if(q->cname_parent)
                  {
                        // report event to any requests listening
                        query_t *cq = q->cname_parent;
                        for(k = 0; k < cq->req_ids_count; ++k)
                        {
                              jdns_event_t *event = jdns_event_new();
                              event->type = JDNS_EVENT_RESPONSE;
                              event->id = cq->req_ids[k];
                              event->status = JDNS_STATUS_ERROR;
                              _append_event_and_hold_id(s, event);
                        }
                        list_remove(s->queries, cq);
                  }

                  return 1;
            }

            new_q = _get_query(s, r->answerRecords[0]->data.name, q->qtype, 1);

            // is the current query a child query? (has a parent)
            if(q->cname_parent)
            {
                  // if so, then set new_q as the new child
                  new_q->cname_chain_count = q->cname_chain_count + 1;
                  new_q->cname_parent = q->cname_parent;
                  new_q->cname_parent->cname_child = new_q;

                  // and delete the current query
                  return 1;
            }
            else
            {
                  // otherwise, the current query becomes a parent, with
                  //   new_q set as the child
                  new_q->cname_chain_count = q->cname_chain_count + 1;
                  new_q->cname_parent = q;
                  q->cname_child = new_q;
                  q->time_start = -1;
                  q->dns_id = -1; // don't handle responses
            }
      }

      // if this query now has a child, then don't report events or delete
      if(q->cname_child)
            return 0;

      // report event to any requests listening
      for(k = 0; k < q->req_ids_count; ++k)
      {
            jdns_event_t *event = jdns_event_new();
            event->type = JDNS_EVENT_RESPONSE;
            event->id = q->req_ids[k];
            event->status = JDNS_STATUS_SUCCESS;
            event->response = jdns_response_copy(r);
            _append_event_and_hold_id(s, event);
      }

      // report to parent
      if(q->cname_parent)
      {
            // report event to any requests listening
            query_t *cq = q->cname_parent;
            for(k = 0; k < cq->req_ids_count; ++k)
            {
                  jdns_event_t *event = jdns_event_new();
                  event->type = JDNS_EVENT_RESPONSE;
                  event->id = cq->req_ids[k];
                  event->status = JDNS_STATUS_SUCCESS;
                  event->response = jdns_response_copy(r);
                  _append_event_and_hold_id(s, event);
            }
            list_remove(s->queries, cq);
      }

      return 1;
}

//----------------------------------------------------------------------------
// jdns - multicast
//----------------------------------------------------------------------------
static jdns_rr_t *_mdnsda2rr(mdnsda a)
{
      jdns_rr_t *rr;

      if(a->type == JDNS_RTYPE_ANY)
            return 0;

      // for AAAA, TXT and HINFO, run the raw rdata through jdns_rr's parser
      if(a->type == JDNS_RTYPE_AAAA || a->type == JDNS_RTYPE_TXT || a->type == JDNS_RTYPE_HINFO)
      {
            jdns_packet_resource_t *pr = jdns_packet_resource_new();
            pr->qname = jdns_string_new();
            jdns_string_set_cstr(pr->qname, (const char *)a->name);
            pr->qtype = a->type;
            pr->qclass = 0x0001; // class is always 1 for us
            if(a->ttl == 0)
                  pr->ttl = 0;
            else
                  pr->ttl = a->real_ttl;
            pr->rdata = jdns_copy_array(a->rdata, a->rdlen);
            pr->rdlength = a->rdlen;

            // we don't need a reference for these types
            rr = jdns_rr_from_resource(pr, 0);
      }
      // else, pull the values out of 'a' directly
      else
      {
            rr = jdns_rr_new();
            rr->owner = _ustrdup(a->name);
            rr->qclass = 0x0001; // class is always 1 for us
            if(a->ttl == 0)
                  rr->ttl = 0;
            else
                  rr->ttl = a->real_ttl;

            switch(a->type)
            {
                  case JDNS_RTYPE_A:
                  {
                        jdns_address_t *addr = jdns_address_new();
                        jdns_address_set_ipv4(addr, a->ip);
                        jdns_rr_set_A(rr, addr);
                        jdns_address_delete(addr);
                        break;
                  }
                  case JDNS_RTYPE_AAAA:
                  {
                        // covered earlier
                        break;
                  }
                  case JDNS_RTYPE_MX:
                  {
                        // don't care about MX
                        jdns_rr_delete(rr);
                        rr = 0;
                        break;
                  }
                  case JDNS_RTYPE_SRV:
                  {
                        jdns_rr_set_SRV(rr, a->rdname, a->srv.port, a->srv.priority, a->srv.weight);
                        break;
                  }
                  case JDNS_RTYPE_CNAME:
                  {
                        jdns_rr_set_CNAME(rr, a->rdname);
                        break;
                  }
                  case JDNS_RTYPE_PTR:
                  {
                        jdns_rr_set_PTR(rr, a->rdname);
                        break;
                  }
                  case JDNS_RTYPE_TXT:
                  {
                        // covered earlier
                        break;
                  }
                  case JDNS_RTYPE_HINFO:
                  {
                        // covered earlier
                        break;
                  }
                  case JDNS_RTYPE_NS:
                  {
                        // don't care about NS
                        jdns_rr_delete(rr);
                        rr = 0;
                        break;
                  }
                  default:
                  {
                        jdns_rr_set_record(rr, a->type, a->rdata, a->rdlen);
                        break;
                  }
            }
      }

      return rr;
}

int _multicast_query_ans(mdnsda a, void *arg)
{
      int n;
      jdns_session_t *s;
      query_t *q;
      jdns_response_t *r;
      jdns_rr_t *rr;
      jdns_event_t *event;

      s = (jdns_session_t *)arg;

      // what query is this for?
      q = 0;
      for(n = 0; n < s->queries->count; ++n)
      {
            query_t *i = (query_t *)s->queries->item[n];
            if((i->qtype == JDNS_RTYPE_ANY || i->qtype == a->type) && jdns_domain_cmp(i->qname, a->name))
            {
                  q = i;
                  break;
            }
      }

      // note: this can't happen, but we'll check anyway
      if(!q)
      {
            _debug_line(s, "no such multicast query");
            return 0;
      }

      rr = _mdnsda2rr(a);
      if(!rr)
            return 0;

      // add/remove as a known
      if(rr->ttl == 0)
      {
            for(n = 0; n < q->mul_known->answerCount; ++n)
            {
                  jdns_rr_t *k = q->mul_known->answerRecords[n];
                  if(_cmp_rr(k, rr))
                  {
                        jdns_response_remove_answer(q->mul_known, n);
                        break;
                  }
            }
      }
      else
            jdns_response_append_answer(q->mul_known, rr);

      r = jdns_response_new();
      jdns_response_append_answer(r, rr);
      jdns_rr_delete(rr);

      // report event to any requests listening
      for(n = 0; n < q->req_ids_count; ++n)
      {
            event = jdns_event_new();
            event->type = JDNS_EVENT_RESPONSE;
            event->id = q->req_ids[n];
            event->status = JDNS_STATUS_SUCCESS;
            event->response = jdns_response_copy(r);
            _append_event(s, event);
      }

      jdns_response_delete(r);
      return 0;
}

query_t *_get_multicast_query(jdns_session_t *s, const unsigned char *qname, int qtype)
{
      int n;
      query_t *q;
      jdns_string_t *str;

      // check for existing queries
      for(n = 0; n < s->queries->count; ++n)
      {
            q = (query_t *)s->queries->item[n];
            if(jdns_domain_cmp(q->qname, qname) && q->qtype == qtype)
            {
                  str = _make_printable_cstr((const char *)q->qname);
                  _debug_line(s, "[%d] reusing query for: [%s] [%s]", q->id, _qtype2str(qtype), str->data);
                  jdns_string_delete(str);
                  return q;
            }
      }

      q = query_new();
      q->id = get_next_qid(s);
      q->qname = _ustrdup(qname);
      q->qtype = qtype;
      q->step = 0;
      q->mul_known = jdns_response_new();
      list_insert(s->queries, q, -1);

      str = _make_printable_cstr((const char *)q->qname);
      _debug_line(s, "[%d] querying: [%s] [%s]", q->id, _qtype2str(qtype), str->data);
      jdns_string_delete(str);
      return q;
}

int _multicast_query(jdns_session_t *s, const unsigned char *name, int qtype)
{
      unsigned char *qname;
      query_t *q;
      int req_id;
      jdns_string_t *str;

      str = _make_printable_cstr((const char *)name);
      _debug_line(s, "query input: [%s]", str->data);
      jdns_string_delete(str);

      // add a dot to the end if needed
      qname = _fix_input(name);

      q = _get_multicast_query(s, qname, qtype);
      req_id = get_next_req_id(s);
      query_add_req_id(q, req_id);
      free(qname);

      // start the mdnsd_query if necessary
      if(q->step == 0)
      {
            q->step = 1;
            mdnsd_query(s->mdns, (char *)q->qname, q->qtype, _multicast_query_ans, s);
      }
      else
      {
            int n;

            // report the knowns
            for(n = 0; n < q->mul_known->answerCount; ++n)
            {
                  const jdns_rr_t *rr;
                  jdns_response_t *r;
                  jdns_event_t *event;

                  rr = q->mul_known->answerRecords[n];
                  r = jdns_response_new();
                  jdns_response_append_answer(r, rr);

                  event = jdns_event_new();
                  event->type = JDNS_EVENT_RESPONSE;
                  event->id = req_id;
                  event->status = JDNS_STATUS_SUCCESS;
                  event->response = r;
                  _append_event(s, event);
            }
      }
      return req_id;
}

void _multicast_cancel(jdns_session_t *s, int req_id)
{
      int n;
      for(n = 0; n < s->queries->count; ++n)
      {
            query_t *q = (query_t *)s->queries->item[n];
            if(query_have_req_id(q, req_id))
            {
                  query_remove_req_id(q, req_id);

                  // if no one else is depending on this request, then take action
                  if(q->req_ids_count == 0)
                  {
                        mdnsd_query(s->mdns, (char *)q->qname, q->qtype, NULL, 0);
                        list_remove(s->queries, q);
                  }
                  break;
            }
      }
}

void _multicast_pubresult(int result, char *name, int type, void *arg)
{
      jdns_session_t *s;
      published_item_t *pub;
      jdns_event_t *event;
      int n;

      s = (jdns_session_t *)arg;

      // find the associated pub item
      pub = 0;
      for(n = 0; n < s->published->count; ++n)
      {
            published_item_t *i = (published_item_t *)s->published->item[n];
            if(strcmp((char *)i->qname, name) == 0 && i->qtype == type)
            {
                  pub = i;
                  break;
            }
      }

      // note: this can't happen, but we'll check anyway
      if(!pub)
      {
            _debug_line(s, "no such multicast published item");
            return;
      }

      if(result == 1)
      {
            jdns_string_t *str = _make_printable_cstr(name);
            _debug_line(s, "published name %s for type %d", str->data, type);
            jdns_string_delete(str);

            event = jdns_event_new();
            event->type = JDNS_EVENT_PUBLISH;
            event->id = pub->id;
            event->status = JDNS_STATUS_SUCCESS;
            _append_event(s, event);
      }
      else
      {
            jdns_string_t *str = _make_printable_cstr(name);
            _debug_line(s, "conflicting name detected %s for type %d", str->data, type);
            jdns_string_delete(str);

            event = jdns_event_new();
            event->type = JDNS_EVENT_PUBLISH;
            event->id = pub->id;
            event->status = JDNS_STATUS_CONFLICT;
            _append_event_and_hold_id(s, event);

            // remove the item
            list_remove(s->published, pub);
      }
}

static jdns_string_t *_create_text(const jdns_stringlist_t *texts)
{
      jdns_string_t *out;
      int n;
      int total;
      unsigned char *buf;

      buf = 0;
      total = 0;
      for(n = 0; n < texts->count; ++n)
            total += texts->item[n]->size + 1;
      if(total > 0)
      {
            int at = 0;
            buf = (unsigned char *)malloc(total);
            for(n = 0; n < texts->count; ++n)
            {
                  unsigned int len = texts->item[n]->size;
                  buf[at++] = len;
                  memcpy(buf + at, texts->item[n]->data, len);
                  at += len;
            }
      }

      out = jdns_string_new();
      if(buf)
      {
            out->data = buf;
            out->size = total;
      }
      else
            jdns_string_set_cstr(out, "");
      return out;
}

static void _publish_applyrr_unknown(jdns_session_t *s, mdnsdr r, const jdns_rr_t *rr)
{
      // for unsupported/unknown, just take the rdata
      // note: for this to work, the app must explicitly set the rdata.
      //   if the record is MX or some other known but unsupported record
      //   type, setting the known fields is not enough
      mdnsd_set_raw(s->mdns, r, (char *)rr->rdata, rr->rdlength);
}

static int _publish_applyrr(jdns_session_t *s, mdnsdr r, const jdns_rr_t *rr)
{
      if(!rr->haveKnown)
      {
            _publish_applyrr_unknown(s, r, rr);
            return 1;
      }

      // jdns_mdnsd supports: A, AAAA, SRV, CNAME, PTR, TXT, and HINFO
      switch(rr->type)
      {
            case JDNS_RTYPE_A:
            {
                  unsigned long int ip_net = htonl(rr->data.address->addr.v4);
                  mdnsd_set_raw(s->mdns, r, (char *)&ip_net, 4);
                  break;
            }
            case JDNS_RTYPE_AAAA:
            {
                  mdnsd_set_raw(s->mdns, r, (char *)rr->data.address->addr.v6, 16);
                  break;
            }
            case JDNS_RTYPE_SRV:
            {
                  mdnsd_set_srv(s->mdns, r, rr->data.server->priority, rr->data.server->weight, rr->data.server->port, (char *)rr->data.server->name);
                  break;
            }
            case JDNS_RTYPE_CNAME:
            {
                  mdnsd_set_host(s->mdns, r, (char *)rr->data.name);
                  break;
            }
            case JDNS_RTYPE_PTR:
            {
                  mdnsd_set_host(s->mdns, r, (char *)rr->data.name);
                  break;
            }
            case JDNS_RTYPE_TXT:
            {
                  jdns_string_t *out = _create_text(rr->data.texts);
                  mdnsd_set_raw(s->mdns, r, (char *)out->data, out->size);
                  jdns_string_delete(out);
                  break;
            }
            case JDNS_RTYPE_HINFO:
            {
                  jdns_string_t *out;
                  jdns_stringlist_t *list;

                  list = jdns_stringlist_new();
                  jdns_stringlist_append(list, rr->data.hinfo.cpu);
                  jdns_stringlist_append(list, rr->data.hinfo.os);
                  out = _create_text(list);
                  jdns_stringlist_delete(list);

                  mdnsd_set_raw(s->mdns, r, (char *)out->data, out->size);
                  jdns_string_delete(out);
                  break;
            }
            default:
            {
                  _publish_applyrr_unknown(s, r, rr);
                  break;
            }
      }

      return 1;
}

static void report_published(jdns_session_t *s, published_item_t *pub)
{
      jdns_event_t *event;
      jdns_string_t *str;

      str = _make_printable_cstr((char *)pub->qname);
      _debug_line(s, "published name %s for type %d", str->data, pub->qtype);
      jdns_string_delete(str);

      event = jdns_event_new();
      event->type = JDNS_EVENT_PUBLISH;
      event->id = pub->id;
      event->status = JDNS_STATUS_SUCCESS;
      _append_event(s, event);
}

int _multicast_publish(jdns_session_t *s, int mode, const jdns_rr_t *rr)
{
      mdnsdr r;
      published_item_t *pub;
      int next_id;
      jdns_event_t *event;
      int n;

      r = 0;
      next_id = get_next_req_id(s);

      // see if we have an item with this name+type combination already
      pub = 0;
      for(n = 0; n < s->published->count; ++n)
      {
            published_item_t *i = (published_item_t *)s->published->item[n];
            if(i->qtype == rr->type && jdns_domain_cmp(i->qname, rr->owner))
            {
                  pub = i;
                  break;
            }
      }
      if(pub)
            goto error;

      if(!jdns_rr_verify(rr))
            goto error;

      if(mode == JDNS_PUBLISH_UNIQUE)
            r = mdnsd_unique(s->mdns, (char *)rr->owner, rr->type, rr->ttl, _multicast_pubresult, s);
      else
            r = mdnsd_shared(s->mdns, (char *)rr->owner, rr->type, rr->ttl);

      if(!_publish_applyrr(s, r, rr))
            goto error;

      pub = published_item_new();
      pub->id = next_id;
      pub->mode = mode;
      pub->qname = _ustrdup(rr->owner);
      pub->qtype = rr->type;
      pub->rec = r;
      pub->rr = jdns_rr_copy(rr);
      list_insert(s->published, pub, -1);

      // mdnsd doesn't report publish events for shared, so do that here
      if(mode == JDNS_PUBLISH_SHARED)
            report_published(s, pub);

      return pub->id;

error:
      _debug_line(s, "attempt to publish record, malformed, unsupported, or duplicate type");

      if(r)
      {
            // don't publish
            mdnsd_done(s->mdns, r);
      }

      // send an error to the app
      event = jdns_event_new();
      event->type = JDNS_EVENT_PUBLISH;
      event->id = next_id;
      event->status = JDNS_STATUS_ERROR;
      _append_event_and_hold_id(s, event);

      return next_id;
}

void _multicast_update_publish(jdns_session_t *s, int id, const jdns_rr_t *rr)
{
      mdnsdr r;
      published_item_t *pub;
      int qtype;
      int n;

      pub = 0;
      for(n = 0; n < s->published->count; ++n)
      {
            published_item_t *i = (published_item_t *)s->published->item[n];
            if(i->id == id)
            {
                  pub = i;
                  break;
            }
      }
      if(!pub)
            return;

      qtype = pub->qtype;
      r = pub->rec;

      // expire existing record.  this is mostly needed for shared records
      //   since unique records already have the cache flush bit and that
      //   should achieve the same result.  however, since Apple expires
      //   unique records before updates, so will we.
      mdnsd_done(s->mdns, r);
      if(pub->mode == JDNS_PUBLISH_UNIQUE)
            r = mdnsd_unique(s->mdns, (char *)pub->rr->owner, pub->rr->type, rr->ttl, _multicast_pubresult, s);
      else
            r = mdnsd_shared(s->mdns, (char *)pub->rr->owner, pub->rr->type, rr->ttl);
      pub->rec = r;

      if(!_publish_applyrr(s, r, rr))
      {
            _debug_line(s, "attempt to update_publish an unsupported type");
            return;
      }
}

void _multicast_cancel_publish(jdns_session_t *s, int id)
{
      int n;
      for(n = 0; n < s->published->count; ++n)
      {
            published_item_t *i = (published_item_t *)s->published->item[n];
            if(i->id == id)
            {
                  mdnsd_done(s->mdns, i->rec);
                  list_remove(s->published, i);
                  break;
            }
      }
}

void _multicast_flush(jdns_session_t *s)
{
      int n;

      // to flush, we make like our queries and published items are all new.
      // we'll do this by destroying/creating the mdnsd object again (so it
      // is fresh) and then reapply all queries and published items to it.

      // start over with mdnsd
      mdnsd_free(s->mdns);
      s->mdns = mdnsd_new(0x0001, 1000, s->port, _callback_time_now, _callback_rand_int, s);

      // attempt to publish again
      for(n = 0; n < s->published->count; ++n)
      {
            published_item_t *i;
            mdnsdr r;

            i = (published_item_t *)s->published->item[n];
            if(i->mode == JDNS_PUBLISH_UNIQUE)
                  r = mdnsd_unique(s->mdns, (char *)i->rr->owner, i->rr->type, i->rr->ttl, _multicast_pubresult, s);
            else
                  r = mdnsd_shared(s->mdns, (char *)i->rr->owner, i->rr->type, i->rr->ttl);
            _publish_applyrr(s, r, i->rr);
            i->rec = r;
      }

      // restore the queries
      for(n = 0; n < s->queries->count; ++n)
      {
            query_t *q = (query_t *)s->queries->item[n];

            // issue the query
            mdnsd_query(s->mdns, (char *)q->qname, q->qtype, _multicast_query_ans, s);
      }
}

int jdns_step_multicast(jdns_session_t *s, int now)
{
      int need_read, need_write, smallest_time;
      struct mytimeval *tv;
      jdns_packet_t *packet;
      int flags;

      // not used
      (void)now;

      need_read = 0;
      need_write = 0;

      if(s->shutdown == 1)
            mdnsd_shutdown(s->mdns);

      while(1)
      {
            jdns_address_t *addr;
            unsigned short int port;
            int ret;
            unsigned char *buf;
            int buf_len;

            if(!mdnsd_out(s->mdns, &packet, &addr, &port))
                  break;

            if(!s->handle_writable)
            {
                  need_write = 1;
                  jdns_address_delete(addr);
                  break;
            }

            if(!jdns_packet_export(packet, JDNS_UDP_MUL_OUT_MAX))
            {
                  _debug_line(s, "outgoing packet export error, not sending");
                  jdns_packet_delete(packet);
                  continue;
            }

            buf = packet->raw_data;
            buf_len = packet->raw_size;

            // multicast
            if(!addr)
            {
                  addr = jdns_address_copy(s->maddr);
                  port = s->port;
            }

            _debug_line(s, "SEND %s:%d (size=%d)", addr->c_str, port, buf_len);
            _print_hexdump(s, buf, buf_len);

            ret = s->cb.udp_write(s, s->cb.app, s->handle, addr, port, buf, buf_len);

            jdns_address_delete(addr);
            jdns_packet_delete(packet);

            // if we can't write the packet, oh well
            if(ret == 0)
            {
                  s->handle_writable = 0;
                  need_write = 1;
                  break;
            }
      }

      if(s->shutdown == 1)
      {
            jdns_event_t *event = jdns_event_new();
            event->type = JDNS_EVENT_SHUTDOWN;
            _append_event(s, event);
            s->shutdown = 2;
            return 0;
      }

      // let's always ask for reads, just so the user doesn't have to
      //  worry about what should happen to incoming packets otherwise
      need_read = 1;

      if(s->handle_readable)
      {
            while(1)
            {
                  unsigned char buf[JDNS_UDP_MUL_IN_MAX];
                  int bufsize = JDNS_UDP_MUL_IN_MAX;
                  int ret;
                  jdns_address_t *addr;
                  int port;
                  jdns_response_t *r;

                  addr = jdns_address_new();
                  ret = s->cb.udp_read(s, s->cb.app, s->handle, addr, &port, buf, &bufsize);

                  // no packet?
                  if(ret == 0)
                  {
                        s->handle_readable = 0;
                        jdns_address_delete(addr);
                        break;
                  }

                  _debug_line(s, "RECV %s:%d (size=%d)", addr->c_str, port, bufsize);
                  _print_hexdump(s, buf, bufsize);

                  if(!jdns_packet_import(&packet, buf, bufsize))
                  {
                        _debug_line(s, "error parsing packet / too large");

                        jdns_address_delete(addr);
                        continue;
                  }

                  _print_packet(s, packet);

                  r = _packet2response(packet, 0, 0, 0x7fff);
                  _print_records(s, r, 0);

                  mdnsd_in(s->mdns, packet, r, addr, (unsigned short)port);

                  jdns_address_delete(addr);
                  jdns_packet_delete(packet);
                  jdns_response_delete(r);
            }
      }

      tv = mdnsd_sleep(s->mdns);
      smallest_time = tv->tv_sec * 1000 + tv->tv_usec / 1000;

      flags = 0;
      if(smallest_time != -1)
      {
            flags |= JDNS_STEP_TIMER;
            s->next_timer = smallest_time;

            // offset it a little bit, so that the user doesn't call
            //  us too early, resulting in a no-op and another timer
            //  of 1 millisecond.
            s->next_timer += 2;
      }
      if(need_read || need_write)
            flags |= JDNS_STEP_HANDLE;
      return flags;
}

Generated by  Doxygen 1.6.0   Back to index