This repository has been archived on 2025-12-24. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
yachat/iris-legacy/iris/irisnet/jdns/jdns.c

3177 lines
70 KiB
C
Raw Normal View History

2025-12-25 01:37:49 +05:00
/*
* 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;
}