Do better with unbound transport mapping and fix problems with sync fallback

This commit is contained in:
Sara Dickinson 2015-06-25 20:21:00 +01:00
parent 8819d29535
commit cb5bbac26d
3 changed files with 123 additions and 105 deletions

View File

@ -1217,7 +1217,7 @@ getdns_set_base_dns_transports(struct getdns_context *context,
static getdns_return_t
set_ub_dns_transport(struct getdns_context* context) {
/* These mappings are not exact because Unbound is configured differently,
so just map as close as possible from the first 1 or 2 transports. */
so just map as close as possible. Not all options can be supported.*/
switch (context->dns_transports[0]) {
case GETDNS_TRANSPORT_UDP:
set_ub_string_opt(context, "do-udp:", "yes");
@ -1226,25 +1226,38 @@ set_ub_dns_transport(struct getdns_context* context) {
else
set_ub_string_opt(context, "do-tcp:", "no");
break;
case GETDNS_TRANSPORT_TLS:
/* Note: If TLS is used in recursive mode this will try TLS on port
* 53... So this is prohibited when preparing for resolution.*/
if (context->dns_transport_count == 0) {
set_ub_string_opt(context, "ssl-upstream:", "yes");
set_ub_string_opt(context, "do-udp:", "no");
set_ub_string_opt(context, "do-tcp:", "yes");
break;
}
if (context->dns_transports[1] != GETDNS_TRANSPORT_TCP)
break;
/* Fallthrough */
case GETDNS_TRANSPORT_STARTTLS:
case GETDNS_TRANSPORT_TCP:
/* Note: no STARTTLS or fallback to TCP available directly in unbound, so we just
* use TCP for now to make sure the messages are sent. */
set_ub_string_opt(context, "do-udp:", "no");
set_ub_string_opt(context, "do-tcp:", "yes");
break;
case GETDNS_TRANSPORT_TLS:
case GETDNS_TRANSPORT_STARTTLS:
set_ub_string_opt(context, "do-udp:", "no");
set_ub_string_opt(context, "do-tcp:", "yes");
/* Find out if there is a fallback available. */
int fallback = 0;
for (size_t i = 1; i < context->dns_transport_count; i++) {
if (context->dns_transports[i] == GETDNS_TRANSPORT_TCP) {
fallback = 1;
break;
}
else if (context->dns_transports[i] == GETDNS_TRANSPORT_UDP) {
set_ub_string_opt(context, "do-udp:", "yes");
set_ub_string_opt(context, "do-tcp:", "no");
fallback = 1;
break;
}
}
if (context->dns_transports[0] == GETDNS_TRANSPORT_TLS) {
if (fallback == 0)
/* Use TLS if it is the only thing.*/
set_ub_string_opt(context, "ssl-upstream:", "yes");
break;
} else if (fallback == 0)
/* Can't support STARTTLS with no fallback. This leads to
* timeouts with un stub validation.... */
set_ub_string_opt(context, "do-tcp:", "no");
break;
default:
return GETDNS_RETURN_CONTEXT_UPDATE_FAIL;
}
@ -2011,10 +2024,13 @@ getdns_context_prepare_for_resolution(struct getdns_context *context,
return GETDNS_RETURN_BAD_CONTEXT;
}
}
/* Block use of TLS ONLY in recursive mode as it won't work */
/* Block use of STARTTLS/TLS ONLY in recursive mode as it won't work */
/* Note: If TLS is used in recursive mode this will try TLS on port
* 53 so it is blocked here. So is STARTTLS only at the moment. */
if (context->resolution_type == GETDNS_RESOLUTION_RECURSING &&
context->dns_transports[0] == GETDNS_TRANSPORT_TLS &&
context->dns_transport_count == 1)
context->dns_transport_count == 1 &&
(context->dns_transports[0] == GETDNS_TRANSPORT_TLS ||
context->dns_transports[0] == GETDNS_TRANSPORT_STARTTLS))
return GETDNS_RETURN_BAD_CONTEXT;
if (context->resolution_type_set == context->resolution_type)

View File

@ -48,6 +48,7 @@
#define STUB_TCP_AGAIN -3
#define STUB_TCP_ERROR -2
/* Don't currently have access to the context whilst doing handshake */
#define TIMEOUT_TLS 2500
static time_t secret_rollover_time = 0;
@ -59,9 +60,9 @@ 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_schedule_events(getdns_upstream *upstream,
static void upstream_reschedule_events(getdns_upstream *upstream,
size_t idle_timeout);
static void upstream_schedule_netreq_events(getdns_upstream *upstream,
static void upstream_reschedule_netreq_events(getdns_upstream *upstream,
getdns_network_req *netreq);
static int upstream_connect(getdns_upstream *upstream,
getdns_transport_list_t transport,
@ -340,14 +341,6 @@ is_starttls_response(getdns_network_req *netreq)
return 0;
}
static int
is_starttls_request(getdns_network_req *netreq)
{
return ((strcmp(netreq->owner->name, "STARTTLS") == 0) &&
netreq->request_type == GETDNS_RRTYPE_TXT &&
netreq->request_class == GLDNS_RR_CLASS_CH);
}
/** best effort to set nonblocking */
static void
getdns_sock_nonblock(int sockfd)
@ -415,7 +408,7 @@ stub_next_upstream(getdns_network_req *netreq)
static void
stub_cleanup(getdns_network_req *netreq)
{
DEBUG_STUB("%s\n", __FUNCTION__);
DEBUG_STUB("*** %s\n", __FUNCTION__);
getdns_dns_req *dnsreq = netreq->owner;
getdns_network_req *r, *prev_r;
getdns_upstream *upstream;
@ -449,13 +442,13 @@ stub_cleanup(getdns_network_req *netreq)
prev_r ? prev_r : NULL;
break;
}
upstream_schedule_events(upstream, netreq->owner->context->idle_timeout);
upstream_reschedule_events(upstream, netreq->owner->context->idle_timeout);
}
static int
tls_cleanup(getdns_upstream *upstream)
{
DEBUG_STUB("%s\n", __FUNCTION__);
DEBUG_STUB("*** %s\n", __FUNCTION__);
SSL_free(upstream->tls_obj);
upstream->tls_obj = NULL;
upstream->tls_hs_state = GETDNS_HS_FAILED;
@ -464,7 +457,7 @@ tls_cleanup(getdns_upstream *upstream)
GETDNS_SCHEDULE_EVENT(upstream->loop, upstream->fd, TIMEOUT_FOREVER,
getdns_eventloop_event_init(&upstream->event, upstream,
NULL, upstream_write_cb, NULL));
/* Reset sync event, with wrong timeout*/
/* Reset sync event, with full timeout (which isn't correct)*/
getdns_network_req *netreq = upstream->write_queue;
if (netreq && (netreq->event.write_cb || netreq->event.read_cb)) {
GETDNS_CLEAR_EVENT(netreq->owner->loop, &netreq->event);
@ -504,14 +497,6 @@ upstream_erred(getdns_upstream *upstream)
upstream->fd = -1;
}
static void
message_erred(getdns_network_req *netreq)
{
stub_cleanup(netreq);
netreq->state = NET_REQ_FINISHED;
priv_getdns_check_dns_req_complete(netreq->owner);
}
void
priv_getdns_cancel_stub_request(getdns_network_req *netreq)
{
@ -522,7 +507,7 @@ priv_getdns_cancel_stub_request(getdns_network_req *netreq)
static void
stub_erred(getdns_network_req *netreq)
{
DEBUG_STUB("%s\n", __FUNCTION__);
DEBUG_STUB("*** %s\n", __FUNCTION__);
stub_next_upstream(netreq);
stub_cleanup(netreq);
/* TODO[TLS]: When we get an error (which is probably a timeout) and are
@ -535,12 +520,12 @@ stub_erred(getdns_network_req *netreq)
static void
stub_timeout_cb(void *userarg)
{
DEBUG_STUB("%s\n", __FUNCTION__);
DEBUG_STUB("*** %s\n", __FUNCTION__);
getdns_network_req *netreq = (getdns_network_req *)userarg;
/* For now, mark a STARTTLS timeout as a failured negotiation and allow
* fallback but don't close the connection. */
if (is_starttls_request(netreq)) {
if (netreq->owner == netreq->upstream->starttls_req) {
netreq->upstream->tls_hs_state = GETDNS_HS_FAILED;
stub_next_upstream(netreq);
stub_cleanup(netreq);
@ -549,7 +534,6 @@ stub_timeout_cb(void *userarg)
stub_next_upstream(netreq);
stub_cleanup(netreq);
DEBUG_STUB("%d\n", netreq->upstream->transport);
if (netreq->fd >= 0) close(netreq->fd);
(void) getdns_context_request_timed_out(netreq->owner);
}
@ -557,12 +541,11 @@ stub_timeout_cb(void *userarg)
static void
upstream_idle_timeout_cb(void *userarg)
{
DEBUG_STUB("%s\n", __FUNCTION__);
DEBUG_STUB("*** %s\n", __FUNCTION__);
getdns_upstream *upstream = (getdns_upstream *)userarg;
/*There is a race condition with a new request being scheduled while this happens
so take ownership of the fd asap*/
int fd = upstream->fd;
DEBUG_STUB("%d\n", upstream->transport);
upstream->fd = -1;
upstream->event.timeout_cb = NULL;
upstream->event.read_cb = NULL;
@ -582,7 +565,7 @@ upstream_idle_timeout_cb(void *userarg)
static void
upstream_tls_timeout_cb(void *userarg)
{
DEBUG_STUB("%s\n", __FUNCTION__);
DEBUG_STUB("*** %s\n", __FUNCTION__);
getdns_upstream *upstream = (getdns_upstream *)userarg;
/* Clean up and trigger a write to let the fallback code to its job */
tls_cleanup(upstream);
@ -608,7 +591,7 @@ upstream_tls_timeout_cb(void *userarg)
static void
stub_tls_timeout_cb(void *userarg)
{
DEBUG_STUB("%s\n", __FUNCTION__);
DEBUG_STUB("*** %s\n", __FUNCTION__);
getdns_network_req *netreq = (getdns_network_req *)userarg;
getdns_upstream *upstream = netreq->upstream;
/* Clean up and trigger a write to let the fallback code to its job */
@ -879,7 +862,7 @@ tls_create_object(getdns_context *context, int fd)
static int
tls_do_handshake(getdns_upstream *upstream)
{
DEBUG_STUB("%s\n", __FUNCTION__);
DEBUG_STUB("--- %s\n", __FUNCTION__);
int r;
int want;
ERR_clear_error();
@ -1303,7 +1286,7 @@ stub_tcp_write_cb(void *userarg)
static void
upstream_read_cb(void *userarg)
{
DEBUG_STUB("%s\n", __FUNCTION__);
DEBUG_STUB("--- READ: %s\n", __FUNCTION__);
getdns_upstream *upstream = (getdns_upstream *)userarg;
getdns_network_req *netreq;
getdns_dns_req *dnsreq;
@ -1355,14 +1338,6 @@ upstream_read_cb(void *userarg)
netreq->secure = 0;
netreq->bogus = 0;
/* This also reschedules events for the upstream*/
stub_cleanup(netreq);
/* More to read/write for syncronous lookups? */
if (netreq->event.read_cb) {
upstream_schedule_netreq_events(upstream, netreq);
}
if (netreq->owner == upstream->starttls_req) {
dnsreq = netreq->owner;
if (is_starttls_response(netreq)) {
@ -1380,7 +1355,16 @@ upstream_read_cb(void *userarg)
netreq->owner->context->timeout,
getdns_eventloop_event_init(&upstream->event, upstream,
NULL, upstream_write_cb, NULL));
} else
}
/* This also reschedules events for the upstream*/
stub_cleanup(netreq);
/* More to read/write for syncronous lookups? */
if (netreq->event.read_cb)
upstream_reschedule_netreq_events(upstream, netreq);
if (netreq->owner != upstream->starttls_req)
priv_getdns_check_dns_req_complete(netreq->owner);
}
}
@ -1388,19 +1372,20 @@ upstream_read_cb(void *userarg)
static void
netreq_upstream_read_cb(void *userarg)
{
DEBUG_STUB("%s\n", __FUNCTION__);
DEBUG_STUB("--- READ: %s\n", __FUNCTION__);
upstream_read_cb(((getdns_network_req *)userarg)->upstream);
}
static void
upstream_write_cb(void *userarg)
{
DEBUG_STUB("%s\n", __FUNCTION__);
getdns_upstream *upstream = (getdns_upstream *)userarg;
getdns_network_req *netreq = upstream->write_queue;
getdns_dns_req *dnsreq = netreq->owner;
int q;
DEBUG_STUB("--- WRITE: %s: %p TYPE: %d\n", __FUNCTION__, netreq,
netreq->request_type);
if (tls_requested(netreq) && tls_should_write(upstream))
q = stub_tls_write(upstream, &upstream->tcp, netreq);
else
@ -1411,18 +1396,18 @@ upstream_write_cb(void *userarg)
return;
case STUB_TCP_ERROR:
/* Could not complete the TLS set up. Need to fallback.*/
DEBUG_STUB("Setting write error\n");
/* Problem with the TCP connection itself. Need to fallback.*/
DEBUG_STUB("--- WRITE: Setting write error\n");
upstream->tcp.write_error = 1;
stub_next_upstream(netreq);
if (fallback_on_write(netreq) == STUB_TCP_ERROR)
message_erred(netreq);
return;
/* Fall through */
case STUB_TLS_SETUP_ERROR:
/* Could not complete the TLS set up. Need to fallback.*/
if (fallback_on_write(netreq) == STUB_TCP_ERROR)
message_erred(netreq);
stub_cleanup(netreq);
if (fallback_on_write(netreq) == STUB_TCP_ERROR) {
netreq->state = NET_REQ_FINISHED;
priv_getdns_check_dns_req_complete(netreq->owner);
}
return;
default:
@ -1480,6 +1465,9 @@ upstream_write_cb(void *userarg)
static void
netreq_upstream_write_cb(void *userarg)
{
DEBUG_STUB("--- WRITE: %s: %p TYPE: %d\n", __FUNCTION__,
((getdns_network_req *)userarg),
((getdns_network_req *)userarg)->request_type);
upstream_write_cb(((getdns_network_req *)userarg)->upstream);
}
@ -1518,7 +1506,7 @@ upstream_transport_valid(getdns_upstream *upstream,
static getdns_upstream *
upstream_select(getdns_network_req *netreq, getdns_transport_list_t transport)
{
DEBUG_STUB("%s\n", __FUNCTION__);
DEBUG_STUB(" %s\n", __FUNCTION__);
getdns_upstream *upstream;
getdns_upstreams *upstreams = netreq->owner->upstreams;
size_t i;
@ -1535,12 +1523,12 @@ upstream_select(getdns_network_req *netreq, getdns_transport_list_t transport)
/* TODO[TLS]: Should we create a tmp array of upstreams with correct*/
/* transport type and/or maintain separate current for transports?*/
i = upstreams->current;
DEBUG_STUB("Current upstream %d\n",(int)i);
DEBUG_STUB(" current upstream: %d\n",(int)i);
do {
if (upstreams->upstreams[i].to_retry > 0 &&
upstream_transport_valid(&upstreams->upstreams[i], transport)) {
upstreams->current = i;
DEBUG_STUB("Selected upstream %d\n",(int)i);
DEBUG_STUB(" selected upstream: %d\n",(int)i);
return &upstreams->upstreams[i];
}
if (++i > upstreams->count)
@ -1554,8 +1542,10 @@ upstream_select(getdns_network_req *netreq, getdns_transport_list_t transport)
upstream = &upstreams->upstreams[i];
/* Need to check again that the transport is valid */
if (!upstream_transport_valid(upstream, transport))
if (!upstream_transport_valid(upstream, transport)) {
DEBUG_STUB(" ! No valid upstream available\n");
return NULL;
}
upstream->back_off++;
upstream->to_retry = 1;
upstreams->current = upstream - upstreams->upstreams;
@ -1567,7 +1557,7 @@ int
upstream_connect(getdns_upstream *upstream, getdns_transport_list_t transport,
getdns_dns_req *dnsreq)
{
DEBUG_STUB("%s\n", __FUNCTION__);
DEBUG_STUB(" %s\n", __FUNCTION__);
int fd = -1;
switch(transport) {
case GETDNS_TRANSPORT_UDP:
@ -1635,7 +1625,7 @@ find_upstream_for_specific_transport(getdns_network_req *netreq,
getdns_transport_list_t transport,
int *fd)
{
DEBUG_STUB("%s %d \n", __FUNCTION__, transport);
DEBUG_STUB(" %s: %d \n", __FUNCTION__, transport);
getdns_upstream *upstream = upstream_select(netreq, transport);
if (!upstream)
return NULL;
@ -1672,52 +1662,62 @@ fallback_on_write(getdns_network_req *netreq)
/* Deal with UDP and change error code*/
DEBUG_STUB("%s ***\n", __FUNCTION__);
DEBUG_STUB("#-> %s: %p TYPE: %d\n", __FUNCTION__, netreq, netreq->request_type);
getdns_upstream *upstream = netreq->upstream;
/* Remove from queue*/
/* Remove from queue and deal with the old upstream events*/
if (!(upstream->write_queue = netreq->write_queue_tail)) {
upstream->write_queue_last = NULL;
upstream->event.write_cb = NULL;
GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event);
}
netreq->write_queue_tail = NULL;
upstream_schedule_events(upstream, netreq->owner->context->idle_timeout);
upstream_reschedule_events(upstream, netreq->owner->context->idle_timeout);
if (priv_getdns_submit_stub_request(netreq) != GETDNS_RETURN_GOOD)
/* Try to find a fallback transport*/
getdns_return_t result = priv_getdns_submit_stub_request(netreq);
/* For sync messages we must re-schedule the events on the old upstream
* here too. Must schedule this last to make sure it is called back first! */
if (netreq->owner->loop != upstream->loop && upstream->write_queue)
upstream_reschedule_netreq_events(upstream, upstream->write_queue);
if (result != GETDNS_RETURN_GOOD)
return STUB_TCP_ERROR;
/* For sync messages we must re-schedule the events on the original upstream
* here.*/
if (netreq->owner->loop != upstream->loop && upstream->write_queue) {
/* Must schedule this last to make sure it is called back first....?*/
upstream_schedule_netreq_events(upstream, upstream->write_queue);
}
return (netreq->transports[netreq->transport_current]
== GETDNS_TRANSPORT_UDP) ?
netreq->fd : netreq->upstream->fd;
}
static void
upstream_schedule_events(getdns_upstream *upstream, size_t idle_timeout) {
upstream_reschedule_events(getdns_upstream *upstream, size_t idle_timeout) {
DEBUG_STUB("%s\n", __FUNCTION__);
DEBUG_STUB("# %s\n", __FUNCTION__);
int reschedule = 0;
if (!upstream->write_queue && upstream->event.write_cb) {
upstream->event.write_cb = NULL;
reschedule = 1;
}
if (upstream->write_queue && !upstream->event.write_cb) {
upstream->event.write_cb = upstream_write_cb;
reschedule = 1;
}
if (!upstream->netreq_by_query_id.count && upstream->event.read_cb) {
upstream->event.read_cb = NULL;
reschedule = 1;
}
if (upstream->netreq_by_query_id.count && !upstream->event.read_cb) {
upstream->event.read_cb = upstream_read_cb;
reschedule = 1;
}
if (reschedule) {
GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event);
if (upstream->event.read_cb || upstream->event.write_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: *Idle connection* \n", __FUNCTION__);
upstream->event.timeout_cb = upstream_idle_timeout_cb;
if (upstream->tcp.write_error != 0)
idle_timeout = 0;
@ -1728,9 +1728,9 @@ upstream_schedule_events(getdns_upstream *upstream, size_t idle_timeout) {
}
static void
upstream_schedule_netreq_events(getdns_upstream *upstream,
getdns_network_req *netreq) {
DEBUG_STUB("%s\n", __FUNCTION__);
upstream_reschedule_netreq_events(getdns_upstream *upstream,
getdns_network_req *netreq) {
DEBUG_STUB("# %s: %p: TYPE: %d\n", __FUNCTION__, netreq, netreq->request_type);
getdns_dns_req *dnsreq = netreq->owner;
GETDNS_CLEAR_EVENT(dnsreq->loop, &netreq->event);
if (upstream->netreq_by_query_id.count || upstream->write_queue)
@ -1746,7 +1746,7 @@ upstream_schedule_netreq_events(getdns_upstream *upstream,
/* This is a sync call, and the connection is idle. But we can't set a
* timeout since we won't have an event loop if there are no netreqs
* So we will have to be aggressive and shut the connection....*/
DEBUG_STUB("%s -> closing connection\n", __FUNCTION__);
DEBUG_STUB("# %s: **Closing connection**\n", __FUNCTION__);
if (upstream->tls_obj) {
SSL_shutdown(upstream->tls_obj);
SSL_free(upstream->tls_obj);
@ -1760,7 +1760,7 @@ upstream_schedule_netreq_events(getdns_upstream *upstream,
static void
upstream_schedule_netreq(getdns_upstream *upstream, getdns_network_req *netreq)
{
DEBUG_STUB("%s\n", __FUNCTION__);
DEBUG_STUB("# %s: %p TYPE: %d\n", __FUNCTION__, netreq, netreq->request_type);
/* We have a connected socket and a global event loop */
assert(upstream->fd >= 0);
assert(upstream->loop);
@ -1794,7 +1794,7 @@ upstream_schedule_netreq(getdns_upstream *upstream, getdns_network_req *netreq)
getdns_return_t
priv_getdns_submit_stub_request(getdns_network_req *netreq)
{
DEBUG_STUB("%s\n", __FUNCTION__);
DEBUG_STUB("--> %s\n", __FUNCTION__);
int fd = -1;
getdns_dns_req *dnsreq = netreq->owner;

View File

@ -277,6 +277,11 @@ getdns_return_t parse_args(int argc, char **argv)
" %d", r);
break;
}
} else if (arg[1] == '0') {
/* Unset all existing extensions*/
getdns_dict_destroy(extensions);
extensions = getdns_dict_create();
break;
} else if ((r = getdns_dict_set_int(extensions, arg+1,
GETDNS_EXTENSION_TRUE))) {
fprintf(stderr, "Could not set extension "
@ -605,9 +610,7 @@ main(int argc, char **argv)
r = GETDNS_RETURN_GENERIC_ERROR;
break;
}
if (r)
goto done_destroy_extensions;
if (!quiet) {
if (response && !quiet) {
if ((response_str = json ?
getdns_print_json_dict(response, json == 1)
: getdns_pretty_print_dict(response))) {
@ -620,14 +623,15 @@ main(int argc, char **argv)
fprintf( stderr
, "Could not print response\n");
}
} else if (r == GETDNS_RETURN_GOOD) {
}
if (r == GETDNS_RETURN_GOOD) {
uint32_t status;
getdns_dict_get_int(response, "status", &status);
fprintf(stdout, "Response code was: GOOD. Status was: %s\n",
getdns_get_errorstr_by_id(status));
}
else if (interactive)
fprintf(stderr, "An error occurred: %d\n", r);
} else
fprintf(stderr, "An error occurred: %d '%s'\n", r,
getdns_get_errorstr_by_id(r));
}
} while (interactive);
@ -646,8 +650,6 @@ done_destroy_context:
if (r == CONTINUE)
return 0;
if (r)
fprintf(stderr, "An error occurred: %d\n", r);
fprintf(stdout, "\nAll done.\n");
return r;
}