diff --git a/src/const-info.c b/src/const-info.c index 4556634a..b539094a 100644 --- a/src/const-info.c +++ b/src/const-info.c @@ -73,6 +73,7 @@ static struct const_info consts_info[] = { { 619, "GETDNS_CONTEXT_CODE_EDNS_CLIENT_SUBNET_PRIVATE", GETDNS_CONTEXT_CODE_EDNS_CLIENT_SUBNET_PRIVATE_TEXT }, { 620, "GETDNS_CONTEXT_CODE_TLS_QUERY_PADDING_BLOCKSIZE", GETDNS_CONTEXT_CODE_TLS_QUERY_PADDING_BLOCKSIZE_TEXT }, { 621, "GETDNS_CONTEXT_CODE_PUBKEY_PINSET", GETDNS_CONTEXT_CODE_PUBKEY_PINSET_TEXT }, + { 622, "GETDNS_CONTEXT_CODE_TLS_USE_ALL_UPSTREAMS", GETDNS_CONTEXT_CODE_TLS_USE_ALL_UPSTREAMS_TEXT }, { 700, "GETDNS_CALLBACK_COMPLETE", GETDNS_CALLBACK_COMPLETE_TEXT }, { 701, "GETDNS_CALLBACK_CANCEL", GETDNS_CALLBACK_CANCEL_TEXT }, { 702, "GETDNS_CALLBACK_TIMEOUT", GETDNS_CALLBACK_TIMEOUT_TEXT }, @@ -161,6 +162,7 @@ static struct const_name_info consts_name_info[] = { { "GETDNS_CONTEXT_CODE_TIMEOUT", 616 }, { "GETDNS_CONTEXT_CODE_TLS_AUTHENTICATION", 618 }, { "GETDNS_CONTEXT_CODE_TLS_QUERY_PADDING_BLOCKSIZE", 620 }, + { "GETDNS_CONTEXT_CODE_TLS_USE_ALL_UPSTREAMS", 622 }, { "GETDNS_CONTEXT_CODE_UPSTREAM_RECURSIVE_SERVERS", 603 }, { "GETDNS_DNSSEC_BOGUS", 401 }, { "GETDNS_DNSSEC_INDETERMINATE", 402 }, diff --git a/src/context.c b/src/context.c index 5421fd81..e5b183d6 100644 --- a/src/context.c +++ b/src/context.c @@ -647,6 +647,7 @@ upstreams_create(getdns_context *context, size_t size) r->referenced = 1; r->count = 0; r->current_udp = 0; + r->current_stateful = 0; return r; } @@ -721,17 +722,17 @@ _getdns_upstream_shutdown(getdns_upstream *upstream) if (upstream->tls_auth_state > upstream->best_tls_auth_state) upstream->best_tls_auth_state = upstream->tls_auth_state; #if defined(DAEMON_DEBUG) && DAEMON_DEBUG - DEBUG_DAEMON("%s %s : Conn closed : Transport=%s - Resp=%d,Timeouts=%d,Auth=%s,Keepalive(ms)=%d\n", + DEBUG_DAEMON("%s %-40s : Conn closed : Transport=%s - Resp=%d,Timeouts=%d,Auth=%s,Keepalive(ms)=%d\n", STUB_DEBUG_DAEMON, upstream->addr_str, (upstream->transport == GETDNS_TRANSPORT_TLS ? "TLS" : "TCP"), (int)upstream->responses_received, (int)upstream->responses_timeouts, _getdns_auth_str(upstream->tls_auth_state), (int)upstream->keepalive_timeout); - DEBUG_DAEMON("%s %s : Upstream stats: Transport=%s - Resp=%d,Timeouts=%d,Best_auth=%s\n", + DEBUG_DAEMON("%s %-40s : Upstream stats: Transport=%s - Resp=%d,Timeouts=%d,Best_auth=%s\n", STUB_DEBUG_DAEMON, upstream->addr_str, (upstream->transport == GETDNS_TRANSPORT_TLS ? "TLS" : "TCP"), (int)upstream->total_responses, (int)upstream->total_timeouts, _getdns_auth_str(upstream->best_tls_auth_state)); - DEBUG_DAEMON("%s %s : Upstream stats: Transport=%s - Conns=%d,Conn_fails=%d,Conn_shutdowns=%d,Backoffs=%d\n", + DEBUG_DAEMON("%s %-40s : Upstream stats: Transport=%s - Conns=%d,Conn_fails=%d,Conn_shutdowns=%d,Backoffs=%d\n", STUB_DEBUG_DAEMON, upstream->addr_str, (upstream->transport == GETDNS_TRANSPORT_TLS ? "TLS" : "TCP"), (int)upstream->conn_completed, (int)upstream->conn_setup_failed, @@ -760,7 +761,7 @@ _getdns_upstream_shutdown(getdns_upstream *upstream) upstream->conn_shutdowns = 0; upstream->conn_backoffs++; #if defined(DAEMON_DEBUG) && DAEMON_DEBUG - DEBUG_DAEMON("%s %s : !Backing off this upstream - Will retry as new upstream at %s", + DEBUG_DAEMON("%s %-40s : !Backing off this upstream - Will retry as new upstream at %s", STUB_DEBUG_DAEMON, upstream->addr_str, asctime(gmtime(&upstream->conn_retry_time))); #endif @@ -1428,6 +1429,7 @@ getdns_context_create_with_extended_memory_functions( goto error; result->tls_auth = GETDNS_AUTHENTICATION_NONE; result->tls_auth_min = GETDNS_AUTHENTICATION_NONE; + result->tls_use_all_upstreams = 0; result->limit_outstanding_queries = 0; /* unbound context is initialized here */ @@ -2031,6 +2033,27 @@ getdns_context_set_tls_authentication(getdns_context *context, return GETDNS_RETURN_GOOD; } /* getdns_context_set_tls_authentication_list */ +/* + * getdns_context_set_tls_use_all_upstreams + * + */ +getdns_return_t +getdns_context_set_tls_use_all_upstreams(getdns_context *context, uint8_t value) +{ + RETURN_IF_NULL(context, GETDNS_RETURN_INVALID_PARAMETER); + /* only allow 0 or 1 */ + if (value != 0 && value != 1) { + return GETDNS_RETURN_CONTEXT_UPDATE_FAIL; + } + + context->tls_use_all_upstreams = value; + + dispatch_updated(context, GETDNS_CONTEXT_CODE_TLS_USE_ALL_UPSTREAMS); + + return GETDNS_RETURN_GOOD; +} /* getdns_context_set_tls_use_all_upstreams */ + + #ifdef HAVE_LIBUNBOUND static void set_ub_limit_outstanding_queries(getdns_context* context, uint16_t value) { @@ -3467,7 +3490,9 @@ _get_context_settings(getdns_context* context) || getdns_dict_set_int(result, "append_name", context->append_name) || getdns_dict_set_int(result, "tls_authentication", - context->tls_auth)) + context->tls_auth) + || getdns_dict_set_int(result, "tls_use_all_upstreams", + context->tls_use_all_upstreams)) goto error; /* list fields */ @@ -3763,6 +3788,15 @@ getdns_context_get_tls_authentication(getdns_context *context, return GETDNS_RETURN_GOOD; } +getdns_return_t +getdns_context_get_tls_use_all_upstreams(getdns_context *context, + uint8_t* value) { + RETURN_IF_NULL(context, GETDNS_RETURN_INVALID_PARAMETER); + RETURN_IF_NULL(value, GETDNS_RETURN_INVALID_PARAMETER); + *value = context->tls_use_all_upstreams; + return GETDNS_RETURN_GOOD; +} + getdns_return_t getdns_context_get_limit_outstanding_queries(getdns_context *context, uint16_t* value) { @@ -4158,6 +4192,7 @@ _getdns_context_config_setting(getdns_context *context, CONTEXT_SETTING_INT(edns_client_subnet_private) CONTEXT_SETTING_INT(tls_authentication) + CONTEXT_SETTING_INT(tls_use_all_upstreams) CONTEXT_SETTING_INT(tls_query_padding_blocksize) /**************************************/ diff --git a/src/context.h b/src/context.h index e6ed49fb..df9eb88f 100644 --- a/src/context.h +++ b/src/context.h @@ -217,6 +217,7 @@ typedef struct getdns_upstreams { size_t referenced; size_t count; size_t current_udp; + size_t current_stateful; getdns_upstream upstreams[]; } getdns_upstreams; @@ -248,6 +249,7 @@ struct getdns_context { uint32_t dnssec_allowed_skew; getdns_tls_authentication_t tls_auth; /* What user requested for TLS*/ getdns_tls_authentication_t tls_auth_min; /* Derived minimum auth allowed*/ + uint8_t tls_use_all_upstreams; getdns_transport_list_t *dns_transports; size_t dns_transport_count; diff --git a/src/getdns/getdns_extra.h.in b/src/getdns/getdns_extra.h.in index a76564eb..90c33c44 100644 --- a/src/getdns/getdns_extra.h.in +++ b/src/getdns/getdns_extra.h.in @@ -76,6 +76,8 @@ extern "C" { #define GETDNS_CONTEXT_CODE_TLS_QUERY_PADDING_BLOCKSIZE_TEXT "Change related to getdns_context_set_tls_query_padding_blocksize" #define GETDNS_CONTEXT_CODE_PUBKEY_PINSET 621 #define GETDNS_CONTEXT_CODE_PUBKEY_PINSET_TEXT "Change related to getdns_context_set_pubkey_pinset" +#define GETDNS_CONTEXT_CODE_TLS_USE_ALL_UPSTREAMS 622 +#define GETDNS_CONTEXT_CODE_TLS_USE_ALL_UPSTREAMS_TEXT "Change related to getdns_context_set_pubkey_pinset" /** @} */ @@ -265,6 +267,9 @@ getdns_return_t getdns_context_set_tls_authentication( getdns_context *context, getdns_tls_authentication_t value); +getdns_return_t +getdns_context_set_tls_use_all_upstreams(getdns_context *context, uint8_t value); + getdns_return_t getdns_context_set_edns_client_subnet_private(getdns_context *context, uint8_t value); @@ -356,6 +361,10 @@ getdns_return_t getdns_context_get_tls_authentication(getdns_context *context, getdns_tls_authentication_t* value); +getdns_return_t +getdns_context_get_tls_use_all_upstreams(getdns_context *context, + uint8_t* value); + /** * Get the currently registered callback function and user defined argument * for context changes. diff --git a/src/libgetdns.symbols b/src/libgetdns.symbols index 6e3cb128..6890dc85 100644 --- a/src/libgetdns.symbols +++ b/src/libgetdns.symbols @@ -30,6 +30,7 @@ getdns_context_get_suffix getdns_context_get_timeout getdns_context_get_tls_authentication getdns_context_get_tls_query_padding_blocksize +getdns_context_get_tls_use_all_upstreams getdns_context_get_update_callback getdns_context_get_upstream_recursive_servers getdns_context_process_async @@ -60,6 +61,7 @@ getdns_context_set_suffix getdns_context_set_timeout getdns_context_set_tls_authentication getdns_context_set_tls_query_padding_blocksize +getdns_context_get_tls_use_all_upstreams getdns_context_set_update_callback getdns_context_set_upstream_recursive_servers getdns_context_set_use_threads diff --git a/src/stub.c b/src/stub.c index e905f600..fe4c52c2 100644 --- a/src/stub.c +++ b/src/stub.c @@ -591,7 +591,7 @@ stub_timeout_cb(void *userarg) netreq->upstream->udp_timeouts++; #if defined(DAEMON_DEBUG) && DAEMON_DEBUG if (netreq->upstream->udp_timeouts % 100 == 0) - DEBUG_DAEMON("%s %s : Upstream stats: Transport=UDP - Resp=%d,Timeouts=%d\n", + DEBUG_DAEMON("%s %-40s : Upstream stats: Transport=UDP - Resp=%d,Timeouts=%d\n", STUB_DEBUG_DAEMON, netreq->upstream->addr_str, (int)netreq->upstream->udp_responses, (int)netreq->upstream->udp_timeouts); #endif @@ -907,7 +907,7 @@ tls_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) STUB_DEBUG_SETUP_TLS, __FUNC__, upstream->fd); #if defined(DAEMON_DEBUG) && DAEMON_DEBUG else - DEBUG_DAEMON("%s %s : Conn failed : Transport=TLS - *Failure* - Pinset validation failure\n", + DEBUG_DAEMON("%s %-40s : Conn failed : Transport=TLS - *Failure* - Pinset validation failure\n", STUB_DEBUG_DAEMON, upstream->addr_str); #endif } else { @@ -1373,7 +1373,7 @@ stub_udp_read_cb(void *userarg) #if defined(DAEMON_DEBUG) && DAEMON_DEBUG if (upstream->udp_responses == 1 || upstream->udp_responses % 100 == 0) - DEBUG_DAEMON("%s %s : Upstream stats: Transport=UDP - Resp=%d,Timeouts=%d\n", + DEBUG_DAEMON("%s %-40s : Upstream stats: Transport=UDP - Resp=%d,Timeouts=%d\n", STUB_DEBUG_DAEMON, upstream->addr_str, (int)upstream->udp_responses, (int)upstream->udp_timeouts); #endif @@ -1608,7 +1608,7 @@ upstream_write_cb(void *userarg) /* Cleaning up after connection or auth check failure. Need to fallback. */ stub_cleanup(netreq); #if defined(DAEMON_DEBUG) && DAEMON_DEBUG - DEBUG_DAEMON("%s %s : Conn closed : Transport=%s - *Failure*\n", + DEBUG_DAEMON("%s %-40s : Conn closed : Transport=%s - *Failure*\n", STUB_DEBUG_DAEMON, upstream->addr_str, (upstream->transport == GETDNS_TRANSPORT_TLS ? "TLS" : "TCP")); #endif @@ -1672,6 +1672,17 @@ upstream_active(getdns_upstream *upstream) return 0; } +static int +upstream_usable(getdns_upstream *upstream) +{ + 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; + return 0; +} + static int upstream_auth_status_ok(getdns_upstream *upstream, getdns_network_req *netreq) { if (netreq->tls_auth_min != GETDNS_AUTHENTICATION_REQUIRED) @@ -1692,10 +1703,16 @@ upstream_valid(getdns_upstream *upstream, getdns_transport_list_t transport, getdns_network_req *netreq) { - if (upstream->transport != transport || upstream->conn_state != GETDNS_CONN_CLOSED) + if (upstream->transport != transport && upstream_usable(upstream)) 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; @@ -1728,7 +1745,7 @@ upstream_select_stateful(getdns_network_req *netreq, getdns_transport_list_t tra getdns_upstreams *upstreams = netreq->owner->upstreams; size_t i; time_t now = time(NULL); - + if (!upstreams->count) return NULL; @@ -1738,37 +1755,55 @@ upstream_select_stateful(getdns_network_req *netreq, getdns_transport_list_t tra upstreams->upstreams[i].conn_retry_time < now) { upstreams->upstreams[i].conn_state = GETDNS_CONN_CLOSED; #if defined(DAEMON_DEBUG) && DAEMON_DEBUG - DEBUG_DAEMON("%s %s : Re-instating upstream\n", + DEBUG_DAEMON("%s %-40s : Re-instating upstream\n", STUB_DEBUG_DAEMON, upstreams->upstreams[i].addr_str); #endif } } - /* 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]; + if (netreq->owner->context->tls_use_all_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 - we will have to open one. Choose the first one that has the best stats - and the right properties, but because we completely back off failed + /* OK - Find the next one to use. First check we have at least one valid + upstream because we completely back off failed upstreams we may have no valid upstream at all (in contrast to UDP). This will be better communicated to the user when we have better error codes*/ - for (i = 0; i < upstreams->count; i++) { + i = upstreams->current_stateful; + do { DEBUG_STUB("%s %-35s: Testing upstreams %d %d\n", STUB_DEBUG_SETUP, __FUNC__, (int)i, (int)upstreams->upstreams[i].conn_state); if (upstream_valid(&upstreams->upstreams[i], transport, netreq)) { upstream = &upstreams->upstreams[i]; break; } - } + i++; + if (i >= upstreams->count) + i = 0; + } while (i != upstreams->current_stateful); if (!upstream) return NULL; - for (i++; i < upstreams->count; i++) { - if (upstream_valid(&upstreams->upstreams[i], transport, netreq) && - upstream_stats(&upstreams->upstreams[i]) > upstream_stats(upstream)) - upstream = &upstreams->upstreams[i]; + + /* Now select the specific upstream */ + if (netreq->owner->context->tls_use_all_upstreams == 0) { + /* Base the decision on the stats, noting we will have started from 0*/ + for (i++; i < upstreams->count; i++) { + if (upstream_valid(&upstreams->upstreams[i], transport, netreq) && + 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; } @@ -1853,7 +1888,7 @@ upstream_connect(getdns_upstream *upstream, getdns_transport_list_t transport, } upstream->conn_state = GETDNS_CONN_SETUP; #if defined(DAEMON_DEBUG) && DAEMON_DEBUG - DEBUG_DAEMON("%s %s : Conn init : Transport=%s - Profile=%s\n", STUB_DEBUG_DAEMON, + DEBUG_DAEMON("%s %-40s : Conn init : Transport=%s - Profile=%s\n", STUB_DEBUG_DAEMON, upstream->addr_str, transport == GETDNS_TRANSPORT_TLS ? "TLS":"TCP", dnsreq->context->tls_auth_min == GETDNS_AUTHENTICATION_NONE ? "Opportunistic":"Strict"); #endif diff --git a/src/tools/stubby.conf b/src/tools/stubby.conf index 1055e277..0bda0e7c 100644 --- a/src/tools/stubby.conf +++ b/src/tools/stubby.conf @@ -1,5 +1,11 @@ { resolution_type: GETDNS_RESOLUTION_STUB , dns_transport_list: [ GETDNS_TRANSPORT_TLS ] +, tls_authentication: GETDNS_AUTHENTICATION_REQUIRED +, tls_query_padding_blocksize: 256 +, edns_client_subnet_private : 1 +, listen_addresses: [ 127.0.0.1, 0::1 ] +, idle_timeout: 10000 +, tls_use_all_upstreams: 1 , upstream_recursive_servers: [ { address_data: 145.100.185.15 , tls_auth_name: "dnsovertls.sinodun.com" @@ -56,9 +62,4 @@ } ] } ] -, tls_authentication: GETDNS_AUTHENTICATION_REQUIRED -, tls_query_padding_blocksize: 256 -, edns_client_subnet_private : 1 -, listen_addresses: [ 127.0.0.1, 0::1 ] -, idle_timeout: 10000 }