/* * 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 #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; }