getdns/src/stub.c

2317 lines
76 KiB
C

/**
*
* /brief function for stub resolving
*
*/
/*
* 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"
/* Intercept and do not sent out COM DS queries with TLS
* For debugging purposes only. Never commit with this turned on.
*/
#define INTERCEPT_COM_DS 0
#include "debug.h"
#include <fcntl.h>
#include "stub.h"
#include "gldns/gbuffer.h"
#include "gldns/pkthdr.h"
#include "gldns/rrdef.h"
#include "gldns/str2wire.h"
#include "gldns/wire2str.h"
#include "rr-iter.h"
#include "context.h"
#include "util-internal.h"
#include "platform.h"
#include "general.h"
#include "pubkey-pinning.h"
/* WSA TODO:
* STUB_TCP_RETRY added to deal with edge triggered event loops (versus
* level triggered). See also lines containing WSA TODO below...
*/
#define STUB_TRY_AGAIN_LATER -24 /* EMFILE, i.e. Out of OS resources */
#define STUB_NO_AUTH -8 /* Existing TLS connection is not authenticated */
#define STUB_CONN_GONE -7 /* Connection has failed, clear queue*/
#define STUB_TCP_RETRY -6
#define STUB_OUT_OF_OPTIONS -5 /* upstream options exceeded MAXIMUM_UPSTREAM_OPTION_SPACE */
#define STUB_SETUP_ERROR -4
#define STUB_TCP_MORE_TO_READ -3
#define STUB_TCP_MORE_TO_WRITE -3
#define STUB_TCP_ERROR -2
/* Don't currently have access to the context whilst doing handshake */
#define TIMEOUT_TLS 2500
/* Arbritray number of message for EDNS keepalive resend*/
#define EDNS_KEEPALIVE_RESEND 5
static time_t secret_rollover_time = 0;
static uint32_t secret = 0;
static uint32_t prev_secret = 0;
static void upstream_read_cb(void *userarg);
static void upstream_write_cb(void *userarg);
static void upstream_idle_timeout_cb(void *userarg);
static void upstream_schedule_netreq(getdns_upstream *upstream,
getdns_network_req *netreq);
static void upstream_reschedule_events(getdns_upstream *upstream);
static int upstream_working_ok(getdns_upstream *upstream);
static int upstream_auth_status_ok(getdns_upstream *upstream,
getdns_network_req *netreq);
static int upstream_connect(getdns_upstream *upstream,
getdns_transport_list_t transport,
getdns_dns_req *dnsreq);
static int fallback_on_write(getdns_network_req *netreq);
static void stub_timeout_cb(void *userarg);
uint64_t _getdns_get_time_as_uintt64();
/*****************************/
/* General utility functions */
/*****************************/
static void
rollover_secret()
{
time_t now = 0;
/* Create and roll server secrets */
if (time(&now) <= secret_rollover_time)
return;
/* Remember previous secret, in to keep answering on rollover
* boundary with old cookie.
*/
prev_secret = secret;
secret = arc4random();
/* Next rollover over EDNS_COOKIE_ROLLOVER_TIME with 30% jitter,
* I.e. some offset + or - 15% of the future point in time.
*/
secret_rollover_time = now + (EDNS_COOKIE_ROLLOVER_TIME / 20 * 17)
+ arc4random_uniform(EDNS_COOKIE_ROLLOVER_TIME / 10 * 3);
}
static void
calc_new_cookie(getdns_upstream *upstream, uint8_t *cookie)
{
const EVP_MD *md;
EVP_MD_CTX *mdctx;
unsigned char md_value[EVP_MAX_MD_SIZE];
unsigned int md_len;
size_t i;
sa_family_t af = upstream->addr.ss_family;
void *sa_addr = ((struct sockaddr*)&upstream->addr)->sa_data;
size_t addr_len = ( af == AF_INET6 ? sizeof(struct sockaddr_in6)
: af == AF_INET ? sizeof(struct sockaddr_in)
: 0 ) - sizeof(sa_family_t);
md = EVP_sha256();
mdctx = EVP_MD_CTX_create();
EVP_DigestInit_ex(mdctx, md, NULL);
EVP_DigestUpdate(mdctx, &secret, sizeof(secret));
EVP_DigestUpdate(mdctx, sa_addr, addr_len);
EVP_DigestFinal_ex(mdctx, md_value, &md_len);
EVP_MD_CTX_destroy(mdctx);
(void) memset(cookie, 0, 8);
for (i = 0; i < md_len; i++)
cookie[i % 8] ^= md_value[i];
}
static getdns_return_t
attach_edns_client_subnet_private(getdns_network_req *req)
{
/* see https://tools.ietf.org/html/rfc7871#section-7.1.2
* all-zeros is a request to not leak the data further:
* A two byte FAMILY field is a SHOULD even when SOURCE
* and SCOPE are 0.
* "\x00\x02" FAMILY: 2 for IPv6 upstreams in network byte order, or
* "\x00\x01" FAMILY: 1 for IPv4 upstreams in network byte order, then:
* "\x00" SOURCE PREFIX-LENGTH: 0
* "\x00"; SCOPE PREFIX-LENGTH: 0
*/
return _getdns_network_req_add_upstream_option(
req, GLDNS_EDNS_CLIENT_SUBNET, 4,
( req->upstream->addr.ss_family == AF_INET6
? "\x00\x02\x00\x00" : "\x00\x01\x00\x00" ));
}
static getdns_return_t
attach_edns_keepalive(getdns_network_req *req)
{
/* Client always sends length 0, omits the timeout */
return _getdns_network_req_add_upstream_option(req,
GLDNS_EDNS_KEEPALIVE,
0, NULL);
}
static getdns_return_t
attach_edns_cookie(getdns_network_req *req)
{
getdns_upstream *upstream = req->upstream;
uint16_t sz;
void* val;
uint8_t buf[8 + 32]; /* server cookies can be no larger than 32 bytes */
rollover_secret();
if (!upstream->has_client_cookie) {
calc_new_cookie(upstream, upstream->client_cookie);
upstream->secret = secret;
upstream->has_client_cookie = 1;
sz = 8;
val = upstream->client_cookie;
} else if (upstream->secret != secret) {
memcpy( upstream->prev_client_cookie
, upstream->client_cookie, 8);
upstream->has_prev_client_cookie = 1;
calc_new_cookie(upstream, upstream->client_cookie);
upstream->secret = secret;
sz = 8;
val = upstream->client_cookie;
} else if (!upstream->has_server_cookie) {
sz = 8;
val = upstream->client_cookie;
} else {
sz = 8 + upstream->server_cookie_len;
memcpy(buf, upstream->client_cookie, 8);
memcpy(buf+8, upstream->server_cookie, upstream->server_cookie_len);
val = buf;
}
return _getdns_network_req_add_upstream_option(req, EDNS_COOKIE_OPCODE, sz, val);
}
/* Will find a matching OPT RR, but leaves the caller to validate it
*
* Returns 2 when found
* 0 when not found
* and 1 on FORMERR
*/
static int
match_edns_opt_rr(uint16_t code, uint8_t *response, size_t response_len,
const uint8_t **position, uint16_t *option_len)
{
_getdns_rr_iter rr_iter_storage, *rr_iter;
const uint8_t *pos;
uint16_t rdata_len, opt_code = 0, opt_len = 0;
/* Search for the OPT RR (if any) */
for ( rr_iter = _getdns_rr_iter_init(&rr_iter_storage
, response, response_len)
; rr_iter
; rr_iter = _getdns_rr_iter_next(rr_iter)) {
if (_getdns_rr_iter_section(rr_iter) != SECTION_ADDITIONAL)
continue;
if (gldns_read_uint16(rr_iter->rr_type) != GETDNS_RRTYPE_OPT)
continue;
break;
}
if (! rr_iter)
return 0; /* No OPT, no cookie */
pos = rr_iter->rr_type + 8;
#if defined(STUB_DEBUG) && STUB_DEBUG
char str_spc[8192], *str = str_spc;
size_t str_len = sizeof(str_spc);
uint8_t *data = (uint8_t *)rr_iter->pos;
size_t data_len = rr_iter->nxt - rr_iter->pos;
(void) gldns_wire2str_rr_scan(
&data, &data_len, &str, &str_len, (uint8_t *)rr_iter->pkt, rr_iter->pkt_end - rr_iter->pkt);
DEBUG_STUB("%s %-35s: OPT RR: %s",
STUB_DEBUG_READ, __FUNC__, str_spc);
#endif
/* OPT found, now search for the specified option */
if (pos + 2 > rr_iter->nxt)
return 1; /* FORMERR */
rdata_len = gldns_read_uint16(pos); pos += 2;
if (pos + rdata_len > rr_iter->nxt)
return 1; /* FORMERR */
while (pos < rr_iter->nxt) {
opt_code = gldns_read_uint16(pos); pos += 2;
opt_len = gldns_read_uint16(pos); pos += 2;
if (pos + opt_len > rr_iter->nxt)
return 1; /* FORMERR */
if (opt_code == code)
break;
pos += opt_len; /* Skip unknown options */
}
if (pos >= rr_iter->nxt || opt_code != code)
return 0; /* Everything OK, just no cookie found. */
*position = pos;
*option_len = opt_len;
return 2;
}
/* TODO: Test combinations of EDNS0 options*/
static int
match_and_process_server_cookie(
getdns_upstream *upstream, uint8_t *response, size_t response_len)
{
const uint8_t *position = NULL;
uint16_t option_len = 0;
int found = match_edns_opt_rr(EDNS_COOKIE_OPCODE, response,
response_len, &position, &option_len);
if (found != 2)
return found;
if (option_len < 16 || option_len > 40)
return 1; /* FORMERR */
if (!upstream->has_client_cookie)
return 1; /* Cookie reply, but we didn't sent one */
if (memcmp(upstream->client_cookie, position, 8) != 0) {
if (!upstream->has_prev_client_cookie)
return 1; /* Cookie didn't match */
if (memcmp(upstream->prev_client_cookie, position, 8) != 0)
return 1; /* Previous cookie didn't match either */
upstream->has_server_cookie = 0;
return 0; /* Don't store server cookie, because it
* is for our previous client cookie
*/
}
position += 8;
option_len -= 8;
upstream->has_server_cookie = 1;
upstream->server_cookie_len = option_len;
(void) memcpy(upstream->server_cookie, position, option_len);
return 0;
}
static void
process_keepalive(
getdns_upstream *upstream, getdns_network_req *netreq,
uint8_t *response, size_t response_len)
{
const uint8_t *position = NULL;
uint16_t option_len = 0;
int found = match_edns_opt_rr(GLDNS_EDNS_KEEPALIVE, response,
response_len, &position, &option_len);
if (found != 2 || option_len != 2) {
if (netreq->keepalive_sent == 1) {
/* For TCP if no keepalive sent back, then we must use 0 idle timeout
as server does not support it. TLS allows idle connections without
keepalive, according to RFC7858. */
#if !defined(KEEP_CONNECTIONS_OPEN_DEBUG) || !KEEP_CONNECTIONS_OPEN_DEBUG
if (upstream->transport != GETDNS_TRANSPORT_TLS)
upstream->keepalive_timeout = 0;
else
#endif
upstream->keepalive_timeout = netreq->owner->context->idle_timeout;
}
return;
}
upstream->server_keepalive_received = 1;
/* Use server sent value unless the client specified a shorter one.
Convert to ms first (wire value has units of 100ms) */
uint64_t server_keepalive = ((uint64_t)gldns_read_uint16(position))*100;
DEBUG_STUB("%s %-35s: FD: %d Server Keepalive received: %d ms\n",
STUB_DEBUG_READ, __FUNC__, upstream->fd,
(int)server_keepalive);
if (netreq->owner->context->idle_timeout < server_keepalive)
upstream->keepalive_timeout = netreq->owner->context->idle_timeout;
else {
if (server_keepalive == 0) {
/* This means the server wants us to shut the connection (sending no
more queries). */
upstream->keepalive_shutdown = 1;
}
upstream->keepalive_timeout = server_keepalive;
DEBUG_STUB("%s %-35s: FD: %d Server Keepalive used: %d ms\n",
STUB_DEBUG_READ, __FUNC__, upstream->fd,
(int)server_keepalive);
}
}
/** best effort to set nonblocking */
static void
getdns_sock_nonblock(int sockfd)
{
#ifdef HAVE_FCNTL
int flag;
if((flag = fcntl(sockfd, F_GETFL)) != -1) {
flag |= O_NONBLOCK;
if(fcntl(sockfd, F_SETFL, flag) == -1) {
/* ignore error, continue blockingly */
}
}
#elif defined(HAVE_IOCTLSOCKET)
unsigned long on = 1;
if(ioctlsocket(sockfd, FIONBIO, &on) != 0) {
/* ignore error, continue blockingly */
}
#endif
}
static int
tcp_connect(getdns_upstream *upstream, getdns_transport_list_t transport)
{
int fd = -1;
DEBUG_STUB("%s %-35s: Creating TCP connection: %p\n", STUB_DEBUG_SETUP,
__FUNC__, (void*)upstream);
if ((fd = socket(upstream->addr.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1)
return -1;
getdns_sock_nonblock(fd);
/* Note that error detection is different with TFO. Since the handshake
doesn't start till the sendto() lack of connection is often delayed until
then or even the subsequent event depending on the error and platform.*/
#ifdef USE_TCP_FASTOPEN
/* Leave the connect to the later call to sendto() if using TCP*/
if (transport == GETDNS_TRANSPORT_TCP)
return fd;
#elif USE_OSX_TCP_FASTOPEN
(void)transport;
sa_endpoints_t endpoints;
endpoints.sae_srcif = 0;
endpoints.sae_srcaddr = NULL;
endpoints.sae_srcaddrlen = 0;
endpoints.sae_dstaddr = (struct sockaddr *)&upstream->addr;
endpoints.sae_dstaddrlen = upstream->addr_len;
if (connectx(fd, &endpoints, SAE_ASSOCID_ANY,
CONNECT_DATA_IDEMPOTENT | CONNECT_RESUME_ON_READ_WRITE,
NULL, 0, NULL, NULL) == 0) {
return fd;
}
if (_getdns_socketerror() == _getdns_EINPROGRESS ||
_getdns_socketerror() == _getdns_EWOULDBLOCK)
return fd;
#else
(void)transport;
#endif
if (connect(fd, (struct sockaddr *)&upstream->addr,
upstream->addr_len) == -1) {
if (_getdns_socketerror() == _getdns_EINPROGRESS ||
_getdns_socketerror() == _getdns_EWOULDBLOCK)
return fd;
_getdns_closesocket(fd);
return -1;
}
return fd;
}
static int
tcp_connected(getdns_upstream *upstream) {
int error = 0;
socklen_t len = (socklen_t)sizeof(error);
getsockopt(upstream->fd, SOL_SOCKET, SO_ERROR, (void*)&error, &len);
if (_getdns_error_wants_retry(error))
return STUB_TCP_RETRY;
else if (error != 0) {
return STUB_SETUP_ERROR;
}
if (upstream->transport == GETDNS_TRANSPORT_TCP &&
upstream->queries_sent == 0) {
upstream->conn_state = GETDNS_CONN_OPEN;
upstream->conn_completed++;
}
return 0;
}
/**************************/
/* Error/cleanup functions*/
/**************************/
static void
stub_next_upstream(getdns_network_req *netreq)
{
getdns_dns_req *dnsreq = netreq->owner;
if (! --netreq->upstream->to_retry) {
/* Limit back_off value to configured maximum */
if (netreq->upstream->back_off * 2 > dnsreq->context->max_backoff_value)
netreq->upstream->to_retry = -(dnsreq->context->max_backoff_value);
else
netreq->upstream->to_retry = -(netreq->upstream->back_off *= 2);
}
dnsreq->upstreams->current_udp+=GETDNS_UPSTREAM_TRANSPORTS;
if (dnsreq->upstreams->current_udp >= dnsreq->upstreams->count)
dnsreq->upstreams->current_udp = 0;
}
static void
remove_from_write_queue(getdns_upstream *upstream, getdns_network_req * netreq)
{
getdns_network_req *r, *prev_r;
for ( r = upstream->write_queue, prev_r = NULL
; r
; prev_r = r, r = r->write_queue_tail) {
if (r != netreq)
continue;
if (prev_r)
prev_r->write_queue_tail = r->write_queue_tail;
else
upstream->write_queue = r->write_queue_tail;
if (r == upstream->write_queue_last) {
/* If r was the last netreq,
* its write_queue tail MUST be NULL
*/
assert(r->write_queue_tail == NULL);
upstream->write_queue_last = prev_r ? prev_r : NULL;
}
netreq->write_queue_tail = NULL;
break; /* netreq found and removed */
}
}
static void
stub_cleanup(getdns_network_req *netreq)
{
DEBUG_STUB("%s %-35s: MSG: %p\n",
STUB_DEBUG_CLEANUP, __FUNC__, (void*)netreq);
getdns_dns_req *dnsreq = netreq->owner;
GETDNS_CLEAR_EVENT(dnsreq->loop, &netreq->event);
if (netreq->query_id_registered) {
(void) _getdns_rbtree_delete(
netreq->query_id_registered, netreq->node.key);
netreq->query_id_registered = NULL;
netreq->node.key = NULL;
}
if (netreq->upstream) {
remove_from_write_queue(netreq->upstream, netreq);
if (netreq->upstream->event.ev)
upstream_reschedule_events(netreq->upstream);
}
}
static void
upstream_failed(getdns_upstream *upstream, int during_setup)
{
getdns_network_req *netreq;
DEBUG_STUB("%s %-35s: FD: %d Failure during connection setup = %d\n",
STUB_DEBUG_CLEANUP, __FUNC__, upstream->fd, during_setup);
/* Fallback code should take care of queue queries and then close conn
when idle.*/
/* [TLS1]TODO: Work out how to re-open the connection and re-try
the queries if there is only one upstream.*/
GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event);
if (during_setup) {
upstream->conn_setup_failed++;
} else {
upstream->conn_shutdowns++;
/* [TLS1]TODO: Re-try these queries if possible.*/
}
upstream->conn_state = GETDNS_CONN_TEARDOWN;
while (upstream->write_queue)
upstream_write_cb(upstream);
while (upstream->netreq_by_query_id.count) {
netreq = (getdns_network_req *)
_getdns_rbtree_first(&upstream->netreq_by_query_id);
stub_cleanup(netreq);
_getdns_netreq_change_state(netreq, NET_REQ_ERRORED);
_getdns_check_dns_req_complete(netreq->owner);
}
_getdns_upstream_shutdown(upstream);
}
void
_getdns_cancel_stub_request(getdns_network_req *netreq)
{
DEBUG_STUB("%s %-35s: MSG: %p\n",
STUB_DEBUG_CLEANUP, __FUNC__, (void*)netreq);
stub_cleanup(netreq);
if (netreq->fd >= 0) {
_getdns_closesocket(netreq->fd);
netreq->fd = -1;
}
}
static void
stub_timeout_cb(void *userarg)
{
getdns_network_req *netreq = (getdns_network_req *)userarg;
DEBUG_STUB("%s %-35s: MSG: %p\n",
STUB_DEBUG_CLEANUP, __FUNC__, (void*)netreq);
stub_cleanup(netreq);
_getdns_netreq_change_state(netreq, NET_REQ_TIMED_OUT);
/* Handle upstream*/
if (netreq->fd >= 0) {
_getdns_closesocket(netreq->fd);
netreq->fd = -1;
netreq->upstream->udp_timeouts++;
if (netreq->upstream->udp_timeouts % 100 == 0)
_getdns_upstream_log(netreq->upstream, GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_INFO,
"%-40s : Upstream : UDP - Resps=%6d, Timeouts =%6d (logged every 100 responses)\n",
netreq->upstream->addr_str,
(int)netreq->upstream->udp_responses, (int)netreq->upstream->udp_timeouts);
stub_next_upstream(netreq);
} else {
netreq->upstream->responses_timeouts++;
}
if (netreq->owner->user_callback) {
netreq->debug_end_time = _getdns_get_time_as_uintt64();
/* Note this calls cancel_request which calls stub_cleanup again....!*/
_getdns_context_request_timed_out(netreq->owner);
} else
_getdns_check_dns_req_complete(netreq->owner);
}
static void
upstream_idle_timeout_cb(void *userarg)
{
getdns_upstream *upstream = (getdns_upstream *)userarg;
DEBUG_STUB("%s %-35s: FD: %d Closing connection\n",
STUB_DEBUG_CLEANUP, __FUNC__, upstream->fd);
GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event);
upstream->event.timeout_cb = NULL;
upstream->event.read_cb = NULL;
upstream->event.write_cb = NULL;
_getdns_upstream_shutdown(upstream);
}
static void
upstream_setup_timeout_cb(void *userarg)
{
getdns_upstream *upstream = (getdns_upstream *)userarg;
DEBUG_STUB("%s %-35s: FD: %d\n",
STUB_DEBUG_CLEANUP, __FUNC__, upstream->fd);
upstream_failed(upstream, 1);
}
/****************************/
/* TCP read/write functions */
/****************************/
static int
stub_tcp_read(int fd, getdns_tcp_state *tcp, struct mem_funcs *mf)
{
ssize_t read;
uint8_t *buf;
size_t buf_size;
if (!tcp->read_buf) {
/* First time tcp read, create a buffer for reading */
if (!(tcp->read_buf = GETDNS_XMALLOC(*mf, uint8_t, 4096)))
return STUB_TCP_ERROR;
tcp->read_buf_len = 4096;
tcp->read_pos = tcp->read_buf;
tcp->to_read = 2; /* Packet size */
}
read = recv(fd, (void *)tcp->read_pos, tcp->to_read, 0);
if (read < 0) {
if (_getdns_socketerror_wants_retry())
return STUB_TCP_RETRY;
else
return STUB_TCP_ERROR;
} else if (read == 0) {
/* Remote end closed the socket */
/* TODO: Try to reconnect */
return STUB_TCP_ERROR;
} else if ((size_t)read > tcp->to_read) {
return STUB_TCP_ERROR;
}
tcp->to_read -= read;
tcp->read_pos += read;
if (tcp->to_read > 0)
return STUB_TCP_MORE_TO_READ;
read = tcp->read_pos - tcp->read_buf;
if (read == 2) {
/* Read the packet size short */
tcp->to_read = gldns_read_uint16(tcp->read_buf);
if (tcp->to_read < GLDNS_HEADER_SIZE)
return STUB_TCP_ERROR;
/* Resize our buffer if needed */
if (tcp->to_read > tcp->read_buf_len) {
buf_size = tcp->read_buf_len;
while (tcp->to_read > buf_size)
buf_size *= 2;
if (!(buf = GETDNS_XREALLOC(*mf,
tcp->read_buf, uint8_t, buf_size)))
return STUB_TCP_ERROR;
tcp->read_buf = buf;
tcp->read_buf_len = buf_size;
}
/* Ready to start reading the packet */
tcp->read_pos = tcp->read_buf;
return STUB_TCP_MORE_TO_READ;
}
return GLDNS_ID_WIRE(tcp->read_buf);
}
/* stub_tcp_write(fd, tcp, netreq)
* will return STUB_TCP_RETRY or STUB_TCP_MORE_TO_WRITE when we need to come
* back again, STUB_TCP_ERROR on error and a query_id on successful sent.
*/
static int
stub_tcp_write(int fd, getdns_tcp_state *tcp, getdns_network_req *netreq)
{
size_t pkt_len;
ssize_t written;
uint16_t query_id;
intptr_t query_id_intptr;
int q = tcp_connected(netreq->upstream);
if (q != 0)
return q;
netreq->debug_udp = 0;
/* Do we have remaining data that we could not write before? */
if (! tcp->write_buf) {
/* No, this is an initial write. Try to send
*/
do {
query_id = arc4random();
query_id_intptr = (intptr_t)query_id;
netreq->node.key = (void *)query_id_intptr;
} while (!_getdns_rbtree_insert(
&netreq->upstream->netreq_by_query_id, &netreq->node));
netreq->query_id_registered = &netreq->upstream->netreq_by_query_id;
GLDNS_ID_SET(netreq->query, query_id);
if (netreq->opt) {
_getdns_network_req_clear_upstream_options(netreq);
/* no limits on the max udp payload size with tcp */
gldns_write_uint16(netreq->opt + 3, 65535);
if (netreq->owner->edns_cookies)
if (attach_edns_cookie(netreq))
return STUB_OUT_OF_OPTIONS;
if (netreq->owner->edns_client_subnet_private)
if (attach_edns_client_subnet_private(netreq))
return STUB_OUT_OF_OPTIONS;
if (netreq->upstream->queries_sent == 0 &&
netreq->owner->context->idle_timeout != 0) {
/* Add the keepalive option to the first query on this connection*/
DEBUG_STUB("%s %-35s: FD: %d Requesting keepalive \n",
STUB_DEBUG_WRITE, __FUNC__, fd);
if (attach_edns_keepalive(netreq))
return STUB_OUT_OF_OPTIONS;
netreq->keepalive_sent = 1;
}
}
pkt_len = _getdns_network_req_add_tsig(netreq);
/* We have an initialized packet buffer.
* Lets see how much of it we can write
*/
/* We use sendto() here which will do both a connect and send */
#ifdef USE_TCP_FASTOPEN
written = sendto(fd, netreq->query - 2, pkt_len + 2,
MSG_FASTOPEN, (struct sockaddr *)&(netreq->upstream->addr),
netreq->upstream->addr_len);
/* If pipelining we will find that the connection is already up so
just fall back to a 'normal' write. */
if (written == -1 && _getdns_socketerror() == _getdns_EISCONN)
written = write(fd, netreq->query - 2, pkt_len + 2);
#else
written = send(fd, (const char *)(netreq->query - 2), pkt_len + 2, 0);
#endif
if ((written == -1 && _getdns_socketerror_wants_retry()) ||
(size_t)written < pkt_len + 2) {
/* We couldn't write the whole packet.
* Setup tcp to track the state.
*/
tcp->write_buf = netreq->query - 2;
tcp->write_buf_len = pkt_len + 2;
tcp->written = written >= 0 ? written : 0;
return written == -1
? STUB_TCP_RETRY
: STUB_TCP_MORE_TO_WRITE;
} else if (written == -1) {
DEBUG_STUB("%s %-35s: MSG: %p error while writing to TCP socket:"
" %s\n", STUB_DEBUG_WRITE, __FUNC__, (void*)netreq
, _getdns_errnostr());
return STUB_TCP_ERROR;
}
/* We were able to write everything! Start reading. */
return (int) query_id;
} else {/* if (! tcp->write_buf) */
/* Coming back from an earlier unfinished write or handshake.
* Try to send remaining data */
written = send(fd, (void *)(tcp->write_buf + tcp->written),
tcp->write_buf_len - tcp->written, 0);
if (written == -1) {
if (_getdns_socketerror_wants_retry())
return STUB_TCP_RETRY;
else {
DEBUG_STUB("%s %-35s: MSG: %p error while writing to TCP socket:"
" %s\n", STUB_DEBUG_WRITE, __FUNC__, (void*)netreq
, _getdns_errnostr());
return STUB_TCP_ERROR;
}
}
tcp->written += written;
if (tcp->written < tcp->write_buf_len)
/* Still more to send */
return STUB_TCP_MORE_TO_WRITE;
query_id = (int)GLDNS_ID_WIRE(tcp->write_buf + 2);
/* Done. Start reading */
tcp->write_buf = NULL;
return query_id;
} /* if (! tcp->write_buf) */
}
/*************************/
/* TLS Utility functions */
/*************************/
static int
tls_requested(getdns_network_req *netreq)
{
return (netreq->transports[netreq->transport_current] ==
GETDNS_TRANSPORT_TLS) ?
1 : 0;
}
static _getdns_tls_connection*
tls_create_object(getdns_dns_req *dnsreq, int fd, getdns_upstream *upstream)
{
/* Create SSL instance and connect with a file descriptor */
getdns_context *context = dnsreq->context;
if (context->tls_ctx == NULL)
return NULL;
_getdns_tls_connection* tls = _getdns_tls_connection_new(context->tls_ctx, fd);
if(!tls)
return NULL;
#if HAVE_TLS_CONN_CURVES_LIST
if (upstream->tls_curves_list)
_getdns_tls_connection_set_curves_list(tls, upstream->tls_curves_list);
#endif
/* make sure we'll be able to find the context again when we need it */
if (_getdns_associate_upstream_with_connection(tls, upstream) != GETDNS_RETURN_GOOD) {
_getdns_tls_connection_free(tls);
return NULL;
}
/* NOTE: this code will fallback on a given upstream, without trying
authentication on other upstreams first. This is non-optimal and but avoids
multiple TLS handshakes before getting a usable connection. */
upstream->tls_fallback_ok = 0;
/* If we have a hostname, always use it */
if (upstream->tls_auth_name[0] != '\0') {
/*Request certificate for the auth_name*/
DEBUG_STUB("%s %-35s: Hostname verification requested for: %s\n",
STUB_DEBUG_SETUP_TLS, __FUNC__, upstream->tls_auth_name);
_getdns_tls_connection_setup_hostname_auth(tls, upstream->tls_auth_name);
/* Allow fallback to opportunistic if settings permit it*/
if (dnsreq->netreqs[0]->tls_auth_min != GETDNS_AUTHENTICATION_REQUIRED)
upstream->tls_fallback_ok = 1;
} else {
/* Lack of host name is OK unless only authenticated
* TLS is specified and we have no pubkey_pinset */
if (dnsreq->netreqs[0]->tls_auth_min == GETDNS_AUTHENTICATION_REQUIRED) {
if (upstream->tls_pubkey_pinset) {
DEBUG_STUB("%s %-35s: Proceeding with only pubkey pinning authentication\n",
STUB_DEBUG_SETUP_TLS, __FUNC__);
} else {
DEBUG_STUB("%s %-35s: ERROR:No auth name or pinset provided for this upstream for Strict TLS authentication\n",
STUB_DEBUG_SETUP_TLS, __FUNC__);
_getdns_upstream_log(upstream, GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_ERR,
"%-40s : Verify fail: *CONFIG ERROR* - No auth name or pinset provided for this upstream for Strict TLS authentication\n",
upstream->addr_str);
upstream->tls_hs_state = GETDNS_HS_FAILED;
_getdns_tls_connection_free(tls);
upstream->tls_auth_state = GETDNS_AUTH_FAILED;
return NULL;
}
} else {
/* no hostname verification, so we will make opportunistic connections */
DEBUG_STUB("%s %-35s: Proceeding even though no hostname provided!\n",
STUB_DEBUG_SETUP_TLS, __FUNC__);
upstream->tls_fallback_ok = 1;
}
}
if (upstream->tls_fallback_ok) {
_getdns_tls_connection_set_cipher_list(tls, "DEFAULT");
DEBUG_STUB("%s %-35s: WARNING: Using Oppotunistic TLS (fallback allowed)!\n",
STUB_DEBUG_SETUP_TLS, __FUNC__);
} else {
if (upstream->tls_cipher_list)
_getdns_tls_connection_set_cipher_list(tls, upstream->tls_cipher_list);
DEBUG_STUB("%s %-35s: Using Strict TLS \n", STUB_DEBUG_SETUP_TLS,
__FUNC__);
}
_getdns_tls_connection_set_host_pinset(tls, upstream->tls_auth_name, upstream->tls_pubkey_pinset);
/* Session resumption. There are trade-offs here. Want to do it when
possible only if we have the right type of connection. Note a change
to the upstream auth info creates a new upstream so never re-uses.*/
if (upstream->tls_session != NULL) {
if ((upstream->tls_fallback_ok == 0 &&
upstream->last_tls_auth_state == GETDNS_AUTH_OK) ||
upstream->tls_fallback_ok == 1) {
_getdns_tls_connection_set_session(tls, upstream->tls_session);
DEBUG_STUB("%s %-35s: Attempting session re-use\n", STUB_DEBUG_SETUP_TLS,
__FUNC__);
}
}
return tls;
}
static int
tls_do_handshake(getdns_upstream *upstream)
{
DEBUG_STUB("%s %-35s: FD: %d \n", STUB_DEBUG_SETUP_TLS,
__FUNC__, upstream->fd);
int r;
while ((r = _getdns_tls_connection_do_handshake(upstream->tls_obj)) != GETDNS_RETURN_GOOD)
{
switch (r) {
case GETDNS_RETURN_TLS_WANT_READ:
GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event);
upstream->event.read_cb = upstream_read_cb;
upstream->event.write_cb = NULL;
GETDNS_SCHEDULE_EVENT(upstream->loop,
upstream->fd, TIMEOUT_TLS, &upstream->event);
upstream->tls_hs_state = GETDNS_HS_READ;
return STUB_TCP_RETRY;
case GETDNS_RETURN_TLS_WANT_WRITE:
GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event);
upstream->event.read_cb = NULL;
upstream->event.write_cb = upstream_write_cb;
GETDNS_SCHEDULE_EVENT(upstream->loop,
upstream->fd, TIMEOUT_TLS, &upstream->event);
upstream->tls_hs_state = GETDNS_HS_WRITE;
return STUB_TCP_RETRY;
default:
DEBUG_STUB("%s %-35s: FD: %d Handshake failed %d\n",
STUB_DEBUG_SETUP_TLS, __FUNC__, upstream->fd,
want);
return STUB_SETUP_ERROR;
}
}
/* A re-used session is not verified so need to fix up state in that case */
if (!_getdns_tls_connection_is_session_reused(upstream->tls_obj))
upstream->tls_auth_state = upstream->last_tls_auth_state;
else if (upstream->tls_pubkey_pinset || upstream->tls_auth_name[0]) {
_getdns_tls_x509* peer_cert = _getdns_tls_connection_get_peer_certificate(upstream->tls_obj);
if (!peer_cert) {
_getdns_upstream_log(upstream,
GETDNS_LOG_UPSTREAM_STATS,
( upstream->tls_fallback_ok
? GETDNS_LOG_INFO : GETDNS_LOG_ERR),
"%-40s : Verify failed : TLS - %s - "
"Remote did not offer certificate\n",
upstream->addr_str,
( upstream->tls_fallback_ok
? "Tolerated because of Opportunistic profile"
: "*Failure*" ));
upstream->tls_auth_state = GETDNS_AUTH_FAILED;
} else {
long verify_errno;
const char* verify_errmsg;
if (!_getdns_tls_connection_verify(upstream->tls_obj, &verify_errno, &verify_errmsg)) {
upstream->tls_auth_state = GETDNS_AUTH_OK;
if (verify_errno != 0) {
_getdns_upstream_log(upstream,
GETDNS_LOG_UPSTREAM_STATS,
( upstream->tls_fallback_ok
? GETDNS_LOG_INFO : GETDNS_LOG_ERR), "%-40s : Verify failed : TLS - %s - "
"(%d) \"%s\"\n", upstream->addr_str,
( upstream->tls_fallback_ok
? "Tolerated because of Opportunistic profile"
: "*Failure*" ),
verify_errno, verify_errmsg);
} else {
_getdns_upstream_log(upstream,
GETDNS_LOG_UPSTREAM_STATS,
( upstream->tls_fallback_ok
? GETDNS_LOG_INFO : GETDNS_LOG_ERR), "%-40s : Verify failed : TLS - %s - "
"%s\n", upstream->addr_str,
( upstream->tls_fallback_ok
? "Tolerated because of Opportunistic profile"
: "*Failure*" ),
verify_errno, verify_errmsg);
}
} else {
_getdns_upstream_log(upstream,
GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_DEBUG,
"%-40s : Verify passed : TLS\n",
upstream->addr_str);
}
_getdns_tls_x509_free(peer_cert);
}
if (upstream->tls_auth_state == GETDNS_AUTH_FAILED
&& !upstream->tls_fallback_ok)
return STUB_SETUP_ERROR;
}
DEBUG_STUB("%s %-35s: FD: %d Handshake succeeded with auth state %s. Session is %s.\n",
STUB_DEBUG_SETUP_TLS, __FUNC__, upstream->fd,
_getdns_auth_str(upstream->tls_auth_state),
_getdns_tls_connection_is_session_reused(upstream->tls_obj) ? "new" : "re-used");
upstream->tls_hs_state = GETDNS_HS_DONE;
upstream->conn_state = GETDNS_CONN_OPEN;
upstream->conn_completed++;
if (upstream->tls_session != NULL)
_getdns_tls_session_free(upstream->tls_session);
upstream->tls_session = _getdns_tls_connection_get_session(upstream->tls_obj);
/* Reset timeout on success*/
GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event);
upstream->event.read_cb = NULL;
upstream->event.write_cb = upstream_write_cb;
GETDNS_SCHEDULE_EVENT(upstream->loop, upstream->fd, TIMEOUT_FOREVER,
getdns_eventloop_event_init(&upstream->event, upstream,
NULL, upstream_write_cb, NULL));
return 0;
}
static int
tls_connected(getdns_upstream* upstream)
{
/* Already have a TLS connection*/
if (upstream->tls_hs_state == GETDNS_HS_DONE)
return 0;
/* Already tried and failed, so let the fallback code take care of things */
if (upstream->tls_hs_state == GETDNS_HS_FAILED)
return STUB_SETUP_ERROR;
/* Lets make sure the TCP connection is up before we try a handshake*/
int q = tcp_connected(upstream);
if (q != 0)
return q;
return tls_do_handshake(upstream);
}
/***************************/
/* TLS read/write functions*/
/***************************/
static int
stub_tls_read(getdns_upstream *upstream, getdns_tcp_state *tcp,
struct mem_funcs *mf)
{
size_t read;
uint8_t *buf;
size_t buf_size;
_getdns_tls_connection* tls_obj = upstream->tls_obj;
int q = tls_connected(upstream);
if (q != 0)
return q;
if (!tcp->read_buf) {
/* First time tls read, create a buffer for reading */
if (!(tcp->read_buf = GETDNS_XMALLOC(*mf, uint8_t, 4096)))
return STUB_TCP_ERROR;
tcp->read_buf_len = 4096;
tcp->read_pos = tcp->read_buf;
tcp->to_read = 2; /* Packet size */
}
switch ((int)_getdns_tls_connection_read(tls_obj, tcp->read_pos, tcp->to_read, &read)) {
case GETDNS_RETURN_GOOD:
break;
case GETDNS_RETURN_TLS_WANT_READ:
return STUB_TCP_RETRY; /* Come back later */
default:
/* TODO[TLS]: Handle GETDNS_RETURN_TLS_WANT_WRITE which means handshake
renegotiation. Need to keep handshake state to do that.*/
return STUB_TCP_ERROR;
}
tcp->to_read -= read;
tcp->read_pos += read;
if ((int)tcp->to_read > 0)
return STUB_TCP_MORE_TO_READ;
read = tcp->read_pos - tcp->read_buf;
if (read == 2) {
/* Read the packet size short */
tcp->to_read = gldns_read_uint16(tcp->read_buf);
if (tcp->to_read < GLDNS_HEADER_SIZE)
return STUB_TCP_ERROR;
/* Resize our buffer if needed */
if (tcp->to_read > tcp->read_buf_len) {
buf_size = tcp->read_buf_len;
while (tcp->to_read > buf_size)
buf_size *= 2;
if (!(buf = GETDNS_XREALLOC(*mf,
tcp->read_buf, uint8_t, buf_size)))
return STUB_TCP_ERROR;
tcp->read_buf = buf;
tcp->read_buf_len = buf_size;
}
/* Ready to start reading the packet */
tcp->read_pos = tcp->read_buf;
switch ((int)_getdns_tls_connection_read(tls_obj, tcp->read_pos, tcp->to_read, &read)) {
case GETDNS_RETURN_GOOD:
break;
case GETDNS_RETURN_TLS_WANT_READ:
return STUB_TCP_RETRY; /* Come back later */
default:
/* TODO[TLS]: Handle GETDNS_RETURN_TLS_WANT_WRITE which means handshake
renegotiation. Need to keep handshake state to do that.*/
return STUB_TCP_ERROR;
}
tcp->to_read -= read;
tcp->read_pos += read;
if ((int)tcp->to_read > 0)
return STUB_TCP_MORE_TO_READ;
}
return GLDNS_ID_WIRE(tcp->read_buf);
}
static int
stub_tls_write(getdns_upstream *upstream, getdns_tcp_state *tcp,
getdns_network_req *netreq)
{
size_t pkt_len;
size_t written;
uint16_t query_id;
intptr_t query_id_intptr;
_getdns_tls_connection* tls_obj = upstream->tls_obj;
uint16_t padding_sz;
int q = tls_connected(upstream);
if (q != 0)
return q;
/* This is the case where the upstream is connected but it isn't an authenticated
connection, but the request needs an authenticated connection. For now, we
fail the write as a special case, since other oppotunistic requests can still use
this upstream. but this needs more thought: Should we open a second connection? */
if (!upstream_auth_status_ok(upstream, netreq))
return STUB_NO_AUTH;
/* Do we have remaining data that we could not write before? */
if (! tcp->write_buf) {
/* No, this is an initial write. Try to send
*/
/* Find a unique query_id not already written (or in
* the write_queue) for that upstream. Register this netreq
* by query_id in the process.
*/
do {
query_id = arc4random();
query_id_intptr = (intptr_t)query_id;
netreq->node.key = (void *)query_id_intptr;
} while (!_getdns_rbtree_insert(
&netreq->upstream->netreq_by_query_id, &netreq->node));
netreq->query_id_registered = &netreq->upstream->netreq_by_query_id;
GLDNS_ID_SET(netreq->query, query_id);
/* TODO: Review if more EDNS0 handling can be centralised.*/
if (netreq->opt) {
_getdns_network_req_clear_upstream_options(netreq);
/* no limits on the max udp payload size with tcp */
gldns_write_uint16(netreq->opt + 3, 65535);
/* we do not edns_cookie over TLS, since TLS
* provides stronger guarantees than cookies
* already */
if (netreq->owner->edns_client_subnet_private)
if (attach_edns_client_subnet_private(netreq))
return STUB_OUT_OF_OPTIONS;
if (netreq->upstream->queries_sent % EDNS_KEEPALIVE_RESEND == 0 &&
netreq->owner->context->idle_timeout != 0) {
/* Add the keepalive option to every nth query on this
connection */
DEBUG_STUB("%s %-35s: FD: %d Requesting keepalive \n",
STUB_DEBUG_SETUP, __FUNC__, upstream->fd);
if (attach_edns_keepalive(netreq))
return STUB_OUT_OF_OPTIONS;
netreq->keepalive_sent = 1;
}
if (netreq->owner->tls_query_padding_blocksize > 0) {
uint16_t blksz = netreq->owner->tls_query_padding_blocksize;
if (blksz == 1) /* use a sensible default policy */
blksz = 128;
pkt_len = netreq->response - netreq->query;
pkt_len += 4; /* this accounts for the OPTION-CODE and OPTION-LENGTH of the padding */
padding_sz = pkt_len % blksz;
if (padding_sz)
padding_sz = blksz - padding_sz;
if (_getdns_network_req_add_upstream_option(netreq,
EDNS_PADDING_OPCODE,
padding_sz, NULL))
return STUB_OUT_OF_OPTIONS;
}
}
pkt_len = _getdns_network_req_add_tsig(netreq);
/* We have an initialized packet buffer.
* Lets see how much of it we can write */
/* TODO[TLS]: Handle error cases, partial writes, renegotiation etc. */
#if INTERCEPT_COM_DS
/* Intercept and do not sent out COM DS queries. For debugging
* purposes only. Never commit with this turned on.
*/
if (netreq->request_type == GETDNS_RRTYPE_DS &&
netreq->owner->name_len == 5 &&
netreq->owner->name[0] == 3 &&
(netreq->owner->name[1] & 0xDF) == 'C' &&
(netreq->owner->name[2] & 0xDF) == 'O' &&
(netreq->owner->name[3] & 0xDF) == 'M' &&
netreq->owner->name[4] == 0) {
debug_req("Intercepting", netreq);
written = pkt_len + 2;
} else
#endif
switch ((int)_getdns_tls_connection_write(tls_obj, netreq->query - 2, pkt_len + 2, &written)) {
case GETDNS_RETURN_GOOD:
break;
case GETDNS_RETURN_TLS_WANT_READ:
case GETDNS_RETURN_TLS_WANT_WRITE:
return STUB_TCP_RETRY;
default:
return STUB_TCP_ERROR;
}
/* We were able to write everything! Start reading. */
return (int) query_id;
}
return STUB_TCP_ERROR;
}
uint64_t
_getdns_get_time_as_uintt64() {
struct timeval tv;
uint64_t now;
if (gettimeofday(&tv, NULL)) {
return 0;
}
now = tv.tv_sec * 1000000 + tv.tv_usec;
return now;
}
/**************************/
/* UDP callback functions */
/**************************/
static void
stub_udp_read_cb(void *userarg)
{
getdns_network_req *netreq = (getdns_network_req *)userarg;
getdns_dns_req *dnsreq = netreq->owner;
getdns_upstream *upstream = netreq->upstream;
ssize_t read;
DEBUG_STUB("%s %-35s: MSG: %p \n", STUB_DEBUG_READ,
__FUNC__, (void*)netreq);
read = recvfrom(netreq->fd, (void *)netreq->response,
netreq->max_udp_payload_size + 1, /* If read == max_udp_payload_size
* then all is good. If read ==
* max_udp_payload_size + 1, then
* we receive more then requested!
* i.e. overflow
*/
0, NULL, NULL);
if (read == -1 && (_getdns_socketerror_wants_retry() ||
_getdns_socketerror() == _getdns_ECONNRESET))
return; /* Try again later */
if (read == -1) {
DEBUG_STUB("%s %-35s: MSG: %p error while reading from socket:"
" %s\n", STUB_DEBUG_READ, __FUNC__, (void*)netreq
, _getdns_errnostr());
stub_cleanup(netreq);
_getdns_netreq_change_state(netreq, NET_REQ_ERRORED);
/* Handle upstream*/
if (netreq->fd >= 0) {
_getdns_closesocket(netreq->fd);
netreq->fd = -1;
stub_next_upstream(netreq);
}
netreq->debug_end_time = _getdns_get_time_as_uintt64();
_getdns_check_dns_req_complete(netreq->owner);
return;
}
if (read < GLDNS_HEADER_SIZE)
return; /* Not DNS */
if (GLDNS_ID_WIRE(netreq->response) != GLDNS_ID_WIRE(netreq->query))
return; /* Cache poisoning attempt ;) */
if (netreq->owner->edns_cookies && match_and_process_server_cookie(
upstream, netreq->response, read))
return; /* Client cookie didn't match? */
GETDNS_CLEAR_EVENT(dnsreq->loop, &netreq->event);
_getdns_closesocket(netreq->fd);
netreq->fd = -1;
while (GLDNS_TC_WIRE(netreq->response)) {
DEBUG_STUB("%s %-35s: MSG: %p TC bit set in response \n", STUB_DEBUG_READ,
__FUNC__, (void*)netreq);
if (!(netreq->transport_current < netreq->transport_count))
break;
getdns_transport_list_t next_transport =
netreq->transports[++netreq->transport_current];
if (next_transport != GETDNS_TRANSPORT_TCP &&
next_transport != GETDNS_TRANSPORT_TLS)
break;
/* For now, special case where fallback should be on the same upstream*/
if ((netreq->fd = upstream_connect(upstream, next_transport,
dnsreq)) == -1)
break;
upstream_schedule_netreq(netreq->upstream, netreq);
GETDNS_SCHEDULE_EVENT(dnsreq->loop, -1,
_getdns_ms_until_expiry(dnsreq->expires),
getdns_eventloop_event_init(&netreq->event,
netreq, NULL, NULL, stub_timeout_cb));
return;
}
netreq->response_len = read;
if (!dnsreq->context->round_robin_upstreams)
dnsreq->upstreams->current_udp = 0;
else {
dnsreq->upstreams->current_udp+=GETDNS_UPSTREAM_TRANSPORTS;
if (dnsreq->upstreams->current_udp >= dnsreq->upstreams->count)
dnsreq->upstreams->current_udp = 0;
}
netreq->debug_end_time = _getdns_get_time_as_uintt64();
_getdns_netreq_change_state(netreq, NET_REQ_FINISHED);
upstream->udp_responses++;
upstream->back_off = 1;
if (upstream->udp_responses == 1 ||
upstream->udp_responses % 100 == 0)
_getdns_upstream_log(upstream, GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_INFO,
"%-40s : Upstream : UDP - Resps=%6d, Timeouts =%6d (logged every 100 responses)\n",
upstream->addr_str,
(int)upstream->udp_responses, (int)upstream->udp_timeouts);
_getdns_check_dns_req_complete(dnsreq);
}
static void
stub_udp_write_cb(void *userarg)
{
getdns_network_req *netreq = (getdns_network_req *)userarg;
getdns_dns_req *dnsreq = netreq->owner;
size_t pkt_len;
ssize_t written;
DEBUG_STUB("%s %-35s: MSG: %p \n", STUB_DEBUG_WRITE,
__FUNC__, (void *)netreq);
GETDNS_CLEAR_EVENT(dnsreq->loop, &netreq->event);
netreq->debug_start_time = _getdns_get_time_as_uintt64();
netreq->debug_udp = 1;
GLDNS_ID_SET(netreq->query, (uint16_t)arc4random());
if (netreq->opt) {
_getdns_network_req_clear_upstream_options(netreq);
if (netreq->edns_maximum_udp_payload_size == -1)
gldns_write_uint16(netreq->opt + 3,
( netreq->max_udp_payload_size =
netreq->upstream->addr.ss_family == AF_INET6
? 1232 : 1432));
if (netreq->owner->edns_cookies)
if (attach_edns_cookie(netreq))
return; /* too many upstream options */
if (netreq->owner->edns_client_subnet_private)
if (attach_edns_client_subnet_private(netreq))
return; /* too many upstream options */
}
pkt_len = _getdns_network_req_add_tsig(netreq);
if ((ssize_t)pkt_len != (written = sendto(
netreq->fd, (const void *)netreq->query, pkt_len, 0,
(struct sockaddr *)&netreq->upstream->addr,
netreq->upstream->addr_len))) {
#if defined(STUB_DEBUG) && STUB_DEBUG
if (written == -1)
DEBUG_STUB( "%s %-35s: MSG: %p error: %s\n"
, STUB_DEBUG_WRITE, __FUNC__, (void *)netreq
, _getdns_errnostr());
else
DEBUG_STUB( "%s %-35s: MSG: %p returned: %d, expected: %d\n"
, STUB_DEBUG_WRITE, __FUNC__, (void *)netreq
, (int)written, (int)pkt_len);
#endif
stub_cleanup(netreq);
_getdns_netreq_change_state(netreq, NET_REQ_ERRORED);
/* Handle upstream*/
if (netreq->fd >= 0) {
_getdns_closesocket(netreq->fd);
netreq->fd = -1;
stub_next_upstream(netreq);
}
netreq->debug_end_time = _getdns_get_time_as_uintt64();
_getdns_check_dns_req_complete(netreq->owner);
return;
}
GETDNS_SCHEDULE_EVENT(dnsreq->loop, netreq->fd,
_getdns_ms_until_expiry(dnsreq->expires),
getdns_eventloop_event_init(&netreq->event, netreq,
stub_udp_read_cb, NULL, stub_timeout_cb));
}
/**************************/
/* Upstream callback functions*/
/**************************/
static void
process_finished_cb(void *userarg)
{
getdns_upstream *upstream = (getdns_upstream *)userarg;
getdns_dns_req *dnsreq;
/* Upstream->loop is always the async one, because finished_events
* are only scheduled against (and thus fired from) the async loop
*/
GETDNS_CLEAR_EVENT(upstream->loop, &upstream->finished_event);
upstream->finished_event.timeout_cb = NULL;
while (upstream->finished_dnsreqs) {
dnsreq = upstream->finished_dnsreqs;
upstream->finished_dnsreqs = dnsreq->finished_next;
_getdns_check_dns_req_complete(dnsreq);
}
}
static void
upstream_read_cb(void *userarg)
{
getdns_upstream *upstream = (getdns_upstream *)userarg;
DEBUG_STUB("%s %-35s: FD: %d \n", STUB_DEBUG_READ, __FUNC__,
upstream->fd);
getdns_network_req *netreq;
int q;
uint16_t query_id;
intptr_t query_id_intptr;
getdns_dns_req *dnsreq;
if (upstream->transport == GETDNS_TRANSPORT_TLS)
q = stub_tls_read(upstream, &upstream->tcp,
&upstream->upstreams->mf);
else
q = stub_tcp_read(upstream->fd, &upstream->tcp,
&upstream->upstreams->mf);
switch (q) {
case STUB_TCP_MORE_TO_READ:
/* WSA TODO: if callback is still upstream_read_cb, do it again
*/
case STUB_TCP_RETRY:
return;
case STUB_SETUP_ERROR: /* Can happen for TLS HS*/
case STUB_TCP_ERROR:
upstream_failed(upstream, (q == STUB_TCP_ERROR ? 0:1) );
return;
default:
/* Lookup netreq */
query_id = (uint16_t) q;
query_id_intptr = (intptr_t) query_id;
netreq = (getdns_network_req *)_getdns_rbtree_delete(
&upstream->netreq_by_query_id, (void *)query_id_intptr);
if (! netreq) /* maybe canceled */ {
/* reset read buffer */
upstream->tcp.read_pos = upstream->tcp.read_buf;
upstream->tcp.to_read = 2;
return;
}
if (netreq->query_id_registered == &upstream->netreq_by_query_id) {
netreq->query_id_registered = NULL;
netreq->node.key = NULL;
} else if (netreq->query_id_registered) {
(void) _getdns_rbtree_delete(
netreq->query_id_registered, netreq->node.key);
netreq->query_id_registered = NULL;
netreq->node.key = NULL;
}
DEBUG_STUB("%s %-35s: MSG: %p (read)\n",
STUB_DEBUG_READ, __FUNC__, (void*)netreq);
_getdns_netreq_change_state(netreq, NET_REQ_FINISHED);
netreq->response = upstream->tcp.read_buf;
netreq->response_len =
upstream->tcp.read_pos - upstream->tcp.read_buf;
upstream->tcp.read_buf = NULL;
upstream->responses_received++;
/* !THIS CODE NEEDS TESTING! */
if (netreq->owner->edns_cookies &&
match_and_process_server_cookie(
netreq->upstream, upstream->tcp.read_buf,
upstream->tcp.read_pos - upstream->tcp.read_buf))
return; /* Client cookie didn't match (or FORMERR) */
if (netreq->owner->context->idle_timeout != 0)
process_keepalive(netreq->upstream, netreq, netreq->response,
netreq->response_len);
netreq->debug_end_time = _getdns_get_time_as_uintt64();
/* This also reschedules events for the upstream*/
stub_cleanup(netreq);
if (!upstream->is_sync_loop || netreq->owner->is_sync_request)
_getdns_check_dns_req_complete(netreq->owner);
else {
assert(upstream->is_sync_loop &&
!netreq->owner->is_sync_request);
/* We have a result for an asynchronously scheduled
* netreq, while processing the synchronous loop.
* Queue dns_req_complete checks.
*/
/* First check if one for the dns_req already exists */
for ( dnsreq = upstream->finished_dnsreqs
; dnsreq && dnsreq != netreq->owner
; dnsreq = dnsreq->finished_next)
; /* pass */
if (!dnsreq) {
/* Schedule dns_req_complete check for this
* netreq's owner
*/
dnsreq = netreq->owner;
dnsreq->finished_next =
upstream->finished_dnsreqs;
upstream->finished_dnsreqs = dnsreq;
if (!upstream->finished_event.timeout_cb) {
upstream->finished_event.timeout_cb
= process_finished_cb;
GETDNS_SCHEDULE_EVENT(
dnsreq->context->extension,
-1, 1, &upstream->finished_event);
}
}
}
/* WSA TODO: if callback is still upstream_read_cb, do it again
*/
return;
}
}
static void
upstream_write_cb(void *userarg)
{
getdns_upstream *upstream = (getdns_upstream *)userarg;
getdns_network_req *netreq = upstream->write_queue;
int q;
_getdns_tls_x509 *cert;
if (!netreq) {
GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event);
upstream->event.write_cb = NULL;
return;
}
netreq->debug_start_time = _getdns_get_time_as_uintt64();
DEBUG_STUB("%s %-35s: MSG: %p (writing)\n", STUB_DEBUG_WRITE,
__FUNC__, (void*)netreq);
/* Health checks on current connection */
if (upstream->conn_state == GETDNS_CONN_TEARDOWN ||
upstream->conn_state == GETDNS_CONN_CLOSED ||
upstream->fd == -1)
q = STUB_CONN_GONE;
else if (!upstream_working_ok(upstream))
q = STUB_TCP_ERROR;
/* Seems ok, now try to write */
else if (tls_requested(netreq))
q = stub_tls_write(upstream, &upstream->tcp, netreq);
else
q = stub_tcp_write(upstream->fd, &upstream->tcp, netreq);
switch (q) {
case STUB_TCP_MORE_TO_WRITE:
/* WSA TODO: if callback is still upstream_write_cb, do it again
*/
case STUB_TCP_RETRY:
return;
case STUB_OUT_OF_OPTIONS:
case STUB_TCP_ERROR:
/* New problem with the TCP connection itself. Need to fallback.*/
/* Fall through */
case STUB_SETUP_ERROR:
/* Could not complete the set up. Need to fallback.*/
DEBUG_STUB("%s %-35s: Upstream: %p ERROR = %d\n", STUB_DEBUG_WRITE,
__FUNC__, (void*)userarg, q);
upstream_failed(upstream, (q == STUB_TCP_ERROR ? 0:1));
return;
case STUB_CONN_GONE:
case STUB_NO_AUTH:
/* Cleaning up after connection or auth check failure. Need to fallback. */
stub_cleanup(netreq);
_getdns_upstream_log(upstream, GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_DEBUG,
"%-40s : Conn closed: %s - *Failure*\n",
upstream->addr_str,
(upstream->transport == GETDNS_TRANSPORT_TLS ? "TLS" : "TCP"));
if (fallback_on_write(netreq) == STUB_TCP_ERROR) {
/* TODO: Need new state to report transport unavailable*/
_getdns_netreq_change_state(netreq, NET_REQ_ERRORED);
_getdns_check_dns_req_complete(netreq->owner);
}
return;
default:
/* Unqueue the netreq from the write_queue */
remove_from_write_queue(upstream, netreq);
if (netreq->owner->return_call_reporting &&
netreq->upstream->tls_obj) {
if (netreq->debug_tls_peer_cert.data == NULL &&
(cert = _getdns_tls_connection_get_peer_certificate(netreq->upstream->tls_obj))) {
netreq->debug_tls_peer_cert.size = _getdns_tls_x509_to_der(
cert, &netreq->debug_tls_peer_cert.data);
_getdns_tls_x509_free(cert);
}
netreq->debug_tls_version = _getdns_tls_connection_get_version(netreq->upstream->tls_obj);
}
/* Need this because auth status is reset on connection close */
netreq->debug_tls_auth_status = netreq->upstream->tls_auth_state;
upstream->queries_sent++;
/* Empty write_queue?, then deschedule upstream write_cb */
if (upstream->write_queue == NULL) {
assert(upstream->write_queue_last == NULL);
GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event);
upstream->event.write_cb = NULL;
/* Reschedule (if already reading) to clear writable */
if (upstream->event.read_cb) {
GETDNS_SCHEDULE_EVENT(upstream->loop,
upstream->fd, TIMEOUT_FOREVER,
&upstream->event);
}
}
/* Schedule reading (if not already scheduled) */
if (!upstream->event.read_cb) {
GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event);
upstream->event.read_cb = upstream_read_cb;
GETDNS_SCHEDULE_EVENT(upstream->loop,
upstream->fd, TIMEOUT_FOREVER, &upstream->event);
}
/* WSA TODO: if callback is still upstream_write_cb, do it again
*/
return;
}
}
/*****************************/
/* Upstream utility functions*/
/*****************************/
static int
upstream_working_ok(getdns_upstream *upstream)
{
/* [TLS1]TODO: This arbitrary logic at the moment - review and improve!*/
return (upstream->responses_timeouts >
upstream->responses_received*
upstream->upstreams->tls_connection_retries ? 0 : 1);
}
static int
upstream_active(getdns_upstream *upstream)
{
if ((upstream->conn_state == GETDNS_CONN_SETUP ||
upstream->conn_state == GETDNS_CONN_OPEN) &&
upstream->keepalive_shutdown == 0)
return 1;
return 0;
}
static int
upstream_usable(getdns_upstream *upstream, int backoff_ok)
{
/* If backoff_ok is not true then only use upstreams that are in a healthy
state. */
if ((upstream->conn_state == GETDNS_CONN_CLOSED ||
upstream->conn_state == GETDNS_CONN_SETUP ||
upstream->conn_state == GETDNS_CONN_OPEN) &&
upstream->keepalive_shutdown == 0)
return 1;
/* Otherwise, allow upstreams that are backed off to be used because that
is better that having no upstream at all. */
if (backoff_ok == 1 &&
upstream->conn_state == GETDNS_CONN_BACKOFF)
return 1;
return 0;
}
static int
upstream_auth_status_ok(getdns_upstream *upstream, getdns_network_req *netreq) {
if (netreq->tls_auth_min != GETDNS_AUTHENTICATION_REQUIRED)
return 1;
return (upstream->tls_auth_state == GETDNS_AUTH_OK ? 1 : 0);
}
static int
upstream_stats(getdns_upstream *upstream)
{
/* [TLS1]TODO: This arbitrary logic at the moment - review and improve!*/
return (upstream->total_responses - upstream->total_timeouts
- upstream->conn_shutdowns*GETDNS_TRANSPORT_FAIL_MULT
- upstream->conn_setup_failed);
}
static int
upstream_valid(getdns_upstream *upstream,
getdns_transport_list_t transport,
getdns_network_req *netreq,
int backoff_ok)
{
/* Checking upstreams with backoff_ok true will also return upstreams
that are in a backoff state. Otherwise only use upstreams that have
a 'good' connection state. backoff_ok is useful when no upstreams at all
are valid, for example when the network connection is down and need to
keep trying to connect before failing completely. */
if (!(upstream->transport == transport && upstream_usable(upstream, backoff_ok)))
return 0;
if (transport == GETDNS_TRANSPORT_TCP)
return 1;
if (upstream->conn_state == GETDNS_CONN_OPEN) {
if (!upstream_auth_status_ok(upstream, netreq))
return 0;
else
return 1;
}
/* We need to check past authentication history to see if this is usable for TLS.*/
if (netreq->tls_auth_min != GETDNS_AUTHENTICATION_REQUIRED)
return 1;
return ((upstream->best_tls_auth_state == GETDNS_AUTH_OK ||
upstream->best_tls_auth_state == GETDNS_AUTH_NONE) ? 1 : 0);
}
static int
upstream_valid_and_open(getdns_upstream *upstream,
getdns_transport_list_t transport,
getdns_network_req *netreq)
{
if (!(upstream->transport == transport && upstream_active(upstream)))
return 0;
if (transport == GETDNS_TRANSPORT_TCP)
return 1;
/* Connection is complete, we know the auth status so check*/
if (upstream->conn_state == GETDNS_CONN_OPEN &&
!upstream_auth_status_ok(upstream, netreq))
return 0;
/* We must have a TLS connection still setting up so schedule and the
write code will check again once the connection is complete*/
return 1;
}
static int
other_transports_working(getdns_network_req *netreq,
getdns_upstreams *upstreams,
getdns_transport_list_t transport)
{
size_t i,j;
for (i = 0; i< netreq->transport_count;i++) {
if (netreq->transports[i] == transport)
continue;
if (netreq->transports[i] == GETDNS_TRANSPORT_UDP) {
for (j = 0; j < upstreams->count; j+=GETDNS_UPSTREAM_TRANSPORTS) {
if (upstreams->upstreams[j].back_off == 1)
return 1;
}
}
else if (netreq->transports[i] == GETDNS_TRANSPORT_TCP ||
netreq->transports[i] == GETDNS_TRANSPORT_TLS) {
for (j = 0; j < upstreams->count; j++) {
if (netreq->transports[i] == upstreams->upstreams[j].transport &&
upstream_valid(&upstreams->upstreams[j], netreq->transports[i],
netreq, 0))
return 1;
}
}
}
return 0;
}
static getdns_upstream *
upstream_select_stateful(getdns_network_req *netreq, getdns_transport_list_t transport)
{
getdns_upstream *upstream = NULL;
getdns_upstreams *upstreams = netreq->owner->upstreams;
size_t i;
time_t now = time(NULL);
if (!upstreams->count)
return NULL;
/* A check to re-instate backed-off upstreams after X amount of time*/
for (i = 0; i < upstreams->count; i++) {
if (upstreams->upstreams[i].conn_state == GETDNS_CONN_BACKOFF &&
upstreams->upstreams[i].conn_retry_time < now) {
upstreams->upstreams[i].conn_state = GETDNS_CONN_CLOSED;
_getdns_upstream_log(upstream, GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_NOTICE,
"%-40s : Upstream : Re-instating %s for this upstream\n",
upstreams->upstreams[i].addr_str,
upstreams->upstreams[i].transport == GETDNS_TRANSPORT_TLS ? "TLS" : "TCP");
}
}
if (netreq->owner->context->round_robin_upstreams == 0) {
/* First find if an open upstream has the correct properties and use that*/
for (i = 0; i < upstreams->count; i++) {
if (upstream_valid_and_open(&upstreams->upstreams[i], transport, netreq))
return &upstreams->upstreams[i];
}
}
/* OK - Find the next one to use. First check we have at least one valid
upstream (not backed-off). Because we completely back off failed
upstreams we may have no valid upstream at all (in contrast to UDP).*/
i = upstreams->current_stateful;
do {
DEBUG_STUB("%s %-35s: Testing upstreams %d %d for transport %d \n",
STUB_DEBUG_SETUP, __FUNC__, (int)i,
(int)upstreams->upstreams[i].conn_state, transport);
if (upstream_valid(&upstreams->upstreams[i], transport, netreq, 0)) {
upstream = &upstreams->upstreams[i];
break;
}
i++;
if (i >= upstreams->count)
i = 0;
} while (i != upstreams->current_stateful);
if (!upstream) {
/* Oh, oh. We have no valid upstreams for this transport. */
/* If there are other fallback transports that are working, we should
use them before forcibly promoting failed upstreams for re-try, since
waiting for the the re-try timer to re-instate them is the right thing
in this case. */
if (other_transports_working(netreq, upstreams, transport))
return NULL;
/* Try to find one that might work so
allow backed off upstreams to be considered valid.
Don't worry about the policy, just use the one with the least bad
stats that still fits the bill (right transport, right authentication)
to try to avoid total failure due to network outages. */
do {
if (upstream_valid(&upstreams->upstreams[i], transport, netreq, 1)) {
upstream = &upstreams->upstreams[i];
break;
}
i++;
if (i >= upstreams->count)
i = 0;
} while (i != upstreams->current_stateful);
if (!upstream) {
/* We _really_ have nothing that authenticates well enough right now...
leave to regular backoff logic. */
return NULL;
}
do {
i++;
if (i >= upstreams->count)
i = 0;
if (upstream_valid(&upstreams->upstreams[i], transport, netreq, 1) &&
upstream_stats(&upstreams->upstreams[i]) > upstream_stats(upstream))
upstream = &upstreams->upstreams[i];
} while (i != upstreams->current_stateful);
upstream->conn_state = GETDNS_CONN_CLOSED;
upstream->conn_backoff_interval = 1;
_getdns_upstream_log(upstream, GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_NOTICE,
"%-40s : Upstream : No valid upstreams for %s... promoting this backed-off upstream for re-try...\n",
upstream->addr_str,
upstream->transport == GETDNS_TRANSPORT_TLS ? "TLS" : "TCP");
return upstream;
}
/* Now select the specific upstream */
if (netreq->owner->context->round_robin_upstreams == 0) {
/* Base the decision on the stats and being not backed-off,
noting we will have started from 0*/
for (i++; i < upstreams->count; i++) {
if (upstream_valid(&upstreams->upstreams[i], transport, netreq, 0) &&
upstream_stats(&upstreams->upstreams[i]) > upstream_stats(upstream))
upstream = &upstreams->upstreams[i];
}
} else {
/* Simplistic, but always just pick the first one, incrementing the current.
Note we are not distinguishing TCP/TLS here....*/
upstreams->current_stateful+=GETDNS_UPSTREAM_TRANSPORTS;
if (upstreams->current_stateful >= upstreams->count)
upstreams->current_stateful = 0;
}
return upstream;
}
/* Used for UDP only */
static getdns_upstream *
upstream_select(getdns_network_req *netreq)
{
getdns_upstream *upstream;
getdns_upstreams *upstreams = netreq->owner->upstreams;
size_t i;
if (!upstreams->count)
return NULL;
/* First UPD/TCP upstream is always at i=0 and then start of each upstream block*/
/* TODO: Have direct access to sets of upstreams for different transports*/
for (i = 0; i < upstreams->count; i+=GETDNS_UPSTREAM_TRANSPORTS)
if (upstreams->upstreams[i].to_retry <= 0)
upstreams->upstreams[i].to_retry++;
i = upstreams->current_udp;
do {
if (upstreams->upstreams[i].to_retry > 0) {
upstreams->current_udp = i;
return &upstreams->upstreams[i];
}
i+=GETDNS_UPSTREAM_TRANSPORTS;
if (i >= upstreams->count)
i = 0;
} while (i != upstreams->current_udp);
/* Select upstream with the lowest back_off value */
upstream = upstreams->upstreams;
for (i = 0; i < upstreams->count; i+=GETDNS_UPSTREAM_TRANSPORTS)
if (upstreams->upstreams[i].back_off < upstream->back_off)
upstream = &upstreams->upstreams[i];
/* Restrict back_off in case no upstream is available to achieve
(more or less) round-robin retry on all upstreams. */
if (upstream->back_off > 4) {
for (i = 0; i < upstreams->count; i+=GETDNS_UPSTREAM_TRANSPORTS)
upstreams->upstreams[i].back_off = 2;
}
upstream->to_retry = 1;
upstreams->current_udp = upstream - upstreams->upstreams;
return upstream;
}
int
upstream_connect(getdns_upstream *upstream, getdns_transport_list_t transport,
getdns_dns_req *dnsreq)
{
DEBUG_STUB("%s %-35s: Getting upstream connection: %p\n", STUB_DEBUG_SETUP,
__FUNC__, (void*)upstream);
int fd = -1;
switch(transport) {
case GETDNS_TRANSPORT_UDP:
if ((fd = socket(
upstream->addr.ss_family, SOCK_DGRAM, IPPROTO_UDP)) == -1)
return -1;
getdns_sock_nonblock(fd);
break;
case GETDNS_TRANSPORT_TCP:
case GETDNS_TRANSPORT_TLS:
/* Use existing if available*/
if (upstream->fd != -1)
return upstream->fd;
fd = tcp_connect(upstream, transport);
if (fd == -1) {
upstream_failed(upstream, 1);
return -1;
}
upstream->loop = dnsreq->loop;
upstream->is_sync_loop = dnsreq->is_sync_request;
upstream->fd = fd;
if (transport == GETDNS_TRANSPORT_TLS) {
upstream->tls_obj = tls_create_object(dnsreq, fd, upstream);
if (upstream->tls_obj == NULL) {
upstream_failed(upstream, 1);
_getdns_closesocket(fd);
return -1;
}
upstream->tls_hs_state = GETDNS_HS_WRITE;
}
upstream->conn_state = GETDNS_CONN_SETUP;
_getdns_upstream_log(upstream, GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_DEBUG,
"%-40s : Conn opened: %s - %s Profile\n",
upstream->addr_str, transport == GETDNS_TRANSPORT_TLS ? "TLS":"TCP",
dnsreq->context->tls_auth_min == GETDNS_AUTHENTICATION_NONE ? "Opportunistic":"Strict");
break;
default:
return -1;
/* Nothing to do*/
}
return fd;
}
static getdns_upstream*
upstream_find_for_transport(getdns_network_req *netreq,
getdns_transport_list_t transport,
int *fd)
{
getdns_upstream *upstream = NULL;
/* UDP always returns an upstream, the only reason this will fail is if
no socket is available, in which case that is an error.*/
if (transport == GETDNS_TRANSPORT_UDP) {
upstream = upstream_select(netreq);
*fd = upstream_connect(upstream, transport, netreq->owner);
return upstream;
}
else {
/* For stateful transport we should keep trying until all our transports
are exhausted/backed-off (no upstream) and until we have tried each
upstream at least once for this netreq in a total backoff scenario */
size_t i = 0;
do {
upstream = upstream_select_stateful(netreq, transport);
if (!upstream)
return NULL;
*fd = upstream_connect(upstream, transport, netreq->owner);
if (i >= upstream->upstreams->count)
return NULL;
i++;
} while (*fd == -1);
DEBUG_STUB("%s %-35s: FD: %d Connecting to upstream: %p No: %d\n",
STUB_DEBUG_SETUP, __FUNC__, *fd, (void*)upstream,
(int)(upstream - netreq->owner->context->upstreams->upstreams));
}
return upstream;
}
static int
upstream_find_for_netreq(getdns_network_req *netreq)
{
int fd = -1;
getdns_upstream *upstream;
size_t i;
for (i = netreq->transport_current;
i < netreq->transport_count; i++) {
upstream = upstream_find_for_transport(netreq,
netreq->transports[i],
&fd);
if (!upstream)
continue;
if (fd == -1) {
if (_getdns_resource_depletion())
return STUB_TRY_AGAIN_LATER;
return -1;
}
if (upstream == netreq->first_upstream)
continue;
netreq->transport_current = i;
netreq->upstream = upstream;
if (!netreq->first_upstream)
netreq->first_upstream = upstream;
netreq->keepalive_sent = 0;
DEBUG_STUB("%s %-35s: MSG: %p found upstream %p with transport %d, fd: %d\n", STUB_DEBUG_SCHEDULE, __FUNC__, (void*)netreq, (void *)upstream, (int)netreq->transports[i], fd);
return fd;
}
/* Handle better, will give generic error*/
DEBUG_STUB("%s %-35s: MSG: %p No valid upstream! \n", STUB_DEBUG_SCHEDULE, __FUNC__, (void*)netreq);
_getdns_context_log(netreq->owner->context, GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_ERR,
" *FAILURE* no valid transports or upstreams available!\n");
return -1;
}
/************************/
/* Scheduling functions */
/***********************/
static int
fallback_on_write(getdns_network_req *netreq)
{
uint64_t now_ms = 0;
/* Deal with UDP one day*/
DEBUG_STUB("%s %-35s: MSG: %p FALLING BACK \n", STUB_DEBUG_SCHEDULE, __FUNC__, (void*)netreq);
/* Try to find a fallback transport*/
getdns_return_t result = _getdns_submit_stub_request(netreq, &now_ms);
if (result != GETDNS_RETURN_GOOD)
return STUB_TCP_ERROR;
return (netreq->transports[netreq->transport_current]
== GETDNS_TRANSPORT_UDP) ?
netreq->fd : netreq->upstream->fd;
}
static void
upstream_reschedule_events(getdns_upstream *upstream) {
DEBUG_STUB("%s %-35s: FD: %d \n", STUB_DEBUG_SCHEDULE,
__FUNC__, upstream->fd);
if (upstream->event.ev)
GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event);
if (upstream->fd == -1 || !( upstream->conn_state == GETDNS_CONN_SETUP
|| upstream->conn_state == GETDNS_CONN_OPEN ))
return;
if (!upstream->write_queue && upstream->event.write_cb) {
upstream->event.write_cb = NULL;
}
if (upstream->write_queue && !upstream->event.write_cb) {
upstream->event.write_cb = upstream_write_cb;
}
if (!upstream->netreq_by_query_id.count && upstream->event.read_cb) {
upstream->event.read_cb = NULL;
}
if (upstream->netreq_by_query_id.count && !upstream->event.read_cb) {
upstream->event.read_cb = upstream_read_cb;
}
if (upstream->event.read_cb || upstream->event.write_cb)
GETDNS_SCHEDULE_EVENT(upstream->loop,
upstream->fd, TIMEOUT_FOREVER, &upstream->event);
else {
DEBUG_STUB("%s %-35s: FD: %d Connection idle - timeout is %d\n",
STUB_DEBUG_SCHEDULE, __FUNC__, upstream->fd,
(int)upstream->keepalive_timeout);
upstream->event.read_cb = upstream_read_cb;
upstream->event.timeout_cb = upstream_idle_timeout_cb;
GETDNS_SCHEDULE_EVENT(upstream->loop, upstream->fd,
upstream->keepalive_timeout, &upstream->event);
}
}
static void
upstream_schedule_netreq(getdns_upstream *upstream, getdns_network_req *netreq)
{
DEBUG_STUB("%s %-35s: MSG: %p (schedule event)\n", STUB_DEBUG_SCHEDULE, __FUNC__, (void*)netreq);
/* We have a connected socket and a global event loop */
assert(upstream->fd >= 0);
assert(upstream->loop);
/* Append netreq to write_queue */
if (!upstream->write_queue) {
upstream->write_queue = upstream->write_queue_last = netreq;
GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event);
if (netreq->owner->is_sync_request && !upstream->is_sync_loop){
/* An initial synchronous call, change loop */
upstream->loop = netreq->owner->loop;
upstream->is_sync_loop = 1;
}
upstream->event.timeout_cb = NULL;
upstream->event.write_cb = upstream_write_cb;
if (upstream->queries_sent == 0) {
/* Set a timeout on the upstream so we can catch failed setup*/
upstream->event.timeout_cb = upstream_setup_timeout_cb;
GETDNS_SCHEDULE_EVENT(upstream->loop, upstream->fd,
_getdns_ms_until_expiry(netreq->owner->expires)/2,
&upstream->event);
} else {
GETDNS_SCHEDULE_EVENT(upstream->loop,
upstream->fd, TIMEOUT_FOREVER, &upstream->event);
}
} else if (netreq->owner->is_sync_request && !upstream->is_sync_loop) {
/* Initial synchronous call on an upstream in use,
* prioritize this request (insert at 0)
* and reschedule against synchronous loop.
*/
netreq->write_queue_tail = upstream->write_queue;
upstream->write_queue = netreq;
GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event);
upstream->loop = netreq->owner->loop;
upstream->is_sync_loop = 1;
GETDNS_SCHEDULE_EVENT(upstream->loop, upstream->fd,
TIMEOUT_FOREVER, &upstream->event);
} else {
/* "Follow-up synchronous" or Asynchronous call,
* this request comes last (append)
*/
upstream->write_queue_last->write_queue_tail = netreq;
upstream->write_queue_last = netreq;
}
}
getdns_return_t
_getdns_submit_stub_request(getdns_network_req *netreq, uint64_t *now_ms)
{
int fd = -1;
getdns_dns_req *dnsreq;
getdns_context *context;
DEBUG_STUB("%s %-35s: MSG: %p TYPE: %d\n", STUB_DEBUG_ENTRY, __FUNC__,
(void*)netreq, netreq->request_type);
dnsreq = netreq->owner;
context = dnsreq->context;
/* This does a best effort to get a initial fd.
* All other set up is done async*/
fd = upstream_find_for_netreq(netreq);
if (fd == -1)
return GETDNS_RETURN_NO_UPSTREAM_AVAILABLE;
else if (fd == STUB_TRY_AGAIN_LATER) {
_getdns_netreq_change_state(netreq, NET_REQ_NOT_SENT);
netreq->node.key = netreq;
if (_getdns_rbtree_insert(
&context->pending_netreqs, &netreq->node))
return GETDNS_RETURN_GOOD;
return GETDNS_RETURN_NO_UPSTREAM_AVAILABLE;
}
switch(netreq->transports[netreq->transport_current]) {
case GETDNS_TRANSPORT_UDP:
netreq->fd = fd;
GETDNS_CLEAR_EVENT(dnsreq->loop, &netreq->event);
GETDNS_SCHEDULE_EVENT(dnsreq->loop, netreq->fd,
_getdns_ms_until_expiry2(dnsreq->expires, now_ms),
getdns_eventloop_event_init(&netreq->event, netreq,
NULL, stub_udp_write_cb, stub_timeout_cb));
return GETDNS_RETURN_GOOD;
case GETDNS_TRANSPORT_TLS:
case GETDNS_TRANSPORT_TCP:
upstream_schedule_netreq(netreq->upstream, netreq);
/* For TLS, set a short timeout to catch setup problems. This is reset
when the connection is successful.*/
GETDNS_CLEAR_EVENT(dnsreq->loop, &netreq->event);
/*************************************************************
****** *****
****** Scheduling differences of *****
****** synchronous and asynchronous requests *****
****** *****
*************************************************************
*
* Besides the asynchronous event loop, which is typically
* shared with the application, every getdns context also
* has another event loop (not registered by the user) which
* is used specifically and only for synchronous requests:
* context->sync_eventloop.
*
* We do not use the asynchronous loop for the duration of the
* synchronous query, because:
* - Callbacks for outstanding (and thus asynchronous) queries
* might fire as a side effect.
* - But worse, since the asynchronous loop is created and
* managed by the user, which may well have her own non-dns
* related events scheduled against it, they will fire as
* well as a side effect of doing the synchronous request!
*
*
* Transports that keep connections open, have their own event
* structure to keep their connection state. The event is
* associated with the upstream struct. Note that there is a
* separate upstream struct for each state full transport, so
* each upstream has multiple transport structs!
*
* side note: The upstream structs have their own reference
* to the "context's" event loop so they can,
* in theory, be detached (to finish running
* queries for example).
*
* If a synchronous request is scheduled for such a transport,
* then the sync-loop temporarily has to "run" that
* upstream/transport's event! Outstanding requests for that
* upstream/transport might come in while processing the
* synchronous call. When this happens, they are queued up
* (at upstream->finished_queue) and an timeout event of 1
* will be scheduled against the asynchronous loop to start
* processing those received request as soon as the
* asynchronous loop will be run.
*
*
* When getdns is linked with libunbound 1.5.8 or older, then
* when a RECURSING synchronous request is made then
* outstanding asynchronously scheduled RECURSING requests
* may fire as a side effect, as we reuse the same code path
* For both synchronous and asynchronous calls,
* ub_resolve_async() is used under the hood.
*
* With libunbound versions newer than 1.5.8, libunbound will
* share the event loops used with getdns which will prevent
* these side effects from happening.
*
*
* The event loop used for a specific request is in
* dnsreq->loop. The asynchronous is always also available
* at the upstream as upstream->loop.
*/
GETDNS_SCHEDULE_EVENT(
dnsreq->loop, -1,
_getdns_ms_until_expiry2(dnsreq->expires, now_ms),
getdns_eventloop_event_init(
&netreq->event, netreq, NULL, NULL,
stub_timeout_cb));
return GETDNS_RETURN_GOOD;
default:
return GETDNS_RETURN_GENERIC_ERROR;
}
}
/* stub.c */