Move serving requests out of getdns_query

This commit is contained in:
Willem Toorop 2016-06-21 16:14:54 +02:00
parent 09b4ef9e9c
commit 3cc369a27d
4 changed files with 693 additions and 452 deletions

View File

@ -66,7 +66,9 @@ CHECK_CFLAGS=@CHECK_CFLAGS@
CHECK_OBJS=check_getdns_common.lo check_getdns_context_set_timeout.lo \
check_getdns.lo check_getdns_transport.lo
DECOMPOSED_OBJS_WITHOUT_JSMN=getdns_str2dict.lo getdns_context_config.lo
DECOMPOSED_OBJS_WITHOUT_JSMN=getdns_str2dict.lo getdns_context_config.lo \
getdns_context_set_listen_addresses.lo
DECOMPOSED_OBJS=$(DECOMPOSED_OBJS_WITHOUT_JSMN) jsmn.lo
ALL_OBJS=$(CHECK_OBJS) check_getdns_libevent.lo check_getdns_libev.lo \
@ -273,9 +275,14 @@ check_getdns_transport.lo check_getdns_transport.o: $(srcdir)/check_getdns_trans
../getdns/getdns_extra.h
getdns_context_config.lo getdns_context_config.o: $(srcdir)/getdns_context_config.c $(srcdir)/getdns_context_config.h \
../getdns/getdns.h ../getdns/getdns_extra.h
getdns_context_set_listen_addresses.lo getdns_context_set_listen_addresses.o: \
$(srcdir)/getdns_context_set_listen_addresses.c ../config.h \
$(srcdir)/getdns_context_set_listen_addresses.h ../getdns/getdns.h \
../getdns/getdns_extra.h $(srcdir)/../types-internal.h ../getdns/getdns.h \
../getdns/getdns_extra.h $(srcdir)/../util/rbtree.h
getdns_query.lo getdns_query.o: $(srcdir)/getdns_query.c ../config.h $(srcdir)/../debug.h ../config.h \
$(srcdir)/getdns_str2dict.h ../getdns/getdns.h $(srcdir)/getdns_context_config.h \
../getdns/getdns_extra.h
$(srcdir)/getdns_context_set_listen_addresses.h ../getdns/getdns_extra.h
getdns_str2dict.lo getdns_str2dict.o: $(srcdir)/getdns_str2dict.c ../config.h $(srcdir)/../const-info.h \
$(srcdir)/jsmn/jsmn.h $(srcdir)/getdns_str2dict.h ../getdns/getdns.h $(srcdir)/../types-internal.h \
../getdns/getdns.h ../getdns/getdns_extra.h $(srcdir)/../util/rbtree.h $(srcdir)/../list.h \

View File

@ -0,0 +1,590 @@
/*
* Copyright (c) 2013, NLNet Labs, Verisign, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the names of the copyright holders nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 Verisign, Inc. 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.
*/
#include "config.h"
#include "getdns_context_set_listen_addresses.h"
#include "getdns/getdns_extra.h"
#include "types-internal.h"
#include <netdb.h>
#define DNS_REQUEST_SZ 4096
#define DOWNSTREAM_IDLE_TIMEOUT 5000
#define TCP_LISTEN_BACKLOG 16
typedef struct listen_data {
getdns_eventloop_event event;
socklen_t addr_len;
struct sockaddr_storage addr;
int fd;
getdns_transport_list_t transport;
getdns_context *context;
/* Should be per context eventually */
getdns_request_handler_t handler;
} listen_data;
typedef struct dns_msg {
listen_data *ld;
} dns_msg;
typedef struct udp_msg {
listen_data *ld;
struct sockaddr_storage remote_in;
socklen_t addrlen;
} udp_msg;
typedef struct tcp_to_write tcp_to_write;
struct tcp_to_write {
size_t write_buf_len;
size_t written;
tcp_to_write *next;
uint8_t write_buf[];
};
typedef struct downstream {
listen_data *ld;
struct sockaddr_storage remote_in;
socklen_t addrlen;
int fd;
getdns_eventloop_event event;
uint8_t *read_buf;
size_t read_buf_len;
uint8_t *read_pos;
size_t to_read;
tcp_to_write *to_write;
size_t to_answer;
} downstream;
typedef struct tcp_msg {
listen_data *ld;
downstream *conn;
} tcp_msg;
static void downstream_destroy(downstream *conn)
{
struct mem_funcs *mf;
getdns_eventloop *loop;
tcp_to_write *cur, *next;
if (!(mf = priv_getdns_context_mf(conn->ld->context)))
return;
if (getdns_context_get_eventloop(conn->ld->context, &loop))
return;
if (conn->event.read_cb||conn->event.write_cb||conn->event.timeout_cb)
loop->vmt->clear(loop, &conn->event);
if (conn->fd >= 0) {
if (close(conn->fd) == -1)
; /* Whatever */
}
GETDNS_FREE(*mf, conn->read_buf);
for (cur = conn->to_write; cur; cur = next) {
next = cur->next;
GETDNS_FREE(*mf, cur);
}
GETDNS_FREE(*mf, conn);
}
static void tcp_write_cb(void *userarg)
{
downstream *conn = (downstream *)userarg;
struct mem_funcs *mf;
getdns_eventloop *loop;
tcp_to_write *to_write;
ssize_t written;
assert(userarg);
if (!(mf = priv_getdns_context_mf(conn->ld->context)))
return;
if (getdns_context_get_eventloop(conn->ld->context, &loop))
return;
/* Reset downstream idle timeout */
loop->vmt->clear(loop, &conn->event);
if (!conn->to_write) {
conn->event.write_cb = NULL;
(void) loop->vmt->schedule(loop, conn->fd,
DOWNSTREAM_IDLE_TIMEOUT, &conn->event);
return;
}
to_write = conn->to_write;
if ((written = write(conn->fd, &to_write->write_buf[to_write->written],
to_write->write_buf_len - to_write->written)) == -1) {
/* IO error, close connection */
conn->event.read_cb = conn->event.write_cb =
conn->event.timeout_cb = NULL;
downstream_destroy(conn);
return;
}
to_write->written += written;
if (to_write->written == to_write->write_buf_len) {
conn->to_write = to_write->next;
GETDNS_FREE(*mf, to_write);
}
if (!conn->to_write)
conn->event.write_cb = NULL;
(void) loop->vmt->schedule(loop, conn->fd,
DOWNSTREAM_IDLE_TIMEOUT, &conn->event);
}
void
_getdns_cancel_reply(getdns_context *context, getdns_transaction_t request_id)
{
/* TODO: Check request_id at context->outbound_requests */
dns_msg *msg = (dns_msg *)(intptr_t)request_id;
struct mem_funcs *mf;
if (context && msg &&
(mf = priv_getdns_context_mf(msg->ld->context)))
GETDNS_FREE(*mf, msg);
}
getdns_return_t
getdns_reply(
getdns_context *context, getdns_transaction_t request_id, getdns_dict *reply)
{
/* TODO: Check request_id at context->outbound_requests */
dns_msg *msg = (dns_msg *)(intptr_t)request_id;
struct mem_funcs *mf;
getdns_eventloop *loop;
uint8_t buf[65536];
size_t len;
getdns_return_t r;
if (!context || !reply || !msg)
return GETDNS_RETURN_INVALID_PARAMETER;
if (!(mf = priv_getdns_context_mf(msg->ld->context)))
return GETDNS_RETURN_GENERIC_ERROR;;
if ((r = getdns_context_get_eventloop(msg->ld->context, &loop)))
return r;
len = sizeof(buf);
if ((r = getdns_msg_dict2wire_buf(reply, buf, &len)))
return r;
else if (msg->ld->transport == GETDNS_TRANSPORT_UDP) {
udp_msg *msg = (udp_msg *)(intptr_t)request_id;
if (sendto(msg->ld->fd, buf, len, 0,
(struct sockaddr *)&msg->remote_in, msg->addrlen) == -1)
; /* IO error, TODO: cleanup this listener */
} else if (msg->ld->transport == GETDNS_TRANSPORT_TCP) {
tcp_msg *msg = (tcp_msg *)(intptr_t)request_id;
tcp_to_write **to_write_p;
tcp_to_write *to_write = (tcp_to_write *)GETDNS_XMALLOC(
*mf, uint8_t, sizeof(tcp_to_write) + len + 2);
if (!to_write)
return GETDNS_RETURN_MEMORY_ERROR;
to_write->write_buf_len = len + 2;
to_write->write_buf[0] = (len >> 8) & 0xFF;
to_write->write_buf[1] = len & 0xFF;
to_write->written = 0;
to_write->next = NULL;
(void) memcpy(to_write->write_buf + 2, buf, len);
/* Appen to_write to conn->to_write list */
for ( to_write_p = &msg->conn->to_write
; *to_write_p
; to_write_p = &(*to_write_p)->next)
; /* pass */
*to_write_p = to_write;
loop->vmt->clear(loop, &msg->conn->event);
msg->conn->event.write_cb = tcp_write_cb;
(void) loop->vmt->schedule(loop,
msg->conn->fd, DOWNSTREAM_IDLE_TIMEOUT,
&msg->conn->event);
}
/* TODO: other transport types */
if (msg)
GETDNS_FREE(*mf, msg);
return r;
}
static void tcp_read_cb(void *userarg)
{
downstream *conn = (downstream *)userarg;
ssize_t bytes_read;
tcp_msg *msg;
getdns_return_t r;
struct mem_funcs *mf;
getdns_eventloop *loop;
getdns_dict *request_dict;
assert(userarg);
if (!(mf = priv_getdns_context_mf(conn->ld->context)))
return;
if ((r = getdns_context_get_eventloop(conn->ld->context, &loop)))
return;
/* Reset downstream idle timeout */
loop->vmt->clear(loop, &conn->event);
(void) loop->vmt->schedule(loop, conn->fd,
DOWNSTREAM_IDLE_TIMEOUT, &conn->event);
if ((bytes_read = read(conn->fd, conn->read_pos, conn->to_read)) == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
return; /* Come back to do the read later */
/* IO error, close connection */
downstream_destroy(conn);
return;
}
if (bytes_read == 0) {
/* remote end closed connection, cleanup */
downstream_destroy(conn);
return;
}
assert(bytes_read <= conn->to_read);
conn->to_read -= bytes_read;
conn->read_pos += bytes_read;
if (conn->to_read)
return; /* More to read */
if (conn->read_pos - conn->read_buf == 2) {
/* read length of dns msg to read */
conn->to_read = (conn->read_buf[0] << 8) | conn->read_buf[1];
if (conn->to_read > conn->read_buf_len) {
GETDNS_FREE(*mf, conn->read_buf);
while (conn->to_read > conn->read_buf_len)
conn->read_buf_len *= 2;
if (!(conn->read_buf = GETDNS_XMALLOC(
*mf, uint8_t, conn->read_buf_len))) {
/* Memory error */
downstream_destroy(conn);
return;
}
}
if (conn->to_read < 12) {
/* Request smaller than DNS header, FORMERR */
downstream_destroy(conn);
return;
}
conn->read_pos = conn->read_buf;
return; /* Read DNS message */
}
if (!(msg = GETDNS_MALLOC(*mf, tcp_msg))) {
/* Memory error */
downstream_destroy(conn);
return;
}
msg->ld = conn->ld;
msg->conn = conn;
if ((r = getdns_wire2msg_dict(conn->read_buf,
(conn->read_pos - conn->read_buf), &request_dict)))
; /* FROMERR on input, ignore */
else {
conn->to_answer += 1;
/* Call request handler */
conn->ld->handler(
conn->ld->context, request_dict, (intptr_t)msg);
conn->read_pos = conn->read_buf;
conn->to_read = 2;
return; /* Read more requests */
}
GETDNS_FREE(*mf, msg);
conn->read_pos = conn->read_buf;
conn->to_read = 2;
/* Read more requests */
}
static void tcp_timeout_cb(void *userarg)
{
downstream *conn = (downstream *)userarg;
assert(userarg);
downstream_destroy(conn);
}
static void tcp_accept_cb(void *userarg)
{
listen_data *ld = (listen_data *)userarg;
downstream *conn;
struct mem_funcs *mf;
getdns_eventloop *loop;
getdns_return_t r;
assert(userarg);
if (!(mf = priv_getdns_context_mf(ld->context)))
return;
if ((r = getdns_context_get_eventloop(ld->context, &loop)))
return;
if (!(conn = GETDNS_MALLOC(*mf, downstream)))
return;
(void) memset(conn, 0, sizeof(downstream));
conn->ld = ld;
conn->addrlen = sizeof(conn->remote_in);
if ((conn->fd = accept(ld->fd,
(struct sockaddr *)&conn->remote_in, &conn->addrlen)) == -1) {
/* IO error, TODO: cleanup this listener */
GETDNS_FREE(*mf, conn);
}
if (!(conn->read_buf = malloc(DNS_REQUEST_SZ))) {
/* Memory error */
GETDNS_FREE(*mf, conn);
return;
}
conn->read_buf_len = DNS_REQUEST_SZ;
conn->read_pos = conn->read_buf;
conn->to_read = 2;
conn->event.userarg = conn;
conn->event.read_cb = tcp_read_cb;
conn->event.timeout_cb = tcp_timeout_cb;
(void) loop->vmt->schedule(loop, conn->fd,
DOWNSTREAM_IDLE_TIMEOUT, &conn->event);
}
static void udp_read_cb(void *userarg)
{
listen_data *ld = (listen_data *)userarg;
udp_msg *msg;
struct mem_funcs *mf;
getdns_dict *request_dict;
/* Maximum reasonable size for requests */
uint8_t buf[4096];
ssize_t len;
getdns_return_t r;
assert(userarg);
if (!(mf = priv_getdns_context_mf(ld->context)))
return;
if (!(msg = GETDNS_MALLOC(*mf, udp_msg)))
return;
msg->ld = ld;
msg->addrlen = sizeof(msg->remote_in);
if ((len = recvfrom(ld->fd, buf, sizeof(buf), 0,
(struct sockaddr *)&msg->remote_in, &msg->addrlen)) == -1)
; /* IO error, TODO: cleanup this listener */
else if ((r = getdns_wire2msg_dict(buf, len, &request_dict)))
; /* FROMERR on input, ignore */
else {
/* Call request handler */
ld->handler(ld->context, request_dict, (intptr_t)msg);
return;
}
GETDNS_FREE(*mf, msg);
}
getdns_return_t getdns_context_set_listen_addresses(getdns_context *context,
getdns_request_handler_t request_handler, getdns_list *listen_addresses)
{
static const getdns_transport_list_t listen_transports[]
= { GETDNS_TRANSPORT_UDP, GETDNS_TRANSPORT_TCP };
static const uint32_t transport_ports[] = { 53, 53 };
static const size_t n_transports = sizeof( listen_transports)
/ sizeof(*listen_transports);
/* Things that should (eventually) be stored in the getdns_context */
size_t listen_count;
listen_data *listening;
struct mem_funcs *mf;
getdns_eventloop *loop;
/* auxiliary variables */
getdns_return_t r;
size_t i;
size_t t;
struct addrinfo hints;
char addrstr[1024], portstr[1024], *eos;
const int enable = 1; /* For SO_REUSEADDR */
if (!(mf = priv_getdns_context_mf(context)))
return GETDNS_RETURN_GENERIC_ERROR;
if ((r = getdns_context_get_eventloop(context, &loop)))
return r;
if ((r = getdns_list_get_length(listen_addresses, &listen_count)))
return r;
if (!listen_count)
return GETDNS_RETURN_GOOD;
if (!(listening = GETDNS_XMALLOC(
*mf, listen_data, listen_count * n_transports)))
return GETDNS_RETURN_MEMORY_ERROR;
(void) memset(listening, 0,
sizeof(listen_data) * n_transports * listen_count);
(void) memset(&hints, 0, sizeof(struct addrinfo));
(void) memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_flags = AI_NUMERICHOST;
for (i = 0; !r && i < listen_count; i++) {
getdns_dict *dict = NULL;
getdns_bindata *address_data;
struct sockaddr_storage addr;
getdns_bindata *scope_id;
if ((r = getdns_list_get_dict(listen_addresses, i, &dict))) {
if ((r = getdns_list_get_bindata(
listen_addresses, i, &address_data)))
break;
} else if ((r = getdns_dict_get_bindata(
dict, "address_data", &address_data)))
break;
if (address_data->size == 4)
addr.ss_family = AF_INET;
else if (address_data->size == 16)
addr.ss_family = AF_INET6;
else {
r = GETDNS_RETURN_INVALID_PARAMETER;
break;
}
if (inet_ntop(addr.ss_family,
address_data->data, addrstr, 1024) == NULL) {
r = GETDNS_RETURN_INVALID_PARAMETER;
break;
}
if (dict && getdns_dict_get_bindata(dict,"scope_id",&scope_id)
== GETDNS_RETURN_GOOD) {
if (strlen(addrstr) + scope_id->size > 1022) {
r = GETDNS_RETURN_INVALID_PARAMETER;
break;
}
eos = &addrstr[strlen(addrstr)];
*eos++ = '%';
(void) memcpy(eos, scope_id->data, scope_id->size);
eos[scope_id->size] = 0;
}
for (t = 0; !r && t < n_transports; t++) {
getdns_transport_list_t transport
= listen_transports[t];
uint32_t port = transport_ports[t];
struct addrinfo *ai;
listen_data *ld = &listening[i * n_transports + t];
ld->fd = -1;
if (dict)
(void) getdns_dict_get_int(dict,
( transport == GETDNS_TRANSPORT_TLS
? "tls_port" : "port" ), &port);
(void) snprintf(portstr, 1024, "%d", (int)port);
if (getaddrinfo(addrstr, portstr, &hints, &ai)) {
r = GETDNS_RETURN_INVALID_PARAMETER;
break;
}
if (!ai)
continue;
ld->addr.ss_family = addr.ss_family;
ld->addr_len = ai->ai_addrlen;
(void) memcpy(&ld->addr, ai->ai_addr, ai->ai_addrlen);
ld->transport = transport;
ld->handler = request_handler;
ld->context = context;
freeaddrinfo(ai);
}
}
if (r) {
GETDNS_FREE(*mf, listening);
listening = NULL;
} else for (i = 0; !r && i < listen_count * n_transports; i++) {
listen_data *ld = &listening[i];
if (ld->transport != GETDNS_TRANSPORT_UDP &&
ld->transport != GETDNS_TRANSPORT_TCP)
continue;
if ((ld->fd = socket(ld->addr.ss_family,
( ld->transport == GETDNS_TRANSPORT_UDP
? SOCK_DGRAM : SOCK_STREAM), 0)) == -1)
/* IO error, TODO: report? */
continue;
if (setsockopt(ld->fd, SOL_SOCKET, SO_REUSEADDR,
&enable, sizeof(int)) < 0)
; /* Ignore */
if (bind(ld->fd, (struct sockaddr *)&ld->addr,
ld->addr_len) == -1) {
/* IO error, TODO: report? */
(void) close(ld->fd);
ld->fd = -1;
}
if (ld->transport == GETDNS_TRANSPORT_UDP) {
ld->event.userarg = ld;
ld->event.read_cb = udp_read_cb;
(void) loop->vmt->schedule(
loop, ld->fd, -1, &ld->event);
} else if (listen(ld->fd, TCP_LISTEN_BACKLOG) == -1) {
/* IO error, TODO: report? */
(void) close(ld->fd);
ld->fd = -1;
} else {
ld->event.userarg = ld;
ld->event.read_cb = tcp_accept_cb;
(void) loop->vmt->schedule(
loop, ld->fd, -1, &ld->event);
}
}
return r;
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2013, NLNet Labs, Verisign, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the names of the copyright holders nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 Verisign, Inc. 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.
*/
#ifndef GETDNS_CONTEXT_SET_LISTEN_ADDRESSES_H_
#define GETDNS_CONTEXT_SET_LISTEN_ADDRESSES_H_
#include "getdns/getdns.h"
typedef void (*getdns_request_handler_t)(
getdns_context *context,
getdns_dict *request,
getdns_transaction_t request_id
);
getdns_return_t getdns_context_set_listen_addresses(getdns_context *context,
getdns_request_handler_t request_handler, getdns_list *listen_addresses);
getdns_return_t getdns_reply(getdns_context *context,
getdns_transaction_t request_id, getdns_dict *reply);
void _getdns_cancel_reply(getdns_context *context,
getdns_transaction_t request_id);
#endif

View File

@ -29,6 +29,7 @@
#include "debug.h"
#include "getdns_str2dict.h"
#include "getdns_context_config.h"
#include "getdns_context_set_listen_addresses.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -383,9 +384,7 @@ static void parse_config(const char *config_str)
if (!(r = getdns_dict_get_list(
config_dict, "listen_addresses", &list))) {
if (listen_list && !listen_dict) {
/* TODO: Stop listening */
getdns_list_destroy(listen_list);
listen_count = 0;
listen_list = NULL;
}
/* Strange construction to copy the list.
@ -1091,117 +1090,14 @@ void read_line_cb(void *userarg)
}
}
typedef struct listen_data {
socklen_t addr_len;
struct sockaddr_storage addr;
int fd;
getdns_transport_list_t transport;
getdns_eventloop_event event;
} listen_data;
listen_data *listening = NULL;
typedef struct dns_msg {
listen_data *ld;
getdns_dict *query;
uint32_t rt;
uint32_t do_bit;
getdns_transaction_t request_id;
getdns_dict *query;
uint32_t rt;
uint32_t do_bit;
uint32_t cd_bit;
} dns_msg;
typedef struct udp_msg {
dns_msg super;
struct sockaddr_storage remote_in;
socklen_t addrlen;
} udp_msg;
typedef struct tcp_to_write tcp_to_write;
struct tcp_to_write {
size_t write_buf_len;
size_t written;
tcp_to_write *next;
uint8_t write_buf[];
};
#define DOWNSTREAM_IDLE_TIMEOUT 5000
typedef struct downstream {
listen_data *ld;
struct sockaddr_storage remote_in;
socklen_t addrlen;
int fd;
getdns_eventloop_event event;
uint8_t *read_buf;
size_t read_buf_len;
uint8_t *read_pos;
size_t to_read;
tcp_to_write *to_write;
size_t to_answer;
} downstream;
typedef struct tcp_msg {
dns_msg super;
downstream *conn;
} tcp_msg;
void downstream_destroy(downstream *conn)
{
tcp_to_write *cur, *next;
if (conn->event.read_cb||conn->event.write_cb||conn->event.timeout_cb)
loop->vmt->clear(loop, &conn->event);
if (conn->fd >= 0) {
if (close(conn->fd) == -1)
perror("close");
}
free(conn->read_buf);
for (cur = conn->to_write; cur; cur = next) {
next = cur->next;
free(cur);
}
free(conn);
}
void tcp_write_cb(void *userarg)
{
downstream *conn = (downstream *)userarg;
tcp_to_write *to_write;
ssize_t written;
assert(userarg);
/* Reset downstream idle timeout */
loop->vmt->clear(loop, &conn->event);
if (!conn->to_write) {
conn->event.write_cb = NULL;
(void) loop->vmt->schedule(loop, conn->fd,
DOWNSTREAM_IDLE_TIMEOUT, &conn->event);
return;
}
to_write = conn->to_write;
if ((written = write(conn->fd, &to_write->write_buf[to_write->written],
to_write->write_buf_len - to_write->written)) == -1) {
perror("write");
conn->event.read_cb = conn->event.write_cb =
conn->event.timeout_cb = NULL;
downstream_destroy(conn);
return;
}
to_write->written += written;
if (to_write->written == to_write->write_buf_len) {
conn->to_write = to_write->next;
free(to_write);
}
if (!conn->to_write)
conn->event.write_cb = NULL;
(void) loop->vmt->schedule(loop, conn->fd,
DOWNSTREAM_IDLE_TIMEOUT, &conn->event);
}
#if defined(TRACE_DEBUG) && TRACE_DEBUG
#define SERVFAIL(error,r,msg,resp_p) do { \
if (r) DEBUG_TRACE("%s: %s\n", error, getdns_get_errorstr_by_id(r)); \
@ -1238,8 +1134,6 @@ void request_cb(getdns_context *context, getdns_callback_type_t callback_type,
dns_msg *msg = (dns_msg *)userarg;
uint32_t qid;
getdns_return_t r = GETDNS_RETURN_GOOD;
uint8_t buf[65536];
size_t len = sizeof(buf);
uint32_t n;
DEBUG_TRACE("reply for: %p %"PRIu64" %d\n", msg, transaction_id, (int)callback_type);
@ -1291,48 +1185,13 @@ void request_cb(getdns_context *context, getdns_callback_type_t callback_type,
SERVFAIL("Recursion not available", 0, msg, &response);
if (!response)
; /* No response, no reply */
/* No response, no reply */
_getdns_cancel_reply(context, msg->request_id);
else if ((r = getdns_msg_dict2wire_buf(response, buf, &len)))
fprintf(stderr, "Could not convert reply: %s\n",
else if ((r = getdns_reply(context, msg->request_id, response))) {
fprintf(stderr, "Could not reply: %s\n",
getdns_get_errorstr_by_id(r));
else if (msg->ld->transport == GETDNS_TRANSPORT_UDP) {
udp_msg *msg = (udp_msg *)userarg;
if (sendto(msg->super.ld->fd, buf, len, 0,
(struct sockaddr *)&msg->remote_in, msg->addrlen) == -1)
perror("sendto");
} else if (msg->ld->transport == GETDNS_TRANSPORT_TCP) {
tcp_msg *msg = (tcp_msg *)userarg;
tcp_to_write **to_write_p;
tcp_to_write *to_write = malloc(sizeof(tcp_to_write) + len + 2);
if (!to_write)
fprintf(stderr, "Could not allocate memory for"
"message to write on tcp stream\n");
else {
to_write->write_buf_len = len + 2;
to_write->write_buf[0] = (len >> 8) & 0xFF;
to_write->write_buf[1] = len & 0xFF;
to_write->written = 0;
to_write->next = NULL;
(void) memcpy(to_write->write_buf + 2, buf, len);
/* Appen to_write to conn->to_write list */
for ( to_write_p = &msg->conn->to_write
; *to_write_p
; to_write_p = &(*to_write_p)->next)
; /* pass */
*to_write_p = to_write;
loop->vmt->clear(loop, &msg->conn->event);
msg->conn->event.write_cb = tcp_write_cb;
(void) loop->vmt->schedule(loop,
msg->conn->fd, DOWNSTREAM_IDLE_TIMEOUT,
&msg->conn->event);
}
_getdns_cancel_reply(context, msg->request_id);
}
if (msg) {
getdns_dict_destroy(msg->query);
@ -1342,7 +1201,8 @@ void request_cb(getdns_context *context, getdns_callback_type_t callback_type,
getdns_dict_destroy(response);
}
getdns_return_t schedule_request(dns_msg *msg)
void incoming_request_handler(getdns_context *context,
getdns_dict *request, getdns_transaction_t request_id)
{
getdns_bindata *qname;
char *qname_str = NULL;
@ -1353,6 +1213,20 @@ getdns_return_t schedule_request(dns_msg *msg)
getdns_list *list;
getdns_transaction_t transaction_id;
getdns_dict *qext;
dns_msg *msg;
getdns_dict *response = NULL;
assert(context);
assert(request);
if (!(msg = malloc(sizeof(dns_msg)))) {
fprintf(stderr, "Could not handle incoming query due to "
"memory error\n");
getdns_dict_destroy(request);
return;
}
msg->query = request;
msg->request_id = request_id;
if (!query_extensions_spc &&
!(query_extensions_spc = getdns_dict_create()))
@ -1437,308 +1311,27 @@ getdns_return_t schedule_request(dns_msg *msg)
qext, msg, &transaction_id, request_cb)))
fprintf(stderr, "Could not schedule query: %s\n",
getdns_get_errorstr_by_id(r));
DEBUG_TRACE("scheduled: %p %"PRIu64" for %s %d\n",
msg, transaction_id, qname_str, (int)qtype);
else {
DEBUG_TRACE("scheduled: %p %"PRIu64" for %s %d\n",
msg, transaction_id, qname_str, (int)qtype);
free(qname_str);
return;
}
free(qname_str);
servfail(msg, &response);
if (!response)
/* No response, no reply */
_getdns_cancel_reply(context, msg->request_id);
return r;
}
void tcp_read_cb(void *userarg)
{
downstream *conn = (downstream *)userarg;
ssize_t bytes_read;
tcp_msg *msg;
getdns_return_t r;
assert(userarg);
/* Reset downstream idle timeout */
loop->vmt->clear(loop, &conn->event);
(void) loop->vmt->schedule(loop, conn->fd,
DOWNSTREAM_IDLE_TIMEOUT, &conn->event);
if ((bytes_read = read(conn->fd, conn->read_pos, conn->to_read)) == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
return;
perror("read");
downstream_destroy(conn);
return;
}
if (bytes_read == 0) {
/* fprintf(stderr, "Remote end closed connection\n"); */
downstream_destroy(conn);
return;
}
assert(bytes_read <= conn->to_read);
conn->to_read -= bytes_read;
conn->read_pos += bytes_read;
if (conn->to_read)
return; /* More to read */
if (conn->read_pos - conn->read_buf == 2) {
/* read length of dns msg to read */
conn->to_read = (conn->read_buf[0] << 8) | conn->read_buf[1];
if (conn->to_read > conn->read_buf_len) {
free(conn->read_buf);
while (conn->to_read > conn->read_buf_len)
conn->read_buf_len *= 2;
if (!(conn->read_buf = malloc(conn->read_buf_len))) {
fprintf(stderr, "Could not enlarge "
"downstream read buffer\n");
downstream_destroy(conn);
return;
}
}
if (conn->to_read < 12) {
fprintf(stderr, "Request smaller than DNS header\n");
downstream_destroy(conn);
return;
}
conn->read_pos = conn->read_buf;
return; /* Read DNS message */
}
if (!(msg = malloc(sizeof(tcp_msg)))) {
fprintf(stderr, "Could not allocate tcp_msg\n");
downstream_destroy(conn);
return;
}
msg->super.ld = conn->ld;
msg->conn = conn;
if ((r = getdns_wire2msg_dict(conn->read_buf,
(conn->read_pos - conn->read_buf), &msg->super.query)))
fprintf(stderr, "Error converting query dns msg: %s\n",
else if ((r = getdns_reply(context, msg->request_id, response))) {
fprintf(stderr, "Could not reply: %s\n",
getdns_get_errorstr_by_id(r));
else if ((r = schedule_request(&msg->super))) {
fprintf(stderr, "Error scheduling query: %s\n",
getdns_get_errorstr_by_id(r));
getdns_dict_destroy(msg->super.query);
} else {
conn->to_answer += 1;
conn->read_pos = conn->read_buf;
conn->to_read = 2;
return; /* Read more requests */
_getdns_cancel_reply(context, msg->request_id);
}
getdns_dict_destroy(msg->query);
free(msg);
conn->read_pos = conn->read_buf;
conn->to_read = 2;
/* Read more requests */
}
void tcp_timeout_cb(void *userarg)
{
downstream *conn = (downstream *)userarg;
assert(userarg);
downstream_destroy(conn);
}
void tcp_accept_cb(void *userarg)
{
listen_data *ld = (listen_data *)userarg;
downstream *conn;
assert(userarg);
if (!(conn = malloc(sizeof(downstream))))
return;
(void) memset(conn, 0, sizeof(downstream));
conn->ld = ld;
conn->addrlen = sizeof(conn->remote_in);
if ((conn->fd = accept(ld->fd,
(struct sockaddr *)&conn->remote_in, &conn->addrlen)) == -1) {
perror("accept");
free(conn);
}
if (!(conn->read_buf = malloc(4096))) {
fprintf(stderr, "Could not allocate downstream read buffer.\n");
free(conn);
}
conn->read_buf_len = 4096;
conn->read_pos = conn->read_buf;
conn->to_read = 2;
conn->event.userarg = conn;
conn->event.read_cb = tcp_read_cb;
conn->event.timeout_cb = tcp_timeout_cb;
(void) loop->vmt->schedule(loop, conn->fd,
DOWNSTREAM_IDLE_TIMEOUT, &conn->event);
}
void udp_read_cb(void *userarg)
{
listen_data *ld = (listen_data *)userarg;
udp_msg *msg;
uint8_t buf[65536];
ssize_t len;
getdns_return_t r;
assert(userarg);
if (!(msg = malloc(sizeof(udp_msg))))
return;
msg->super.ld = ld;
msg->addrlen = sizeof(msg->remote_in);
if ((len = recvfrom(ld->fd, buf, sizeof(buf), 0,
(struct sockaddr *)&msg->remote_in, &msg->addrlen)) == -1)
perror("recvfrom");
else if ((r = getdns_wire2msg_dict(buf, len, &msg->super.query)))
fprintf(stderr, "Error converting query dns msg: %s\n",
getdns_get_errorstr_by_id(r));
else if ((r = schedule_request(&msg->super))) {
fprintf(stderr, "Error scheduling query: %s\n",
getdns_get_errorstr_by_id(r));
getdns_dict_destroy(msg->super.query);
} else
return;
free(msg);
}
getdns_return_t start_daemon()
{
static const getdns_transport_list_t listen_transports[]
= { GETDNS_TRANSPORT_UDP, GETDNS_TRANSPORT_TCP };
static const uint32_t transport_ports[] = { 53, 53 };
static const size_t n_transports = sizeof( listen_transports)
/ sizeof(*listen_transports);
size_t i;
size_t t;
struct addrinfo hints;
getdns_return_t r = GETDNS_RETURN_GOOD;
char addrstr[1024], portstr[1024], *eos;
const int enable = 1; /* For SO_REUSEADDR */
if (!listen_count)
return GETDNS_RETURN_GOOD;
if (!(listening = malloc(
sizeof(listen_data) * n_transports * listen_count)))
return GETDNS_RETURN_MEMORY_ERROR;
(void) memset(listening, 0,
sizeof(listen_data) * n_transports * listen_count);
(void) memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_flags = AI_NUMERICHOST;
for (i = 0; !r && i < listen_count; i++) {
getdns_dict *dict = NULL;
getdns_bindata *address_data;
struct sockaddr_storage addr;
getdns_bindata *scope_id;
if ((r = getdns_list_get_dict(listen_list, i, &dict))) {
if ((r = getdns_list_get_bindata(
listen_list, i, &address_data)))
break;
} else if ((r = getdns_dict_get_bindata(
dict, "address_data", &address_data)))
break;
if (address_data->size == 4)
addr.ss_family = AF_INET;
else if (address_data->size == 16)
addr.ss_family = AF_INET6;
else {
r = GETDNS_RETURN_INVALID_PARAMETER;
break;
}
if (inet_ntop(addr.ss_family,
address_data->data, addrstr, 1024) == NULL) {
r = GETDNS_RETURN_INVALID_PARAMETER;
break;
}
if (dict && getdns_dict_get_bindata(dict,"scope_id",&scope_id)
== GETDNS_RETURN_GOOD) {
if (strlen(addrstr) + scope_id->size > 1022) {
r = GETDNS_RETURN_INVALID_PARAMETER;
break;
}
eos = &addrstr[strlen(addrstr)];
*eos++ = '%';
(void) memcpy(eos, scope_id->data, scope_id->size);
eos[scope_id->size] = 0;
}
for (t = 0; !r && t < n_transports; t++) {
getdns_transport_list_t transport
= listen_transports[t];
uint32_t port = transport_ports[t];
struct addrinfo *ai;
listen_data *ld = &listening[i * n_transports + t];
ld->fd = -1;
if (dict)
(void) getdns_dict_get_int(dict,
( transport == GETDNS_TRANSPORT_TLS
? "tls_port" : "port" ), &port);
(void) snprintf(portstr, 1024, "%d", (int)port);
if (getaddrinfo(addrstr, portstr, &hints, &ai)) {
r = GETDNS_RETURN_INVALID_PARAMETER;
break;
}
if (!ai)
continue;
ld->addr.ss_family = addr.ss_family;
ld->addr_len = ai->ai_addrlen;
(void) memcpy(&ld->addr, ai->ai_addr, ai->ai_addrlen);
ld->transport = transport;
freeaddrinfo(ai);
}
}
if (r) {
free(listening);
listening = NULL;
} else for (i = 0; !r && i < listen_count * n_transports; i++) {
listen_data *ld = &listening[i];
if (ld->transport != GETDNS_TRANSPORT_UDP &&
ld->transport != GETDNS_TRANSPORT_TCP)
continue;
if ((ld->fd = socket(ld->addr.ss_family,
( ld->transport == GETDNS_TRANSPORT_UDP
? SOCK_DGRAM : SOCK_STREAM), 0)) == -1)
perror("socket");
else if (setsockopt(ld->fd, SOL_SOCKET, SO_REUSEADDR,
&enable, sizeof(int)) < 0)
perror("setsockopt(SO_REUSEADDR) failed");
else if (bind(ld->fd, (struct sockaddr *)&ld->addr,
ld->addr_len) == -1)
perror("bind");
else if (ld->transport == GETDNS_TRANSPORT_UDP) {
ld->event.userarg = ld;
ld->event.read_cb = udp_read_cb;
(void) loop->vmt->schedule(
loop, ld->fd, -1, &ld->event);
} else if (listen(ld->fd, 16) == -1)
perror("listen");
else {
ld->event.userarg = ld;
ld->event.read_cb = tcp_accept_cb;
(void) loop->vmt->schedule(
loop, ld->fd, -1, &ld->event);
}
}
return r;
if (response)
getdns_dict_destroy(response);
}
int
@ -1776,7 +1369,11 @@ main(int argc, char **argv)
goto done_destroy_context;
assert(loop);
}
start_daemon();
if (listen_count)
if ((r = getdns_context_set_listen_addresses(context,
incoming_request_handler, listen_list)))
goto done_destroy_context;
/* Make the call */
if (interactive) {
getdns_eventloop_event read_line_ev = {