From 3cc369a27ddfda54c335314d75b5b53950bc9934 Mon Sep 17 00:00:00 2001 From: Willem Toorop Date: Tue, 21 Jun 2016 16:14:54 +0200 Subject: [PATCH] Move serving requests out of getdns_query --- src/test/Makefile.in | 11 +- .../getdns_context_set_listen_addresses.c | 590 ++++++++++++++++++ .../getdns_context_set_listen_addresses.h | 47 ++ src/test/getdns_query.c | 497 ++------------- 4 files changed, 693 insertions(+), 452 deletions(-) create mode 100644 src/test/getdns_context_set_listen_addresses.c create mode 100644 src/test/getdns_context_set_listen_addresses.h diff --git a/src/test/Makefile.in b/src/test/Makefile.in index 45889a03..797f2e6e 100644 --- a/src/test/Makefile.in +++ b/src/test/Makefile.in @@ -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 \ diff --git a/src/test/getdns_context_set_listen_addresses.c b/src/test/getdns_context_set_listen_addresses.c new file mode 100644 index 00000000..4b98a6f3 --- /dev/null +++ b/src/test/getdns_context_set_listen_addresses.c @@ -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 + +#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; +} + diff --git a/src/test/getdns_context_set_listen_addresses.h b/src/test/getdns_context_set_listen_addresses.h new file mode 100644 index 00000000..116634f4 --- /dev/null +++ b/src/test/getdns_context_set_listen_addresses.h @@ -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 diff --git a/src/test/getdns_query.c b/src/test/getdns_query.c index 60a00333..f225b91b 100644 --- a/src/test/getdns_query.c +++ b/src/test/getdns_query.c @@ -29,6 +29,7 @@ #include "debug.h" #include "getdns_str2dict.h" #include "getdns_context_config.h" +#include "getdns_context_set_listen_addresses.h" #include #include #include @@ -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 = {