/* Copyright 2006-2007 Niels Provos * Copyright 2007-2012 Nick Mathewson and Niels Provos * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* Based on software by Adam Langly. Adam's original message: * * Async DNS Library * Adam Langley <agl@imperialviolet.org> * http://www.imperialviolet.org/eventdns.html * Public Domain code * * This software is Public Domain. To view a copy of the public domain dedication, * visit http://creativecommons.org/licenses/publicdomain/ or send a letter to * Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA. * * I ask and expect, but do not require, that all derivative works contain an * attribution similar to: * Parts developed by Adam Langley <agl@imperialviolet.org> * * You may wish to replace the word "Parts" with something else depending on * the amount of original code. * * (Derivative works does not include programs which link against, run or include * the source verbatim in their source distributions) * * Version: 0.1b
*/
/* Persistent handle. We keep this separate from 'struct request' since we * need some object to last for as long as an evdns_request is outstanding so * that it can be canceled, whereas a search request can lead to multiple
* 'struct request' instances being created over its lifetime. */ struct evdns_request { struct request *current_req; struct evdns_base *base;
int pending_cb; /* Waiting for its callback to be invoked; not
* owned by event base any more. */
/* elements used by the searching code */ int search_index; struct search_state *search_state; char *search_origname; /* needs to be free()ed */ int search_flags;
};
struct request {
u8 *request; /* the dns packet data */
u8 request_type; /* TYPE_PTR or TYPE_A or TYPE_AAAA */ unsignedint request_len; int reissue_count; int tx_count; /* the number of times that this packet has been sent */ void *user_pointer; /* the pointer given to us for this request */
evdns_callback_type user_callback; struct nameserver *ns; /* the server which we last sent it */
/* these objects are kept in a circular list */ /* XXX We could turn this into a CIRCLEQ. */ struct request *next, *prev;
struct event timeout_event;
u16 trans_id; /* the transaction id */ unsigned request_appended :1; /* true if the request pointer is data which follows this struct */ unsigned transmit_me :1; /* needs to be transmitted */
/* XXXX This is a horrible hack. */ char **put_cname_in_ptr; /* store the cname here if we get one. */
struct nameserver {
evutil_socket_t socket; /* a connected UDP socket */ struct sockaddr_storage address;
ev_socklen_t addrlen; int failed_times; /* number of times which we have given this server a chance */ int timedout; /* number of times in a row a request has timed out */ struct event event; /* these objects are kept in a circular list */ struct nameserver *next, *prev; struct event timeout_event; /* used to keep the timeout for */ /* when we next probe this server. */ /* Valid if state == 0 */ /* Outstanding probe request for this nameserver, if any */ struct evdns_request *probe_request; char state; /* zero if we think that this server is down */ char choked; /* true if we have an EAGAIN from this server's socket */ char write_waiting; /* true if we are waiting for EV_WRITE events */ struct evdns_base *base;
/* Number of currently inflight requests: used
* to track when we should add/del the event. */ int requests_inflight;
};
/* Represents a local port where we're listening for DNS requests. Right now, */ /* only UDP is supported. */ struct evdns_server_port {
evutil_socket_t socket; /* socket we use to read queries and write replies. */ int refcnt; /* reference count. */ char choked; /* Are we currently blocked from writing? */ char closing; /* Are we trying to close this port, pending writes? */
evdns_request_callback_fn_type user_callback; /* Fn to handle requests */ void *user_data; /* Opaque pointer passed to user_callback */ struct event event; /* Read/write event */ /* circular list of replies that we want to write. */ struct server_request *pending_replies; struct event_base *event_base;
/* Represents part of a reply being built. (That is, a single RR.) */ struct server_reply_item { struct server_reply_item *next; /* next item in sequence. */ char *name; /* name part of the RR */
u16 type; /* The RR type */
u16 class; /* The RR class (usually CLASS_INET) */
u32 ttl; /* The RR TTL */ char is_name; /* True iff data is a label */
u16 datalen; /* Length of data; -1 if data is a label */ void *data; /* The contents of the RR */
};
/* Represents a request that we've received as a DNS server, and holds */ /* the components of the reply as we're constructing it. */ struct server_request { /* Pointers to the next and previous entries on the list of replies */ /* that we're waiting to write. Only set if we have tried to respond */ /* and gotten EAGAIN. */ struct server_request *next_pending; struct server_request *prev_pending;
u16 trans_id; /* Transaction id. */ struct evdns_server_port *port; /* Which port received this request on? */ struct sockaddr_storage addr; /* Where to send the response */
ev_socklen_t addrlen; /* length of addr */
int n_answer; /* how many answer RRs have been set? */ int n_authority; /* how many authority RRs have been set? */ int n_additional; /* how many additional RRs have been set? */
struct server_reply_item *answer; /* linked list of answer RRs */ struct server_reply_item *authority; /* linked list of authority RRs */ struct server_reply_item *additional; /* linked list of additional RRs */
/* Constructed response. Only set once we're ready to send a reply. */ /* Once this is set, the RR fields are cleared, and no more should be set. */ char *response;
size_t response_len;
struct evdns_base { /* An array of n_req_heads circular lists for inflight requests. * Each inflight request req is in req_heads[req->trans_id % n_req_heads].
*/ struct request **req_heads; /* A circular list of requests that we're waiting to send, but haven't
* sent yet because there are too many requests inflight */ struct request *req_waiting_head; /* A circular list of nameservers. */ struct nameserver *server_head; int n_req_heads;
struct event_base *event_base;
/* The number of good nameservers that we have */ int global_good_nameservers;
/* inflight requests are contained in the req_head list */ /* and are actually going out across the network */ int global_requests_inflight; /* requests which aren't inflight are in the waiting list */ /* and are counted here */ int global_requests_waiting;
int global_max_requests_inflight;
struct timeval global_timeout; /* 5 seconds by default */ int global_max_reissues; /* a reissue occurs when we get some errors from the server */ int global_max_retransmits; /* number of times we'll retransmit a request which timed out */ /* number of timeouts in a row before we consider this server to be down */ int global_max_nameserver_timeout; /* true iff we will use the 0x20 hack to prevent poisoning attacks. */ int global_randomize_case;
/* The first time that a nameserver fails, how long do we wait before
* probing to see if it has returned? */ struct timeval global_nameserver_probe_initial_timeout;
/** Port to bind to for outgoing DNS packets. */ struct sockaddr_storage global_outgoing_address; /** ev_socklen_t for global_outgoing_address. 0 if it isn't set. */
ev_socklen_t global_outgoing_addrlen;
struct timeval global_getaddrinfo_allow_skew;
int so_rcvbuf; int so_sndbuf;
int getaddrinfo_ipv4_timeouts; int getaddrinfo_ipv6_timeouts; int getaddrinfo_ipv4_answered; int getaddrinfo_ipv6_answered;
/* Given a pointer to an evdns_server_request, get the corresponding */ /* server_request. */ #define TO_SERVER_REQUEST(base_ptr) \
((struct server_request*) \
(((char*)(base_ptr) - evutil_offsetof(struct server_request, base))))
/* This walks the list of inflight requests to find the */ /* one with a matching transaction id. Returns NULL on */ /* failure */ staticstruct request *
request_find_from_trans_id(struct evdns_base *base, u16 trans_id) { struct request *req = REQ_HEAD(base, trans_id); struct request *const started_at = req;
ASSERT_LOCKED(base);
if (req) { do { if (req->trans_id == trans_id) return req;
req = req->next;
} while (req != started_at);
}
return NULL;
}
/* a libevent callback function which is called when a nameserver */ /* has gone down and we want to test if it has came back to life yet */ staticvoid
nameserver_prod_callback(evutil_socket_t fd, short events, void *arg) { struct nameserver *const ns = (struct nameserver *) arg;
(void)fd;
(void)events;
/* a libevent callback which is called when a nameserver probe (to see if */ /* it has come back to life) times out. We increment the count of failed_times */ /* and wait longer to send the next probe packet. */ staticvoid
nameserver_probe_failed(struct nameserver *const ns) { struct timeval timeout; int i;
ASSERT_LOCKED(ns->base);
(void) evtimer_del(&ns->timeout_event); if (ns->state == 1) { /* This can happen if the nameserver acts in a way which makes us mark */ /* it as bad and then starts sending good replies. */ return;
}
/* called when a nameserver has been deemed to have failed. For example, too */ /* many packets have timed out etc */ staticvoid
nameserver_failed(struct nameserver *const ns, constchar *msg) { struct request *req, *started_at; struct evdns_base *base = ns->base; int i; char addrbuf[128];
ASSERT_LOCKED(base); /* if this nameserver has already been marked as failed */ /* then don't do anything */ if (!ns->state) return;
base->global_good_nameservers--;
EVUTIL_ASSERT(base->global_good_nameservers >= 0); if (base->global_good_nameservers == 0) {
log(EVDNS_LOG_MSG, "All nameservers have failed");
}
ns->state = 0;
ns->failed_times = 1;
if (evtimer_add(&ns->timeout_event,
&base->global_nameserver_probe_initial_timeout) < 0) {
log(EVDNS_LOG_WARN, "Error from libevent when adding timer event for %s",
evutil_format_sockaddr_port_(
(struct sockaddr *)&ns->address,
addrbuf, sizeof(addrbuf))); /* ???? Do more? */
}
/* walk the list of inflight requests to see if any can be reassigned to */ /* a different server. Requests in the waiting queue don't have a */ /* nameserver assigned yet */
/* if we don't have *any* good nameservers then there's no point */ /* trying to reassign requests to one */ if (!base->global_good_nameservers) return;
for (i = 0; i < base->n_req_heads; ++i) {
req = started_at = base->req_heads[i]; if (req) { do { if (req->tx_count == 0 && req->ns == ns) { /* still waiting to go out, can be moved */ /* to another server */
request_swap_ns(req, nameserver_pick(base));
}
req = req->next;
} while (req != started_at);
}
}
}
/* Called to remove a request from a list and dealloc it. */ /* head is a pointer to the head of the list it should be */ /* removed from or NULL if the request isn't in a list. */ /* when free_handle is one, free the handle as well. */ staticvoid
request_finished(struct request *const req, struct request **head, int free_handle) { struct evdns_base *base = req->base; int was_inflight = (head != &base->req_waiting_head);
EVDNS_LOCK(base);
ASSERT_VALID_REQUEST(req);
if (head)
evdns_request_remove(req, head);
log(EVDNS_LOG_DEBUG, "Removing timeout for request %p", req); if (was_inflight) {
evtimer_del(&req->timeout_event);
base->global_requests_inflight--;
req->ns->requests_inflight--;
} else {
base->global_requests_waiting--;
} /* it was initialized during request_new / evtimer_assign */
event_debug_unassign(&req->timeout_event);
if (!req->request_appended) { /* need to free the request data on it's own */
mm_free(req->request);
} else { /* the request data is appended onto the header */ /* so everything gets free()ed when we: */
}
if (req->handle) {
EVUTIL_ASSERT(req->handle->current_req == req);
if (free_handle) {
search_request_finished(req->handle);
req->handle->current_req = NULL; if (! req->handle->pending_cb) { /* If we're planning to run the callback,
* don't free the handle until later. */
mm_free(req->handle);
}
req->handle = NULL; /* If we have a bug, let's crash
* early */
} else {
req->handle->current_req = NULL;
}
}
/* This is called when a server returns a funny error code. */ /* We try the request again with another server. */ /* */ /* return: */ /* 0 ok */ /* 1 failed/reissue is pointless */ staticint
request_reissue(struct request *req) { conststruct nameserver *const last_ns = req->ns;
ASSERT_LOCKED(req->base);
ASSERT_VALID_REQUEST(req); /* the last nameserver should have been marked as failing */ /* by the caller of this function, therefore pick will try */ /* not to return it */
request_swap_ns(req, nameserver_pick(req->base)); if (req->ns == last_ns) { /* ... but pick did return it */ /* not a lot of point in trying again with the */ /* same server */ return 1;
}
/* this function looks for space on the inflight queue and promotes */ /* requests from the waiting queue if it can. */ /* */ /* TODO: */ /* add return code, see at nameserver_pick() and other functions. */ staticvoid
evdns_requests_pump_waiting_queue(struct evdns_base *base) {
ASSERT_LOCKED(base); while (base->global_requests_inflight < base->global_max_requests_inflight &&
base->global_requests_waiting) { struct request *req;
switch (error) { case DNS_ERR_NOTIMPL: case DNS_ERR_REFUSED: /* we regard these errors as marking a bad nameserver */ if (req->reissue_count < req->base->global_max_reissues) { char msg[64];
evutil_snprintf(msg, sizeof(msg), "Bad response %d (%s)",
error, evdns_err_to_string(error));
nameserver_failed(req->ns, msg); if (!request_reissue(req)) return;
} break; case DNS_ERR_SERVERFAILED: /* rcode 2 (servfailed) sometimes means "we * are broken" and sometimes (with some binds) * means "that request was very confusing." * Treat this as a timeout, not a failure.
*/
log(EVDNS_LOG_DEBUG, "Got a SERVERFAILED from nameserver" "at %s; will allow the request to time out.",
evutil_format_sockaddr_port_(
(struct sockaddr *)&req->ns->address,
addrbuf, sizeof(addrbuf))); /* Call the timeout function */
evdns_request_timeout_callback(0, 0, req); return; default: /* we got a good reply from the nameserver: it is up. */ if (req->handle == req->ns->probe_request) { /* Avoid double-free */
req->ns->probe_request = NULL;
}
nameserver_up(req->ns);
}
if (req->handle->search_state &&
req->request_type != TYPE_PTR) { /* if we have a list of domains to search in,
* try the next one */ if (!search_try_next(req->handle)) { /* a new request was issued so this
* request is finished and */ /* the user callback will be made when
* that request (or a */ /* child of it) finishes. */ return;
}
}
/* all else failed. Pass the failure up */
reply_schedule_callback(req, ttl, error, NULL);
request_finished(req, &REQ_HEAD(req->base, req->trans_id), 1);
} else { /* all ok, tell the user */
reply_schedule_callback(req, ttl, 0, reply); if (req->handle == req->ns->probe_request)
req->ns->probe_request = NULL; /* Avoid double-free */
nameserver_up(req->ns);
request_finished(req, &REQ_HEAD(req->base, req->trans_id), 1);
}
}
staticint
name_parse(u8 *packet, int length, int *idx, char *name_out, int name_out_len) { int name_end = -1; int j = *idx; int ptr_count = 0; #define GET32(x) do { if (j + 4 > length) goto err; memcpy(&t32_, packet + j, 4); j += 4; x = ntohl(t32_); } while (0) #define GET16(x) do { if (j + 2 > length) goto err; memcpy(&t_, packet + j, 2); j += 2; x = ntohs(t_); } while (0) #define GET8(x) do { if (j >= length) goto err; x = packet[j++]; } while (0)
/* Normally, names are a series of length prefixed strings terminated */ /* with a length of 0 (the lengths are u8's < 63). */ /* However, the length can start with a pair of 1 bits and that */ /* means that the next 14 bits are a pointer within the current */ /* packet. */
for (;;) {
u8 label_len;
GET8(label_len); if (!label_len) break; if (label_len & 0xc0) {
u8 ptr_low;
GET8(ptr_low); if (name_end < 0) name_end = j;
j = (((int)label_len & 0x3f) << 8) + ptr_low; /* Make sure that the target offset is in-bounds. */ if (j < 0 || j >= length) return -1; /* If we've jumped more times than there are characters in the
* message, we must have a loop. */ if (++ptr_count > length) return -1; continue;
} if (label_len > 63) return -1; if (cp != name_out) { if (cp + 1 >= end) return -1;
*cp++ = '.';
} if (cp + label_len >= end) return -1; if (j + label_len > length) return -1;
memcpy(cp, packet + j, label_len);
cp += label_len;
j += label_len;
} if (cp >= end) return -1;
*cp = '\0'; if (name_end < 0)
*idx = j; else
*idx = name_end; return 0;
err: return -1;
}
/* parses a raw request from a nameserver */ staticint
reply_parse(struct evdns_base *base, u8 *packet, int length) { int j = 0, k = 0; /* index into packet */
u16 t_; /* used by the macros */
u32 t32_; /* used by the macros */ char tmp_name[256], cmp_name[256]; /* used by the macros */ int name_matches = 0;
/* If it's not an answer, it doesn't correspond to any request. */ if (!(flags & _QR_MASK)) return -1; /* must be an answer */ if ((flags & (_RCODE_MASK|_TC_MASK)) && (flags & (_RCODE_MASK|_TC_MASK)) != DNS_ERR_NOTEXIST) { /* there was an error and it's not NXDOMAIN */ goto err;
} /* if (!answers) return; */ /* must have an answer of some form */
/* This macro skips a name in the DNS reply. */ #define SKIP_NAME \ do { tmp_name[0] = '\0'; \ if (name_parse(packet, length, &j, tmp_name, \ sizeof(tmp_name))<0) \ goto err; \
} while (0)
reply.type = req->request_type;
/* skip over each question in the reply */ for (i = 0; i < questions; ++i) { /* the question looks like * <label:name><u16:type><u16:class>
*/
tmp_name[0] = '\0';
cmp_name[0] = '\0';
k = j; if (name_parse(packet, length, &j, tmp_name, sizeof(tmp_name)) < 0) goto err; if (name_parse(req->request, req->request_len, &k,
cmp_name, sizeof(cmp_name))<0) goto err; if (!base->global_randomize_case) { if (strcmp(tmp_name, cmp_name) == 0)
name_matches = 1;
} else { if (evutil_ascii_strcasecmp(tmp_name, cmp_name) == 0)
name_matches = 1;
}
j += 4; if (j > length) goto err;
}
if (!name_matches) goto err;
/* now we have the answer section which looks like * <label:name><u16:type><u16:class><u32:ttl><u16:len><data...>
*/
if (type == TYPE_A && class == CLASS_INET) { int addrcount, addrtocopy; if (req->request_type != TYPE_A) {
j += datalength; continue;
} if ((datalength & 3) != 0) /* not an even number of As. */ goto err;
addrcount = datalength >> 2;
addrtocopy = MIN(MAX_V4_ADDRS - reply.data.a.addrcount, (unsigned)addrcount);
ttl_r = MIN(ttl_r, ttl); /* we only bother with the first four addresses. */ if (j + 4*addrtocopy > length) goto err;
memcpy(&reply.data.a.addresses[reply.data.a.addrcount],
packet + j, 4*addrtocopy);
j += 4*addrtocopy;
reply.data.a.addrcount += addrtocopy;
reply.have_answer = 1; if (reply.data.a.addrcount == MAX_V4_ADDRS) break;
} elseif (type == TYPE_PTR && class == CLASS_INET) { if (req->request_type != TYPE_PTR) {
j += datalength; continue;
} if (name_parse(packet, length, &j, reply.data.ptr.name, sizeof(reply.data.ptr.name))<0) goto err;
ttl_r = MIN(ttl_r, ttl);
reply.have_answer = 1; break;
} elseif (type == TYPE_CNAME) { char cname[HOST_NAME_MAX]; if (!req->put_cname_in_ptr || *req->put_cname_in_ptr) {
j += datalength; continue;
} if (name_parse(packet, length, &j, cname, sizeof(cname))<0) goto err;
*req->put_cname_in_ptr = mm_strdup(cname);
} elseif (type == TYPE_AAAA && class == CLASS_INET) { int addrcount, addrtocopy; if (req->request_type != TYPE_AAAA) {
j += datalength; continue;
} if ((datalength & 15) != 0) /* not an even number of AAAAs. */ goto err;
addrcount = datalength >> 4; /* each address is 16 bytes long */
addrtocopy = MIN(MAX_V6_ADDRS - reply.data.aaaa.addrcount, (unsigned)addrcount);
ttl_r = MIN(ttl_r, ttl);
/* we only bother with the first four addresses. */ if (j + 16*addrtocopy > length) goto err;
memcpy(&reply.data.aaaa.addresses[reply.data.aaaa.addrcount],
packet + j, 16*addrtocopy);
reply.data.aaaa.addrcount += addrtocopy;
j += 16*addrtocopy;
reply.have_answer = 1; if (reply.data.aaaa.addrcount == MAX_V6_ADDRS) break;
} else { /* skip over any other type of resource */
j += datalength;
}
}
if (!reply.have_answer) { for (i = 0; i < authority; ++i) {
u16 type, class;
SKIP_NAME;
GET16(type);
GET16(class);
GET32(ttl);
GET16(datalength); if (type == TYPE_SOA && class == CLASS_INET) {
u32 serial, refresh, retry, expire, minimum;
SKIP_NAME;
SKIP_NAME;
GET32(serial);
GET32(refresh);
GET32(retry);
GET32(expire);
GET32(minimum);
(void)expire;
(void)retry;
(void)refresh;
(void)serial;
ttl_r = MIN(ttl_r, ttl);
ttl_r = MIN(ttl_r, minimum);
} else { /* skip over any other type of resource */
j += datalength;
}
}
}
/* Parse a raw request (packet,length) sent to a nameserver port (port) from */ /* a DNS client (addr,addrlen), and if it's well-formed, call the corresponding */ /* callback. */ staticint
request_parse(u8 *packet, int length, struct evdns_server_port *port, struct sockaddr *addr, ev_socklen_t addrlen)
{ int j = 0; /* index into packet */
u16 t_; /* used by the macros */ char tmp_name[256]; /* used by the macros */
return 0;
err: if (server_req->base.questions) { for (i = 0; i < server_req->base.nquestions; ++i)
mm_free(server_req->base.questions[i]);
mm_free(server_req->base.questions);
}
mm_free(server_req); return -1;
/* Try to choose a strong transaction id which isn't already in flight */ static u16
transaction_id_pick(struct evdns_base *base) {
ASSERT_LOCKED(base); for (;;) {
u16 trans_id;
evutil_secure_rng_get_bytes(&trans_id, sizeof(trans_id));
if (trans_id == 0xffff) continue; /* now check to see if that id is already inflight */ if (request_find_from_trans_id(base, trans_id) == NULL) return trans_id;
}
}
/* choose a namesever to use. This function will try to ignore */ /* nameservers which we think are down and load balance across the rest */ /* by updating the server_head global each time. */ staticstruct nameserver *
nameserver_pick(struct evdns_base *base) { struct nameserver *started_at = base->server_head, *picked;
ASSERT_LOCKED(base); if (!base->server_head) return NULL;
/* if we don't have any good nameservers then there's no */ /* point in trying to find one. */ if (!base->global_good_nameservers) {
base->server_head = base->server_head->next; return base->server_head;
}
/* remember that nameservers are in a circular list */ for (;;) { if (base->server_head->state) { /* we think this server is currently good */
picked = base->server_head;
base->server_head = base->server_head->next; return picked;
}
base->server_head = base->server_head->next; if (base->server_head == started_at) { /* all the nameservers seem to be down */ /* so we just return this one and hope for the */ /* best */
EVUTIL_ASSERT(base->global_good_nameservers == 0);
picked = base->server_head;
base->server_head = base->server_head->next; return picked;
}
}
}
/* this is called when a namesever socket is ready for reading */ staticvoid
nameserver_read(struct nameserver *ns) { struct sockaddr_storage ss;
ev_socklen_t addrlen = sizeof(ss);
u8 packet[1500]; char addrbuf[128];
ASSERT_LOCKED(ns->base);
for (;;) { constint r = recvfrom(ns->socket, (void*)packet, sizeof(packet), 0,
(struct sockaddr*)&ss, &addrlen); if (r < 0) { int err = evutil_socket_geterror(ns->socket); if (EVUTIL_ERR_RW_RETRIABLE(err)) return;
nameserver_failed(ns,
evutil_socket_error_to_string(err)); return;
} if (evutil_sockaddr_cmp((struct sockaddr*)&ss,
(struct sockaddr*)&ns->address, 0)) {
log(EVDNS_LOG_WARN, "Address mismatch on received " "DNS packet. Apparent source was %s",
evutil_format_sockaddr_port_(
(struct sockaddr *)&ss,
addrbuf, sizeof(addrbuf))); return;
}
/* Read a packet from a DNS client on a server port s, parse it, and */ /* act accordingly. */ staticvoid
server_port_read(struct evdns_server_port *s) {
u8 packet[1500]; struct sockaddr_storage addr;
ev_socklen_t addrlen; int r;
ASSERT_LOCKED(s);
for (;;) {
addrlen = sizeof(struct sockaddr_storage);
r = recvfrom(s->socket, (void*)packet, sizeof(packet), 0,
(struct sockaddr*) &addr, &addrlen); if (r < 0) { int err = evutil_socket_geterror(s->socket); if (EVUTIL_ERR_RW_RETRIABLE(err)) return;
log(EVDNS_LOG_WARN, "Error %s (%d) while reading request.",
evutil_socket_error_to_string(err), err); return;
}
request_parse(packet, r, s, (struct sockaddr*) &addr, addrlen);
}
}
/* Try to write all pending replies on a given DNS server port. */ staticvoid
server_port_flush(struct evdns_server_port *port)
{ struct server_request *req = port->pending_replies;
ASSERT_LOCKED(port); while (req) { int r = sendto(port->socket, req->response, (int)req->response_len, 0,
(struct sockaddr*) &req->addr, (ev_socklen_t)req->addrlen); if (r < 0) { int err = evutil_socket_geterror(port->socket); if (EVUTIL_ERR_RW_RETRIABLE(err)) return;
log(EVDNS_LOG_WARN, "Error %s (%d) while writing response to port; dropping", evutil_socket_error_to_string(err), err);
} if (server_request_free(req)) { /* we released the last reference to req->port. */ return;
} else {
EVUTIL_ASSERT(req != port->pending_replies);
req = port->pending_replies;
}
}
/* We have no more pending requests; stop listening for 'writeable' events. */
(void) event_del(&port->event);
event_assign(&port->event, port->event_base,
port->socket, EV_READ | EV_PERSIST,
server_port_ready_callback, port);
if (event_add(&port->event, NULL) < 0) {
log(EVDNS_LOG_WARN, "Error from libevent when adding event for DNS server."); /* ???? Do more? */
}
}
/* set if we are waiting for the ability to write to this server. */ /* if waiting is true then we ask libevent for EV_WRITE events, otherwise */ /* we stop these events. */ staticvoid
nameserver_write_waiting(struct nameserver *ns, char waiting) {
ASSERT_LOCKED(ns->base); if (ns->write_waiting == waiting) return;
ns->write_waiting = waiting;
(void) event_del(&ns->event);
event_assign(&ns->event, ns->base->event_base,
ns->socket, EV_READ | (waiting ? EV_WRITE : 0) | EV_PERSIST,
nameserver_ready_callback, ns); if (event_add(&ns->event, NULL) < 0) { char addrbuf[128];
log(EVDNS_LOG_WARN, "Error from libevent when adding event for %s",
evutil_format_sockaddr_port_(
(struct sockaddr *)&ns->address,
addrbuf, sizeof(addrbuf))); /* ???? Do more? */
}
}
/* a callback function. Called by libevent when the kernel says that */ /* a nameserver socket is ready for writing or reading */ staticvoid
nameserver_ready_callback(evutil_socket_t fd, short events, void *arg) { struct nameserver *ns = (struct nameserver *) arg;
(void)fd;
EVDNS_LOCK(ns->base); if (events & EV_WRITE) {
ns->choked = 0; if (!evdns_transmit(ns->base)) {
nameserver_write_waiting(ns, 0);
}
} if (events & EV_READ) {
nameserver_read(ns);
}
EVDNS_UNLOCK(ns->base);
}
/* a callback function. Called by libevent when the kernel says that */ /* a server socket is ready for writing or reading. */ staticvoid
server_port_ready_callback(evutil_socket_t fd, short events, void *arg) { struct evdns_server_port *port = (struct evdns_server_port *) arg;
(void) fd;
EVDNS_LOCK(port); if (events & EV_WRITE) {
port->choked = 0;
server_port_flush(port);
} if (events & EV_READ) {
server_port_read(port);
}
EVDNS_UNLOCK(port);
}
/* This is an inefficient representation; only use it via the dnslabel_table_*
* functions, so that is can be safely replaced with something smarter later. */ #define MAX_LABELS 128 /* Structures used to implement name compression */ struct dnslabel_entry { char *v; off_t pos; }; struct dnslabel_table { int n_labels; /* number of current entries */ /* map from name to position in message */ struct dnslabel_entry labels[MAX_LABELS];
};
/* Free all storage held by table, but not the table itself. */ staticvoid
dnslabel_clear(struct dnslabel_table *table)
{ int i; for (i = 0; i < table->n_labels; ++i)
mm_free(table->labels[i].v);
table->n_labels = 0;
}
/* return the position of the label in the current message, or -1 if the label */ /* hasn't been used yet. */ staticint
dnslabel_table_get_pos(conststruct dnslabel_table *table, constchar *label)
{ int i; for (i = 0; i < table->n_labels; ++i) { if (!strcmp(label, table->labels[i].v)) return table->labels[i].pos;
} return -1;
}
/* remember that we've used the label at position pos */ staticint
dnslabel_table_add(struct dnslabel_table *table, constchar *label, off_t pos)
{ char *v; int p; if (table->n_labels == MAX_LABELS) return (-1);
v = mm_strdup(label); if (v == NULL) return (-1);
p = table->n_labels++;
table->labels[p].v = v;
table->labels[p].pos = pos;
return (0);
}
/* Converts a string to a length-prefixed set of DNS labels, starting */ /* at buf[j]. name and buf must not overlap. name_len should be the length */ /* of name. table is optional, and is used for compression. */ /* */ /* Input: abc.def */ /* Output: <3>abc<3>def<0> */ /* */ /* Returns the first index after the encoded name, or negative on error. */ /* -1 label was > 63 bytes */ /* -2 name too long to fit in buffer. */ /* */ static off_t
dnsname_to_labels(u8 *const buf, size_t buf_len, off_t j, constchar *name, const size_t name_len, struct dnslabel_table *table) { constchar *end = name + name_len; int ref = 0;
u16 t_;
for (;;) { constchar *const start = name; if (table && (ref = dnslabel_table_get_pos(table, name)) >= 0) {
APPEND16(ref | 0xc000); return j;
}
name = strchr(name, '.'); if (!name) { const size_t label_len = end - start; if (label_len > 63) return -1; if ((size_t)(j+label_len+1) > buf_len) return -2; if (table) dnslabel_table_add(table, start, j);
buf[j++] = (ev_uint8_t)label_len;
memcpy(buf + j, start, label_len);
j += (int) label_len; break;
} else { /* append length of the label. */ const size_t label_len = name - start; if (label_len > 63) return -1; if ((size_t)(j+label_len+1) > buf_len) return -2; if (table) dnslabel_table_add(table, start, j);
buf[j++] = (ev_uint8_t)label_len;
memcpy(buf + j, start, label_len);
j += (int) label_len; /* hop over the '.' */
name++;
}
}
/* the labels must be terminated by a 0. */ /* It's possible that the name ended in a . */ /* in which case the zero is already there */ if (!j || buf[j-1]) buf[j++] = 0; return j;
overflow: return (-2);
}
/* Finds the length of a dns request for a DNS name of the given */ /* length. The actual request may be smaller than the value returned */ /* here */ static size_t
evdns_request_len(const size_t name_len) { return 96 + /* length of the DNS standard header */
name_len + 2 +
4; /* space for the resource type */
}
/* build a dns request packet into buf. buf should be at least as long */ /* as evdns_request_len told you it should be. */ /* */ /* Returns the amount of space used. Negative on error. */ staticint
evdns_request_data_build(constchar *const name, const size_t name_len, const u16 trans_id, const u16 type, const u16 class,
u8 *const buf, size_t buf_len) {
off_t j = 0; /* current offset into buf */
u16 t_; /* used by the macros */
APPEND16(trans_id);
APPEND16(0x0100); /* standard query, recusion needed */
APPEND16(1); /* one question */
APPEND16(0); /* no answers */
APPEND16(0); /* no authority */
APPEND16(0); /* no additional */
/* exported function */ int
evdns_server_request_add_reply(struct evdns_server_request *req_, int section, constchar *name, int type, intclass, int ttl, int datalen, int is_name, constchar *data)
{ struct server_request *req = TO_SERVER_REQUEST(req_); struct server_reply_item **itemp, *item; int *countp; int result = -1;
EVDNS_LOCK(req->port); if (req->response) /* have we already answered? */ goto done;
/* Set response bit and error code; copy OPCODE and RD fields from
* question; copy RA and AA if set by caller. */
flags = req->base.flags;
flags |= (_QR_MASK | err);
/* exported function */ int
evdns_server_request_respond(struct evdns_server_request *req_, int err)
{ struct server_request *req = TO_SERVER_REQUEST(req_); struct evdns_server_port *port = req->port; int r = -1;
EVDNS_LOCK(port); if (!req->response) { if ((r = evdns_server_request_format_response(req, err))<0) goto done;
}
r = sendto(port->socket, req->response, (int)req->response_len, 0,
(struct sockaddr*) &req->addr, (ev_socklen_t)req->addrlen); if (r<0) { int sock_err = evutil_socket_geterror(port->socket); if (EVUTIL_ERR_RW_RETRIABLE(sock_err)) goto done;
if (event_add(&port->event, NULL) < 0) {
log(EVDNS_LOG_WARN, "Error from libevent when adding event for DNS server");
}
}
r = 1; goto done;
} if (server_request_free(req)) {
r = 0; goto done;
}
if (port->pending_replies)
server_port_flush(port);
r = 0;
done:
EVDNS_UNLOCK(port); return r;
}
/* Free all storage held by RRs in req. */ staticvoid
server_request_free_answers(struct server_request *req)
{ struct server_reply_item *victim, *next, **list; int i; for (i = 0; i < 3; ++i) { if (i==0)
list = &req->answer; elseif (i==1)
list = &req->authority; else
list = &req->additional;
victim = *list; while (victim) {
next = victim->next;
mm_free(victim->name); if (victim->data)
mm_free(victim->data);
mm_free(victim);
victim = next;
}
*list = NULL;
}
}
/* Free all storage held by req, and remove links to it. */ /* return true iff we just wound up freeing the server_port. */ staticint
server_request_free(struct server_request *req)
{ int i, rc=1, lock=0; if (req->base.questions) { for (i = 0; i < req->base.nquestions; ++i)
mm_free(req->base.questions[i]);
mm_free(req->base.questions);
}
if (req->port) {
EVDNS_LOCK(req->port);
lock=1; if (req->port->pending_replies == req) { if (req->next_pending && req->next_pending != req)
req->port->pending_replies = req->next_pending; else
req->port->pending_replies = NULL;
}
rc = --req->port->refcnt;
}
/* Free all storage held by an evdns_server_port. Only called when */ staticvoid
server_port_free(struct evdns_server_port *port)
{
EVUTIL_ASSERT(port);
EVUTIL_ASSERT(!port->refcnt);
EVUTIL_ASSERT(!port->pending_replies); if (port->socket > 0) {
evutil_closesocket(port->socket);
port->socket = -1;
}
(void) event_del(&port->event);
event_debug_unassign(&port->event);
EVTHREAD_FREE_LOCK(port->lock, EVTHREAD_LOCKTYPE_RECURSIVE);
mm_free(port);
}
/* exported function */ int
evdns_server_request_drop(struct evdns_server_request *req_)
{ struct server_request *req = TO_SERVER_REQUEST(req_);
server_request_free(req); return 0;
}
/* exported function */ int
evdns_server_request_get_requesting_addr(struct evdns_server_request *req_, struct sockaddr *sa, int addr_len)
{ struct server_request *req = TO_SERVER_REQUEST(req_); if (addr_len < (int)req->addrlen) return -1;
memcpy(sa, &(req->addr), req->addrlen); return req->addrlen;
}
#undef APPEND16 #undef APPEND32
/* this is a libevent callback function which is called when a request */ /* has timed out. */ staticvoid
evdns_request_timeout_callback(evutil_socket_t fd, short events, void *arg) { struct request *const req = (struct request *) arg; struct evdns_base *base = req->base;
r = sendto(server->socket, (void*)req->request, req->request_len, 0,
(struct sockaddr *)&server->address, server->addrlen); if (r < 0) { int err = evutil_socket_geterror(server->socket); if (EVUTIL_ERR_RW_RETRIABLE(err)) return 1;
nameserver_failed(req->ns, evutil_socket_error_to_string(err)); return 2;
} elseif (r != (int)req->request_len) { return 1; /* short write */
} else { return 0;
}
}
/* try to send a request, updating the fields of the request */ /* as needed */ /* */ /* return: */ /* 0 ok */ /* 1 failed */ staticint
evdns_request_transmit(struct request *req) { int retcode = 0, r;
ASSERT_LOCKED(req->base);
ASSERT_VALID_REQUEST(req); /* if we fail to send this packet then this flag marks it */ /* for evdns_transmit */
req->transmit_me = 1;
EVUTIL_ASSERT(req->trans_id != 0xffff);
if (!req->ns)
{ /* unable to transmit request if no nameservers */ return 1;
}
if (req->ns->choked) { /* don't bother trying to write to a socket */ /* which we have had EAGAIN from */ return 1;
}
r = evdns_request_transmit_to(req, req->ns); switch (r) { case 1: /* temp failure */
req->ns->choked = 1;
nameserver_write_waiting(req->ns, 1); return 1; case 2: /* failed to transmit the request entirely. we can fallthrough since * we'll set a timeout, which will time out, and make us retransmit the
* request anyway. */
retcode = 1;
EVUTIL_FALLTHROUGH; default: /* all ok */
log(EVDNS_LOG_DEBUG, "Setting timeout for request %p, sent to nameserver %p", req, req->ns); if (evtimer_add(&req->timeout_event, &req->base->global_timeout) < 0) {
log(EVDNS_LOG_WARN, "Error from libevent when adding timer for request %p",
req); /* ???? Do more? */
}
req->tx_count++;
req->transmit_me = 0; return retcode;
}
}
if (result == DNS_ERR_CANCEL) { /* We canceled this request because the nameserver came up * for some other reason. Do not change our opinion about
* the nameserver. */ return;
}
EVDNS_LOCK(ns->base);
ns->probe_request = NULL; if (result == DNS_ERR_NONE || result == DNS_ERR_NOTEXIST) { /* this is a good reply */
nameserver_up(ns);
} else {
nameserver_probe_failed(ns);
}
EVDNS_UNLOCK(ns->base);
}
staticvoid
nameserver_send_probe(struct nameserver *const ns) { struct evdns_request *handle; struct request *req; char addrbuf[128]; /* here we need to send a probe to a given nameserver */ /* in the hope that it is up now. */
ASSERT_LOCKED(ns->base);
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5
¤ Dauer der Verarbeitung: 0.27 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.