3177 lines
70 KiB
C
3177 lines
70 KiB
C
|
|
/*
|
||
|
|
* Copyright (C) 2005,2006 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)
|
||
|
|
|
||
|
|
//----------------------------------------------------------------------------
|
||
|
|
// 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 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 can match or be CNAME
|
||
|
|
if((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
|
||
|
|
//----------------------------------------------------------------------------
|
||
|
|
typedef struct list_item
|
||
|
|
{
|
||
|
|
void (*dtor)(void *);
|
||
|
|
} list_item_t;
|
||
|
|
|
||
|
|
typedef struct list
|
||
|
|
{
|
||
|
|
int count;
|
||
|
|
list_item_t **item;
|
||
|
|
} list_t;
|
||
|
|
|
||
|
|
list_t *list_new()
|
||
|
|
{
|
||
|
|
list_t *l = alloc_type(list_t);
|
||
|
|
l->count = 0;
|
||
|
|
l->item = 0;
|
||
|
|
return l;
|
||
|
|
}
|
||
|
|
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
typedef struct name_server
|
||
|
|
{
|
||
|
|
void (*dtor)(struct name_server *);
|
||
|
|
int id;
|
||
|
|
jdns_address_t *address;
|
||
|
|
int port;
|
||
|
|
} name_server_t;
|
||
|
|
|
||
|
|
void name_server_delete(name_server_t *ns);
|
||
|
|
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
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;
|
||
|
|
|
||
|
|
// 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->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);
|
||
|
|
}
|
||
|
|
|
||
|
|
void query_clear_servers_tried(query_t *q)
|
||
|
|
{
|
||
|
|
if(q->servers_tried)
|
||
|
|
free(q->servers_tried);
|
||
|
|
q->servers_tried_count = 0;
|
||
|
|
q->servers_tried = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
|
||
|
|
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
|
||
|
|
//----------------------------------------------------------------------------
|
||
|
|
struct jdns_session
|
||
|
|
{
|
||
|
|
jdns_callbacks_t cb;
|
||
|
|
int mode;
|
||
|
|
int shutdown;
|
||
|
|
int next_qid;
|
||
|
|
int next_req_id;
|
||
|
|
int next_dns_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;
|
||
|
|
|
||
|
|
// 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->next_dns_id = s->cb.rand_int(s, s->cb.app);
|
||
|
|
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->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->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 _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 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;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return id;
|
||
|
|
}
|
||
|
|
|
||
|
|
// starts at random, must fit in 16 bits
|
||
|
|
static int get_next_dns_id(jdns_session_t *s)
|
||
|
|
{
|
||
|
|
return (s->next_dns_id++ & 0xffff);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 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(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);
|
||
|
|
}
|
||
|
|
|
||
|
|
void jdns_cancel_query(jdns_session_t *s, int id)
|
||
|
|
{
|
||
|
|
int n;
|
||
|
|
|
||
|
|
// 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 _cancel_query 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)
|
||
|
|
{
|
||
|
|
_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;
|
||
|
|
}
|
||
|
|
|
||
|
|
//----------------------------------------------------------------------------
|
||
|
|
// 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);
|
||
|
|
vsprintf(buf, 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 _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, 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, jdns_rr_copy(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 *_get_query(jdns_session_t *s, const unsigned char *qname, int qtype, int unique)
|
||
|
|
{
|
||
|
|
int n;
|
||
|
|
query_t *q;
|
||
|
|
jdns_string_t *str;
|
||
|
|
|
||
|
|
if(!unique)
|
||
|
|
{
|
||
|
|
// 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)
|
||
|
|
{
|
||
|
|
// if it is inactive, just nuke it
|
||
|
|
if(q->step == -1)
|
||
|
|
{
|
||
|
|
_remove_query_datagrams(s, q);
|
||
|
|
list_remove(s->queries, q);
|
||
|
|
--n; // adjust position
|
||
|
|
}
|
||
|
|
// otherwise, latch onto the first one we find
|
||
|
|
else
|
||
|
|
{
|
||
|
|
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"
|
||
|
|
if(lowest_timeleft < (3 * 60 * 1000))
|
||
|
|
{
|
||
|
|
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, q))
|
||
|
|
{
|
||
|
|
_remove_query_datagrams(s, q);
|
||
|
|
list_remove(s->queries, q);
|
||
|
|
--n; // adjust position
|
||
|
|
}
|
||
|
|
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(s, event);
|
||
|
|
}
|
||
|
|
|
||
|
|
_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);
|
||
|
|
|
||
|
|
// 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 or failed
|
||
|
|
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) && !query_server_failed(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;
|
||
|
|
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
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
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);
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
|
||
|
|
// if there is no suitable name server, don't accept
|
||
|
|
// as a reply
|
||
|
|
if(!ns)
|
||
|
|
q = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
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;
|
||
|
|
int nxdomain;
|
||
|
|
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;
|
||
|
|
nxdomain = 0;
|
||
|
|
|
||
|
|
r = 0;
|
||
|
|
|
||
|
|
// nxdomain
|
||
|
|
if(packet->opts.rcode == 3)
|
||
|
|
{
|
||
|
|
r = jdns_response_new();
|
||
|
|
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)
|
||
|
|
{
|
||
|
|
if(nxdomain)
|
||
|
|
{
|
||
|
|
// cache nxdomain for 1 minute
|
||
|
|
if(q->qtype != JDNS_RTYPE_ANY)
|
||
|
|
{
|
||
|
|
_cache_remove_all_of_kind(s, q->qname, q->qtype);
|
||
|
|
_cache_add(s, q->qname, q->qtype, now, 60, 0);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
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(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(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)
|
||
|
|
query_add_server_failed(q, ns->id);
|
||
|
|
|
||
|
|
if(_process_response(s, r, nxdomain, q))
|
||
|
|
{
|
||
|
|
_remove_query_datagrams(s, q);
|
||
|
|
list_remove(s->queries, q);
|
||
|
|
}
|
||
|
|
|
||
|
|
jdns_response_delete(r);
|
||
|
|
}
|
||
|
|
|
||
|
|
int _process_response(jdns_session_t *s, jdns_response_t *r, int nxdomain, query_t *q)
|
||
|
|
{
|
||
|
|
int k;
|
||
|
|
|
||
|
|
// 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;
|
||
|
|
|
||
|
|
// 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(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(s, event);
|
||
|
|
}
|
||
|
|
list_remove(s->queries, cq);
|
||
|
|
}
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
// nxdomain
|
||
|
|
else if(nxdomain)
|
||
|
|
{
|
||
|
|
// 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_NXDOMAIN;
|
||
|
|
_append_event(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(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 >= 16)
|
||
|
|
{
|
||
|
|
// 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(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(s, event);
|
||
|
|
}
|
||
|
|
list_remove(s->queries, cq);
|
||
|
|
}
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
new_q = _get_query(s, r->answerRecords[0]->data.name, q->qtype, 1);
|
||
|
|
if(q->cname_parent)
|
||
|
|
{
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
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(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(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;
|
||
|
|
}
|
||
|
|
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
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(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(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;
|
||
|
|
|
||
|
|
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;
|
||
|
|
}
|