From f2ae55858f99383769723280629fe84dfeea1e15 Mon Sep 17 00:00:00 2001 From: Sara Dickinson Date: Sun, 19 Apr 2015 17:16:58 +0100 Subject: [PATCH 01/12] First pass at making handshake async. Lots of issues with this code still - timeouts are not being rescheduled on fallback - several error cases are not being handled correctly (e.g. 8.8.8.8) and a user callback is not always called - the fallback mechanism is not generic (specific to tls to tcp) --- src/context.c | 162 +++++++------ src/context.h | 22 +- src/request-internal.c | 1 + src/stub.c | 502 ++++++++++++++++++++++++----------------- src/types-internal.h | 1 + 5 files changed, 405 insertions(+), 283 deletions(-) diff --git a/src/context.c b/src/context.c index 45e4b472..e1f31e24 100644 --- a/src/context.c +++ b/src/context.c @@ -62,6 +62,18 @@ typedef struct host_name_addrs { uint8_t host_name[]; } host_name_addrs; +static in_port_t +getdns_port_array[GETDNS_PORT_LAST] = { + GETDNS_PORT_NUM_TCP, + GETDNS_PORT_NUM_TLS +}; + +// char* +// getdns_port_str_array[] = { +// "53", +// "1021" +// }; + /* Private functions */ getdns_return_t create_default_namespaces(struct getdns_context *context); static struct getdns_list *create_default_root_servers(void); @@ -240,7 +252,7 @@ sockaddr_dict(getdns_context *context, struct sockaddr *sa) break; port = ntohs(((struct sockaddr_in *)sa)->sin_port); - if (port != 0 && port != 53 && + if (port != 0 && port != GETDNS_PORT_NUM_TCP && getdns_dict_set_int(address, "port", (uint32_t)port)) break; @@ -256,7 +268,7 @@ sockaddr_dict(getdns_context *context, struct sockaddr *sa) break; port = ntohs(((struct sockaddr_in6 *)sa)->sin6_port); - if (port != 0 && port != 53 && + if (port != 0 && port != GETDNS_PORT_NUM_TCP && getdns_dict_set_int(address, "port", (uint32_t)port)) break; @@ -527,10 +539,7 @@ upstream_ntop_buf(getdns_upstream *upstream, getdns_transport_t transport, if (upstream_scope_id(upstream)) (void) snprintf(buf + strlen(buf), len - strlen(buf), "%%%d", (int)*upstream_scope_id(upstream)); - if (transport == GETDNS_TRANSPORT_TLS_ONLY_KEEP_CONNECTIONS_OPEN) - (void) snprintf(buf + strlen(buf), len - strlen(buf), - "@%d", GETDNS_TLS_PORT); - else if (upstream_port(upstream) != 53 && upstream_port(upstream) != 0) + else if (upstream_port(upstream) != GETDNS_PORT_NUM_TCP && upstream_port(upstream) != 0) (void) snprintf(buf + strlen(buf), len - strlen(buf), "@%d", (int)upstream_port(upstream)); } @@ -557,6 +566,10 @@ upstream_init(getdns_upstream *upstream, /* For sharing a socket to this upstream with TCP */ upstream->fd = -1; upstream->tls_obj = NULL; + upstream->base_transport = (upstream_port(upstream) == GETDNS_PORT_NUM_TLS ? + GETDNS_TRANSPORT_TLS : + GETDNS_TRANSPORT_TCP); + upstream->tls_hs_state = GETDNS_HS_NONE; upstream->loop = NULL; (void) getdns_eventloop_event_init( &upstream->event, upstream, NULL, NULL, NULL); @@ -659,20 +672,25 @@ set_os_defaults(struct getdns_context *context) token = parse + strcspn(parse, " \t\r\n"); *token = 0; - if ((s = getaddrinfo(parse, "53", &hints, &result))) - continue; + //getdns_port_type_t port_type = GETDNS_PORT_FIRST; + //for (; port_type < GETDNS_PORT_LAST; port_type++) { + // TODO[TLS]: Seeing strange crash in ub_create_ctx when using the loop here.... + //fprintf(stderr,"creating upstream %s\n", parse); + if ((s = getaddrinfo(parse, "53", /*getdns_port_str_array[port_type],*/ &hints, &result))) + continue; - /* No lookups, so maximal 1 result */ - if (! result) continue; + /* No lookups, so maximal 1 result */ + if (! result) continue; - /* Grow array when needed */ - if (context->upstreams->count == upstreams_limit) - context->upstreams = upstreams_resize( - context->upstreams, (upstreams_limit *= 2)); + /* Grow array when needed */ + if (context->upstreams->count == upstreams_limit) + context->upstreams = upstreams_resize( + context->upstreams, (upstreams_limit *= 2)); - upstream = &context->upstreams-> - upstreams[context->upstreams->count++]; - upstream_init(upstream, context->upstreams, result); + upstream = &context->upstreams-> + upstreams[context->upstreams->count++]; + upstream_init(upstream, context->upstreams, result); + //} freeaddrinfo(result); } fclose(in); @@ -1456,63 +1474,68 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, hints.ai_addr = NULL; hints.ai_next = NULL; - upstreams = upstreams_create(context, count); + upstreams = upstreams_create(context, count*2); for (i = 0; i < count; i++) { - getdns_dict *dict; - getdns_bindata *address_type; - getdns_bindata *address_data; - uint32_t port; - getdns_bindata *scope_id; - struct addrinfo *ai; - getdns_upstream *upstream; + /* Loop twice to create TCP and TLS upstreams*/ + getdns_port_type_t port_type = GETDNS_PORT_FIRST; + for (; port_type < GETDNS_PORT_LAST; port_type++) { + getdns_dict *dict; + getdns_bindata *address_type; + getdns_bindata *address_data; + uint32_t port; + getdns_bindata *scope_id; + struct addrinfo *ai; + getdns_upstream *upstream; - upstream = &upstreams->upstreams[upstreams->count]; - if ((r = getdns_list_get_dict(upstream_list, i, &dict))) - goto error; + upstream = &upstreams->upstreams[upstreams->count]; + if ((r = getdns_list_get_dict(upstream_list, i, &dict))) + goto error; - if ((r = getdns_dict_get_bindata( - dict, "address_type",&address_type))) - goto error; - if (address_type->size < 4) - goto invalid_parameter; - if (strncmp((char *)address_type->data, "IPv4", 4) == 0) - upstream->addr.ss_family = AF_INET; - else if (strncmp((char *)address_type->data, "IPv6", 4) == 0) - upstream->addr.ss_family = AF_INET6; - else goto invalid_parameter; - - if ((r = getdns_dict_get_bindata( - dict, "address_data", &address_data))) - goto error; - if ((upstream->addr.ss_family == AF_INET && - address_data->size != 4) || - (upstream->addr.ss_family == AF_INET6 && - address_data->size != 16)) - goto invalid_parameter; - if (inet_ntop(upstream->addr.ss_family, address_data->data, - addrstr, 1024) == NULL) - goto invalid_parameter; - - port = 53; - (void) getdns_dict_get_int(dict, "port", &port); - (void) snprintf(portstr, 1024, "%d", (int)port); - - if (getdns_dict_get_bindata(dict, "scope_id", &scope_id) == - GETDNS_RETURN_GOOD) { - if (strlen(addrstr) + scope_id->size > 1022) + if ((r = getdns_dict_get_bindata( + dict, "address_type",&address_type))) + goto error; + if (address_type->size < 4) goto invalid_parameter; - eos = &addrstr[strlen(addrstr)]; - *eos++ = '%'; - (void) memcpy(eos, scope_id->data, scope_id->size); - eos[scope_id->size] = 0; + if (strncmp((char *)address_type->data, "IPv4", 4) == 0) + upstream->addr.ss_family = AF_INET; + else if (strncmp((char *)address_type->data, "IPv6", 4) == 0) + upstream->addr.ss_family = AF_INET6; + else goto invalid_parameter; + + if ((r = getdns_dict_get_bindata( + dict, "address_data", &address_data))) + goto error; + if ((upstream->addr.ss_family == AF_INET && + address_data->size != 4) || + (upstream->addr.ss_family == AF_INET6 && + address_data->size != 16)) + goto invalid_parameter; + if (inet_ntop(upstream->addr.ss_family, address_data->data, + addrstr, 1024) == NULL) + goto invalid_parameter; + + /* So should we be throwing away the port the user set?*/ + port = (uint32_t)(int)getdns_port_array[port_type]; + (void) getdns_dict_get_int(dict, "port", &port); + (void) snprintf(portstr, 1024, "%d", (int)port); + + if (getdns_dict_get_bindata(dict, "scope_id", &scope_id) == + GETDNS_RETURN_GOOD) { + if (strlen(addrstr) + scope_id->size > 1022) + goto invalid_parameter; + eos = &addrstr[strlen(addrstr)]; + *eos++ = '%'; + (void) memcpy(eos, scope_id->data, scope_id->size); + eos[scope_id->size] = 0; + } + + if (getaddrinfo(addrstr, portstr, &hints, &ai)) + goto invalid_parameter; + + upstream_init(upstream, upstreams, ai); + upstreams->count++; + freeaddrinfo(ai); } - - if (getaddrinfo(addrstr, portstr, &hints, &ai)) - goto invalid_parameter; - - upstream_init(upstream, upstreams, ai); - upstreams->count++; - freeaddrinfo(ai); } priv_getdns_upstreams_dereference(context->upstreams); context->upstreams = upstreams; @@ -1729,6 +1752,7 @@ ub_setup_stub(struct ub_ctx *ctx, getdns_context *context) getdns_upstreams *upstreams = context->upstreams; (void) ub_ctx_set_fwd(ctx, NULL); + /*TODO[TLS]: Order the upstreams so the TLS ones are first if doing TLS*/ for (i = 0; i < upstreams->count; i++) { upstream = &upstreams->upstreams[i]; upstream_ntop_buf(upstream, context->dns_transport, addr, 1024); diff --git a/src/context.h b/src/context.h index 12a2c263..f2c1f698 100644 --- a/src/context.h +++ b/src/context.h @@ -49,7 +49,10 @@ struct ub_ctx; #define GETDNS_FN_RESOLVCONF "/etc/resolv.conf" #define GETDNS_FN_HOSTS "/etc/hosts" -#define GETDNS_TLS_PORT 1021 +#define GETDNS_PORT_NUM_TCP 53 +#define GETDNS_PORT_NUM_TLS 1021 +#define GETDNS_PORT_STR_TCP "53" +#define GETDNS_PORT_STR_TLS "1021" enum filechgs { GETDNS_FCHG_ERRORS = -1 , GETDNS_FCHG_NOERROR = 0 @@ -80,6 +83,21 @@ typedef enum getdns_base_transport { GETDNS_TRANSPORT_TLS } getdns_base_transport_t; +typedef enum getdns_port_type { + GETDNS_PORT_FIRST = 0, + GETDNS_PORT_TCP = 0, + GETDNS_PORT_TLS = 1, + GETDNS_PORT_LAST = 2 +} getdns_port_type_t; + +typedef enum getdns_tls_hs_state { + GETDNS_HS_NONE, + GETDNS_HS_WRITE, + GETDNS_HS_READ, + GETDNS_HS_DONE, + GETDNS_HS_FAILED +} getdns_tls_hs_state_t; + typedef struct getdns_upstream { struct getdns_upstreams *upstreams; @@ -93,6 +111,8 @@ typedef struct getdns_upstream { /* For sharing a TCP socket to this upstream */ int fd; SSL* tls_obj; + getdns_base_transport_t base_transport; + getdns_tls_hs_state_t tls_hs_state; getdns_eventloop_event event; getdns_eventloop *loop; getdns_tcp_state tcp; diff --git a/src/request-internal.c b/src/request-internal.c index 0889b3fc..e9bb3b22 100644 --- a/src/request-internal.c +++ b/src/request-internal.c @@ -89,6 +89,7 @@ network_req_init(getdns_network_req *net_req, getdns_dns_req *owner, net_req->upstream = NULL; net_req->fd = -1; + net_req->transport = GETDNS_TRANSPORT_UDP_FIRST_AND_FALL_BACK_TO_TCP; memset(&net_req->event, 0, sizeof(net_req->event)); memset(&net_req->tcp, 0, sizeof(net_req->tcp)); net_req->query_id = 0; diff --git a/src/stub.c b/src/stub.c index 0914aa57..775ff9ad 100755 --- a/src/stub.c +++ b/src/stub.c @@ -41,10 +41,23 @@ #include "util-internal.h" #include "general.h" +#define STUB_TLS_SETUP_ERROR -3 +#define STUB_TCP_AGAIN -2 +#define STUB_TCP_ERROR -1 + 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 int tcp_connect (getdns_upstream *upstream, getdns_base_transport_t transport); +static int connect_to_upstream(getdns_upstream *upstream, + getdns_base_transport_t transport, + getdns_context *context); +static void upstream_schedule_netreq(getdns_upstream *upstream, + getdns_network_req *netreq); + static void rollover_secret() { @@ -305,7 +318,7 @@ static void upstream_erred(getdns_upstream *upstream) { getdns_network_req *netreq; - + fprintf(stderr,"[TLS]: upstream_erred\n"); while ((netreq = upstream->write_queue)) { stub_cleanup(netreq); netreq->state = NET_REQ_FINISHED; @@ -318,8 +331,6 @@ upstream_erred(getdns_upstream *upstream) netreq->state = NET_REQ_FINISHED; priv_getdns_check_dns_req_complete(netreq->owner); } - /* TODO[TLS]: When we get an error (which is probably a timeout) and are - * using to keep connections open should we leave the connection up here? */ if (upstream->tls_obj) { SSL_shutdown(upstream->tls_obj); SSL_free(upstream->tls_obj); @@ -327,6 +338,7 @@ upstream_erred(getdns_upstream *upstream) } close(upstream->fd); upstream->fd = -1; + /*TODO[TLS]: Upstream errors don't trigger the user callback....*/ } void @@ -339,8 +351,11 @@ priv_getdns_cancel_stub_request(getdns_network_req *netreq) static void stub_erred(getdns_network_req *netreq) { + fprintf(stderr,"[TLS]: stub_erred\n"); stub_next_upstream(netreq); stub_cleanup(netreq); + /* TODO[TLS]: When we get an error (which is probably a timeout) and are + * using to keep connections open should we leave the connection up here? */ if (netreq->fd >= 0) close(netreq->fd); netreq->state = NET_REQ_FINISHED; priv_getdns_check_dns_req_complete(netreq->owner); @@ -349,6 +364,7 @@ stub_erred(getdns_network_req *netreq) static void stub_timeout_cb(void *userarg) { + fprintf(stderr,"[TLS]: stub_timeout_cb\n"); getdns_network_req *netreq = (getdns_network_req *)userarg; stub_next_upstream(netreq); @@ -460,8 +476,19 @@ stub_udp_write_cb(void *userarg) stub_udp_read_cb, NULL, stub_timeout_cb)); } + +static int +transport_matches(struct getdns_upstream *upstream, getdns_base_transport_t transport) { + if (upstream->base_transport != transport) + return 0; + if (transport == GETDNS_TRANSPORT_TLS && + upstream->tls_hs_state == GETDNS_HS_FAILED) + return 0; + return 1; +} + static getdns_upstream * -pick_upstream(getdns_dns_req *dnsreq) +pick_upstream(getdns_dns_req *dnsreq, int level) { getdns_upstream *upstream; size_t i; @@ -469,13 +496,17 @@ pick_upstream(getdns_dns_req *dnsreq) if (!dnsreq->upstreams->count) return NULL; + getdns_base_transport_t transport = priv_get_base_transport( + dnsreq->context->dns_transport, level); + for (i = 0; i < dnsreq->upstreams->count; i++) if (dnsreq->upstreams->upstreams[i].to_retry <= 0) dnsreq->upstreams->upstreams[i].to_retry++; i = dnsreq->upstreams->current; do { - if (dnsreq->upstreams->upstreams[i].to_retry > 0) { + if (dnsreq->upstreams->upstreams[i].to_retry > 0 && + transport_matches(&dnsreq->upstreams->upstreams[i], transport)) { dnsreq->upstreams->current = i; return &dnsreq->upstreams->upstreams[i]; } @@ -485,8 +516,8 @@ pick_upstream(getdns_dns_req *dnsreq) upstream = dnsreq->upstreams->upstreams; for (i = 1; i < dnsreq->upstreams->count; i++) - if (dnsreq->upstreams->upstreams[i].back_off < - upstream->back_off) + if (dnsreq->upstreams->upstreams[i].back_off < upstream->back_off && + transport_matches(&dnsreq->upstreams->upstreams[i], transport)) upstream = &dnsreq->upstreams->upstreams[i]; upstream->back_off++; @@ -495,9 +526,6 @@ pick_upstream(getdns_dns_req *dnsreq) return upstream; } -#define STUB_TCP_AGAIN -2 -#define STUB_TCP_ERROR -1 - static int stub_tcp_read(int fd, getdns_tcp_state *tcp, struct mem_funcs *mf) { @@ -602,117 +630,174 @@ stub_tcp_read_cb(void *userarg) } } -/** wait for a socket to become ready */ -static int -sock_wait(int sockfd) -{ - int ret; - fd_set fds; - FD_ZERO(&fds); - FD_SET(FD_SET_T sockfd, &fds); - /*TODO[TLS]: Pick up this timeout from the context*/ - struct timeval timeout = {5, 0 }; - ret = select(sockfd+1, NULL, &fds, NULL, &timeout); - if(ret == 0) - /* timeout expired */ - return 0; - else if(ret == -1) - /* error */ - return 0; - return 1; -} - -static int -sock_connected(int sockfd) -{ - /* wait(write) until connected or error */ - while(1) { - int error = 0; - socklen_t len = (socklen_t)sizeof(error); - - if(!sock_wait(sockfd)) { - close(sockfd); - return -1; - } - - /* check if there is a pending error for nonblocking connect */ - if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void*)&error, &len) < 0) { - error = errno; /* on solaris errno is error */ - } - if (error == EINPROGRESS || error == EWOULDBLOCK) - continue; /* try again */ - else if (error != 0) { - close(sockfd); - return -1; - } - /* connected */ - break; - } - return sockfd; -} - -/* The connection testing and handshake should be handled by integrating this - * with the event loop framework, but for now just implement a standalone - * handshake method.*/ static SSL* -do_tls_handshake(getdns_dns_req *dnsreq, getdns_upstream *upstream) +create_tls_object(getdns_context *context, int fd) { - /*Lets make sure the connection is up before we try a handshake*/ - if (errno == EINPROGRESS && sock_connected(upstream->fd) == -1) { - return NULL; - } - /* Create SSL instance */ - if (dnsreq->context->tls_ctx == NULL) + if (context->tls_ctx == NULL) return NULL; - SSL* ssl = SSL_new(dnsreq->context->tls_ctx); + SSL* ssl = SSL_new(context->tls_ctx); if(!ssl) { return NULL; } /* Connect the SSL object with a file descriptor */ - if(!SSL_set_fd(ssl, upstream->fd)) { + if(!SSL_set_fd(ssl,fd)) { SSL_free(ssl); return NULL; } SSL_set_connect_state(ssl); (void) SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); - - int r; - int want; - fd_set fds; - FD_ZERO(&fds); - FD_SET(upstream->fd, &fds); - struct timeval timeout = {dnsreq->context->timeout/1000, 0 }; - while ((r = SSL_do_handshake(ssl)) != 1) - { - want = SSL_get_error(ssl, r); - switch (want) { - case SSL_ERROR_WANT_READ: - if (select(upstream->fd + 1, &fds, NULL, NULL, &timeout) == 0) { - SSL_free(ssl); - return NULL; - } - break; - case SSL_ERROR_WANT_WRITE: - if (select(upstream->fd + 1, NULL, &fds, NULL, &timeout) == 0) { - SSL_free(ssl); - return NULL; - } - break; - default: - SSL_free(ssl); - return NULL; - } - } return ssl; } static int -stub_tls_read(SSL* tls_obj, getdns_tcp_state *tcp, struct mem_funcs *mf) +do_tls_handshake(getdns_upstream *upstream) +{ + + fprintf(stderr,"[TLS]: do_tls_handshake\n"); + + int r; + int want; + ERR_clear_error(); + while ((r = SSL_do_handshake(upstream->tls_obj)) != 1) + { + want = SSL_get_error(upstream->tls_obj, r); + switch (want) { + case SSL_ERROR_WANT_READ: + fprintf(stderr,"[TLS]: SSL_ERROR_WANT_READ\n"); + upstream->event.read_cb = upstream_read_cb; + upstream->event.write_cb = NULL; + GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); + GETDNS_SCHEDULE_EVENT(upstream->loop, + upstream->fd, TIMEOUT_FOREVER, &upstream->event); + upstream->tls_hs_state = GETDNS_HS_READ; + return STUB_TCP_AGAIN; + case SSL_ERROR_WANT_WRITE: + fprintf(stderr,"[TLS]: SSL_ERROR_WANT_WRITE\n"); + upstream->event.read_cb = NULL; + upstream->event.write_cb = upstream_write_cb; + GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); + GETDNS_SCHEDULE_EVENT(upstream->loop, + upstream->fd, TIMEOUT_FOREVER, &upstream->event); + upstream->tls_hs_state = GETDNS_HS_WRITE; + return STUB_TCP_AGAIN; + default: + SSL_free(upstream->tls_obj); + upstream->tls_obj = NULL; + upstream->tls_hs_state = GETDNS_HS_FAILED; + upstream->fd = -1; + return STUB_TLS_SETUP_ERROR; + } + } + upstream->tls_hs_state = GETDNS_HS_DONE; + upstream->event.read_cb = NULL; + upstream->event.write_cb = upstream_write_cb; + GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); + GETDNS_SCHEDULE_EVENT(upstream->loop, + upstream->fd, TIMEOUT_FOREVER, &upstream->event); + return 0; +} + +/* TODO[TLS]: Could think about fallback on read error aswell.*/ +static int +fallback_on_write(getdns_network_req *netreq) { + + /* This should really check if any request in the queue can fallback...*/ + if (netreq->transport != GETDNS_TRANSPORT_TLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN) + return STUB_TCP_ERROR; + + /* Deal with old upstream */ + getdns_upstream *upstream = netreq->upstream; + upstream->write_queue = NULL; + upstream->write_queue_last = NULL; + upstream->event.write_cb = NULL; + GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); + + /* Now set up new upstream */ + getdns_upstream *new_upstream = pick_upstream(netreq->owner, 1); + if (!new_upstream) + return STUB_TCP_ERROR; + + /* get transport generically*/ + int fd = connect_to_upstream(new_upstream, GETDNS_TRANSPORT_TCP, netreq->owner->context); + if (fd == -1) + return STUB_TCP_ERROR; + + fprintf(stderr,"[TLS]: tcp_fallback to %d \n", new_upstream->fd); + getdns_network_req *next_req; + while (netreq != NULL) { + next_req = netreq->write_queue_tail; + if (netreq->transport == GETDNS_TRANSPORT_TLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN) { + netreq->upstream = new_upstream; + upstream_schedule_netreq(new_upstream, netreq); + /* TODO: Timout need to be adjusted and rescheduled on the new fd ....*/ + /* Note, setup timeout should be shorter than message timeout for + * messages with fallback or don't have time to re-try. */ + } + /*else.... leave request to timeout?*/ + netreq = next_req; + } + + return STUB_TCP_AGAIN; +} + +static int +setup_tls(getdns_upstream* upstream) +{ + int ret; + /* Already have a connection*/ + if (upstream->tls_hs_state == GETDNS_HS_DONE && + (upstream->tls_obj != NULL) && (upstream->fd != -1)) + return 0; + + /* Lets make sure the connection is up before we try a handshake*/ + int error = 0; + socklen_t len = (socklen_t)sizeof(error); + /* TODO: This doesn't handle the case where the far end doesn't do a reset + * as is the case with e.g. 8.8.8.8. For that case the timeout kicks in + * and the user callback fails the message without the chance to fallback... + * Note that acutally the TCP code doesn't check the connection state before + * doing a first write either.... + * Perhaps we should have a write_timeout_cb on the write and then schedule + * the stub_timeout_cb for matching the response??? */ + getsockopt(upstream->fd, SOL_SOCKET, SO_ERROR, (void*)&error, &len); + if (error == EINPROGRESS || error == EWOULDBLOCK) { + fprintf(stderr,"[TLS]: blocking.......\n"); + return STUB_TCP_AGAIN; /* try again */ + } + else if (error != 0) { + fprintf(stderr,"[TLS]: died gettting connection\n"); + SSL_free(upstream->tls_obj); + upstream->tls_obj = NULL; + upstream->tls_hs_state = GETDNS_HS_FAILED; + upstream->fd = -1; + return STUB_TLS_SETUP_ERROR; + } + + ret = do_tls_handshake(upstream); + switch (ret) { + case STUB_TCP_AGAIN: + return ret; + case STUB_TCP_ERROR: + fprintf(stderr,"[TLS]: W: Handshake has failed %d\n", upstream->tls_hs_state); + return STUB_TLS_SETUP_ERROR; + default: + fprintf(stderr,"[TLS]: W:after handshake %d, %s\n", upstream->tls_hs_state, upstream->tls_obj== NULL? "NULL":"Not NULL" ); + return 0; + } +} + +static int +stub_tls_read(getdns_upstream *upstream, getdns_tcp_state *tcp, struct mem_funcs *mf) { ssize_t read; uint8_t *buf; size_t buf_size; + SSL* tls_obj = upstream->tls_obj; + + int q = setup_tls(upstream); + if (q != 0) + return q; if (!tcp->read_buf) { /* First time tls read, create a buffer for reading */ @@ -794,9 +879,12 @@ upstream_read_cb(void *userarg) int q; uint16_t query_id; intptr_t query_id_intptr; + + + fprintf(stderr,"[TLS]: upstream_read_cb on %d\n", upstream->fd); if (upstream->tls_obj) - q = stub_tls_read(upstream->tls_obj, &upstream->tcp, + q = stub_tls_read(upstream, &upstream->tcp, &upstream->upstreams->mf); else q = stub_tcp_read(upstream->fd, &upstream->tcp, @@ -1018,12 +1106,17 @@ stub_tcp_write_cb(void *userarg) } static int -stub_tls_write(SSL* tls_obj, getdns_tcp_state *tcp, getdns_network_req *netreq) +stub_tls_write(getdns_upstream *upstream, getdns_tcp_state *tcp, getdns_network_req *netreq) { size_t pkt_len = netreq->response - netreq->query; ssize_t written; uint16_t query_id; intptr_t query_id_intptr; + SSL* tls_obj = upstream->tls_obj; + + int q = setup_tls(upstream); + if (q != 0) + return q; /* Do we have remaining data that we could not write before? */ if (! tcp->write_buf) { @@ -1073,8 +1166,11 @@ upstream_write_cb(void *userarg) getdns_dns_req *dnsreq = netreq->owner; int q; + + fprintf(stderr,"[TLS]: method: upstream_write_cb %d\n", upstream->fd); + if (upstream->tls_obj) - q = stub_tls_write(upstream->tls_obj, &upstream->tcp, netreq); + q = stub_tls_write(upstream, &upstream->tcp, netreq); else q = stub_tcp_write(upstream->fd, &upstream->tcp, netreq); @@ -1086,8 +1182,16 @@ upstream_write_cb(void *userarg) stub_erred(netreq); return; + case STUB_TLS_SETUP_ERROR: + /* Could not complete the TLS set up. Need to fallback on this upstream + * if possible.*/ + if (fallback_on_write(netreq) == STUB_TCP_ERROR) + stub_erred(netreq); + return; + default: netreq->query_id = (uint16_t) q; + fprintf(stderr,"[TLS]: method: upstream_write_cb, successfull write %d\n", upstream->fd); /* Unqueue the netreq from the write_queue */ if (!(upstream->write_queue = netreq->write_queue_tail)) { @@ -1137,6 +1241,8 @@ upstream_schedule_netreq(getdns_upstream *upstream, getdns_network_req *netreq) assert(upstream->fd >= 0); assert(upstream->loop); + fprintf(stderr,"[TLS]: method: upstream_schedule_netreq %d\n", upstream->fd); + /* Append netreq to write_queue */ if (!upstream->write_queue) { upstream->write_queue = upstream->write_queue_last = netreq; @@ -1150,39 +1256,12 @@ upstream_schedule_netreq(getdns_upstream *upstream, getdns_network_req *netreq) } } -static in_port_t -get_port(struct sockaddr_storage* addr) -{ - return ntohs(addr->ss_family == AF_INET - ? ((struct sockaddr_in *)addr)->sin_port - : ((struct sockaddr_in6*)addr)->sin6_port); -} - -static void -set_port(struct sockaddr_storage* addr, in_port_t port) -{ - addr->ss_family == AF_INET - ? (((struct sockaddr_in *)addr)->sin_port = htons(port)) - : (((struct sockaddr_in6*)addr)->sin6_port = htons(port)); -} - static int -tcp_connect (getdns_upstream *upstream, getdns_base_transport_t transport) { +tcp_connect(getdns_upstream *upstream, getdns_base_transport_t transport) +{ - int fd =-1; - struct sockaddr_storage connect_addr; - struct sockaddr_storage* addr = &upstream->addr; - socklen_t addr_len = upstream->addr_len; - - /* TODO[TLS]: For now, override the port to a hardcoded value*/ - if (transport == GETDNS_TRANSPORT_TLS && - (int)get_port(addr) != GETDNS_TLS_PORT) { - connect_addr = upstream->addr; - addr = &connect_addr; - set_port(addr, GETDNS_TLS_PORT); - } - - if ((fd = socket(addr->ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) + int fd = -1; + if ((fd = socket(upstream->addr.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) return -1; getdns_sock_nonblock(fd); @@ -1192,8 +1271,8 @@ tcp_connect (getdns_upstream *upstream, getdns_base_transport_t transport) { transport == GETDNS_TRANSPORT_TCP_SINGLE) return fd; #endif - if (connect(fd, (struct sockaddr *)addr, - addr_len) == -1) { + if (connect(fd, (struct sockaddr *)&upstream->addr, + upstream->addr_len) == -1) { if (errno != EINPROGRESS) { close(fd); return -1; @@ -1202,106 +1281,103 @@ tcp_connect (getdns_upstream *upstream, getdns_base_transport_t transport) { return fd; } +int +connect_to_upstream(getdns_upstream *upstream, getdns_base_transport_t transport, + getdns_context *context) +{ + + if ((transport == GETDNS_TRANSPORT_TCP || + transport == GETDNS_TRANSPORT_TLS) + && upstream->fd != -1) { + fprintf(stderr,"[TLS]: method: tcp_connect using existing fd %d\n", upstream->fd); + return upstream->fd; + } + + int fd; + switch(transport) { + case GETDNS_TRANSPORT_UDP: + if ((fd = socket( + upstream->addr.ss_family, SOCK_DGRAM, IPPROTO_UDP)) == -1) + return -1; + getdns_sock_nonblock(fd); + return fd; + + case GETDNS_TRANSPORT_TCP_SINGLE: + case GETDNS_TRANSPORT_TCP: + fd = tcp_connect(upstream, transport); + break; + + case GETDNS_TRANSPORT_TLS: + fd = tcp_connect(upstream, transport); + if (fd == -1 || + (upstream->tls_obj = create_tls_object(context, fd)) == NULL ) { + close(fd); + return -1; + } + upstream->tls_hs_state = GETDNS_HS_WRITE; + break; + default: + return -1; + /* Nothing to do*/ + } + if (fd != -1) { + upstream->loop = context->extension; + upstream->fd = fd; + } + fprintf(stderr,"[TLS]: method: tcp_connect created new connection %d\n", fd); + return fd; +} + getdns_return_t priv_getdns_submit_stub_request(getdns_network_req *netreq) { - getdns_dns_req *dnsreq = netreq->owner; - getdns_upstream *upstream = pick_upstream(dnsreq); + getdns_dns_req *dnsreq = netreq->owner; - if (!upstream) - return GETDNS_RETURN_GENERIC_ERROR; - - // Work out the primary and fallback transport options + /* TODO[TLS - 1]: This will become a double while loop trying all the upstreams on all the + * transports for a connection since we need a fd to schedule on, using previous known capabilities + * All other set up is done async*/ + /* Work out the primary and fallback transport options */ getdns_base_transport_t transport = priv_get_base_transport( dnsreq->context->dns_transport,0); getdns_base_transport_t fb_transport = priv_get_base_transport( dnsreq->context->dns_transport,1); + getdns_upstream *upstream = pick_upstream(dnsreq, 0); + if (!upstream) + return GETDNS_RETURN_GENERIC_ERROR; + int fd = connect_to_upstream(upstream, transport, dnsreq->context); + if (fd == -1) { + if (fb_transport == GETDNS_TRANSPORT_NONE) + return GETDNS_RETURN_GENERIC_ERROR; + upstream = pick_upstream(dnsreq, 1); + if ((fd = connect_to_upstream(upstream, fb_transport, dnsreq->context)) == -1) + return GETDNS_RETURN_GENERIC_ERROR; + } + + netreq->upstream = upstream; + netreq->transport = dnsreq->context->dns_transport; + switch(transport) { case GETDNS_TRANSPORT_UDP: - - if ((netreq->fd = socket( - upstream->addr.ss_family, SOCK_DGRAM, IPPROTO_UDP)) == -1) - return GETDNS_RETURN_GENERIC_ERROR; - - getdns_sock_nonblock(netreq->fd); - netreq->upstream = upstream; - - GETDNS_SCHEDULE_EVENT( - dnsreq->loop, netreq->fd, dnsreq->context->timeout, - getdns_eventloop_event_init(&netreq->event, netreq, - NULL, stub_udp_write_cb, stub_timeout_cb)); - - return GETDNS_RETURN_GOOD; - case GETDNS_TRANSPORT_TCP_SINGLE: - - if ((netreq->fd = tcp_connect(upstream, transport)) == -1) - return GETDNS_RETURN_GENERIC_ERROR; - netreq->upstream = upstream; - + netreq->fd = fd; GETDNS_SCHEDULE_EVENT( dnsreq->loop, netreq->fd, dnsreq->context->timeout, getdns_eventloop_event_init(&netreq->event, netreq, - NULL, stub_tcp_write_cb, stub_timeout_cb)); - + NULL, (transport == GETDNS_TRANSPORT_UDP ? stub_udp_write_cb: + stub_tcp_write_cb), stub_timeout_cb)); return GETDNS_RETURN_GOOD; case GETDNS_TRANSPORT_TCP: case GETDNS_TRANSPORT_TLS: /* In coming comments, "global" means "context wide" */ - - /* Are we the first? (Is global socket initialized?) */ if (upstream->fd == -1) { - /* TODO[TLS]: We should remember on the context if we had to fallback - * for this upstream so when re-connecting from a dropped TCP - * connection we don't retry TLS. */ - int fallback = 0; - - /* We are the first. Make global socket and connect. */ - if ((upstream->fd = tcp_connect(upstream, transport)) == -1) { - if (fb_transport == GETDNS_TRANSPORT_NONE) - return GETDNS_RETURN_GENERIC_ERROR; - if ((upstream->fd = tcp_connect(upstream, fb_transport)) == -1) - return GETDNS_RETURN_GENERIC_ERROR; - fallback = 1; - } - - /* Now do a handshake for TLS. Note waiting for this to succeed or - * timeout blocks the scheduling of any messages for this upstream*/ - if (transport == GETDNS_TRANSPORT_TLS && (fallback == 0)) { - upstream->tls_obj = do_tls_handshake(dnsreq, upstream); - if (!upstream->tls_obj) { - if (fb_transport == GETDNS_TRANSPORT_NONE) - return GETDNS_RETURN_GENERIC_ERROR; - close(upstream->fd); - if ((upstream->fd = tcp_connect(upstream, fb_transport)) == -1) - return GETDNS_RETURN_GENERIC_ERROR; - } - } - /* Attach to the global event loop - * so it can do it's own scheduling - */ upstream->loop = dnsreq->context->extension; - } else { - /* Cater for the case of the user downgrading and existing TLS - connection to TCP for some reason...*/ - if (transport == GETDNS_TRANSPORT_TCP && upstream->tls_obj) { - SSL_shutdown(upstream->tls_obj); - SSL_free(upstream->tls_obj); - upstream->tls_obj = NULL; - } + upstream->fd = fd; } - netreq->upstream = upstream; - - /* We have a context wide socket. - * Now schedule the write request. - */ upstream_schedule_netreq(upstream, netreq); - - /* Schedule at least the timeout locally. - * And also the write if we perform a synchronous lookup - */ + /* TODO[TLS]: Timeout handling for async calls must change.... + * Maybe even change scheduling for sync calls here too*/ GETDNS_SCHEDULE_EVENT( dnsreq->loop, upstream->fd, dnsreq->context->timeout, getdns_eventloop_event_init(&netreq->event, netreq, NULL, @@ -1314,4 +1390,4 @@ priv_getdns_submit_stub_request(getdns_network_req *netreq) } } -/* stub.c */ +/* stub.c */ \ No newline at end of file diff --git a/src/types-internal.h b/src/types-internal.h index 5220d599..7ca48d7b 100644 --- a/src/types-internal.h +++ b/src/types-internal.h @@ -191,6 +191,7 @@ typedef struct getdns_network_req /* For stub resolving */ struct getdns_upstream *upstream; int fd; + getdns_transport_t transport; getdns_eventloop_event event; getdns_tcp_state tcp; uint16_t query_id; From 3de15ad78293da2aaeadff14d70474d9a7ef7cfa Mon Sep 17 00:00:00 2001 From: Sara Dickinson Date: Fri, 24 Apr 2015 16:29:08 +0100 Subject: [PATCH 02/12] Change internal transport handling to use a list, not a fixed type --- src/context.c | 119 +++++++++++----------- src/context.h | 14 +-- src/request-internal.c | 4 +- src/stub.c | 223 ++++++++++++++++++++--------------------- src/types-internal.h | 13 ++- 5 files changed, 190 insertions(+), 183 deletions(-) diff --git a/src/context.c b/src/context.c index e1f31e24..fb3140d5 100644 --- a/src/context.c +++ b/src/context.c @@ -526,8 +526,7 @@ upstream_scope_id(getdns_upstream *upstream) } static void -upstream_ntop_buf(getdns_upstream *upstream, getdns_transport_t transport, - char *buf, size_t len) +upstream_ntop_buf(getdns_upstream *upstream, char *buf, size_t len) { /* Also possible but prints scope_id by name (nor parsed by unbound) * @@ -566,9 +565,8 @@ upstream_init(getdns_upstream *upstream, /* For sharing a socket to this upstream with TCP */ upstream->fd = -1; upstream->tls_obj = NULL; - upstream->base_transport = (upstream_port(upstream) == GETDNS_PORT_NUM_TLS ? - GETDNS_TRANSPORT_TLS : - GETDNS_TRANSPORT_TCP); + upstream->dns_base_transport = (upstream_port(upstream) == GETDNS_PORT_NUM_TLS ? + GETDNS_BASE_TRANSPORT_TLS : GETDNS_BASE_TRANSPORT_TCP); upstream->tls_hs_state = GETDNS_HS_NONE; upstream->loop = NULL; (void) getdns_eventloop_event_init( @@ -672,10 +670,10 @@ set_os_defaults(struct getdns_context *context) token = parse + strcspn(parse, " \t\r\n"); *token = 0; - //getdns_port_type_t port_type = GETDNS_PORT_FIRST; - //for (; port_type < GETDNS_PORT_LAST; port_type++) { - // TODO[TLS]: Seeing strange crash in ub_create_ctx when using the loop here.... - //fprintf(stderr,"creating upstream %s\n", parse); + getdns_port_type_t port_type = GETDNS_PORT_FIRST; + for (; port_type < GETDNS_PORT_LAST; port_type++) { + // TODO[TLS]: Seeing strange crash in ub_create_ctx when using the loop here.... + fprintf(stderr,"creating upstream %s\n", parse); if ((s = getaddrinfo(parse, "53", /*getdns_port_str_array[port_type],*/ &hints, &result))) continue; @@ -690,7 +688,7 @@ set_os_defaults(struct getdns_context *context) upstream = &context->upstreams-> upstreams[context->upstreams->count++]; upstream_init(upstream, context->upstreams, result); - //} + } freeaddrinfo(result); } fclose(in); @@ -819,6 +817,7 @@ getdns_context_create_with_extended_memory_functions( result->dnssec_allowed_skew = 0; result->edns_maximum_udp_payload_size = -1; result->dns_transport = GETDNS_TRANSPORT_UDP_FIRST_AND_FALL_BACK_TO_TCP; + priv_set_base_dns_transports(result->dns_base_transports, result->dns_transport); result->limit_outstanding_queries = 0; result->has_ta = priv_getdns_parse_ta_file(NULL, NULL); result->return_dnssec_status = GETDNS_EXTENSION_FALSE; @@ -1142,31 +1141,38 @@ getdns_context_set_namespaces(struct getdns_context *context, return GETDNS_RETURN_GOOD; } /* getdns_context_set_namespaces */ -getdns_base_transport_t -priv_get_base_transport(getdns_transport_t transport, int level) { - if (!(level == 0 || level == 1)) return GETDNS_TRANSPORT_NONE; - switch (transport) { - case GETDNS_TRANSPORT_UDP_FIRST_AND_FALL_BACK_TO_TCP: - if (level == 0) return GETDNS_TRANSPORT_UDP; - if (level == 1) return GETDNS_TRANSPORT_TCP; - case GETDNS_TRANSPORT_UDP_ONLY: - if (level == 0) return GETDNS_TRANSPORT_UDP; - if (level == 1) return GETDNS_TRANSPORT_NONE; - case GETDNS_TRANSPORT_TCP_ONLY: - if (level == 0) return GETDNS_TRANSPORT_TCP_SINGLE; - if (level == 1) return GETDNS_TRANSPORT_NONE; - case GETDNS_TRANSPORT_TCP_ONLY_KEEP_CONNECTIONS_OPEN: - if (level == 0) return GETDNS_TRANSPORT_TCP; - if (level == 1) return GETDNS_TRANSPORT_NONE; - case GETDNS_TRANSPORT_TLS_ONLY_KEEP_CONNECTIONS_OPEN: - if (level == 0) return GETDNS_TRANSPORT_TLS; - if (level == 1) return GETDNS_TRANSPORT_NONE; - case GETDNS_TRANSPORT_TLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN: - if (level == 0) return GETDNS_TRANSPORT_TLS; - if (level == 1) return GETDNS_TRANSPORT_TCP; - default: - return GETDNS_TRANSPORT_NONE; - } +/* TODO[TLS]: Modify further when API changed.*/ +getdns_return_t +priv_set_base_dns_transports(getdns_base_transport_t *dns_base_transports, + getdns_transport_t value) +{ + for (int i = 0; i < GETDNS_BASE_TRANSPORT_MAX; i++) + dns_base_transports[i] = GETDNS_BASE_TRANSPORT_NONE; + switch (value) { + case GETDNS_TRANSPORT_UDP_FIRST_AND_FALL_BACK_TO_TCP: + dns_base_transports[0] = GETDNS_BASE_TRANSPORT_UDP; + dns_base_transports[1] = GETDNS_BASE_TRANSPORT_TCP_SINGLE; + break; + case GETDNS_TRANSPORT_UDP_ONLY: + dns_base_transports[0] = GETDNS_BASE_TRANSPORT_UDP; + break; + case GETDNS_TRANSPORT_TCP_ONLY: + dns_base_transports[0] = GETDNS_BASE_TRANSPORT_TCP_SINGLE; + break; + case GETDNS_TRANSPORT_TCP_ONLY_KEEP_CONNECTIONS_OPEN: + dns_base_transports[0] = GETDNS_BASE_TRANSPORT_TCP; + break; + case GETDNS_TRANSPORT_TLS_ONLY_KEEP_CONNECTIONS_OPEN: + dns_base_transports[0] = GETDNS_BASE_TRANSPORT_TLS; + break; + case GETDNS_TRANSPORT_TLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN: + dns_base_transports[0] = GETDNS_BASE_TRANSPORT_TLS; + dns_base_transports[1] = GETDNS_BASE_TRANSPORT_TCP; + break; + default: + return GETDNS_RETURN_CONTEXT_UPDATE_FAIL; + } + return GETDNS_RETURN_GOOD; } static getdns_return_t @@ -1188,9 +1194,8 @@ set_ub_dns_transport(struct getdns_context* context, set_ub_string_opt(context, "do-tcp:", "yes"); break; case GETDNS_TRANSPORT_TLS_ONLY_KEEP_CONNECTIONS_OPEN: - /* Hum. If used in recursive mode this will try TLS on port 53... - * So we need to fix or document that or delay setting it until - * resolution.*/ + /* Note: If TLS is used in recursive mode this will try TLS on port + * 53... So this is prohibited when preparing for resolution.*/ set_ub_string_opt(context, "ssl-upstream:", "yes"); /* Fall through*/ case GETDNS_TRANSPORT_TLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN: @@ -1213,17 +1218,22 @@ getdns_return_t getdns_context_set_dns_transport(struct getdns_context *context, getdns_transport_t value) { + /* TODO[TLS]: Modify further when API changed.*/ RETURN_IF_NULL(context, GETDNS_RETURN_INVALID_PARAMETER); /* Note that the call below does not have any effect in unbound after the - * ctx is finalised. So will not apply for recursive mode or stub + dnssec. + * ctx is finalised so for recursive mode or stub + dnssec only the first + * transport specified on the first query is used. * However the method returns success as otherwise the transport could not - * be reset for stub mode..... + * be reset for stub mode. * Also, not all transport options supported in libunbound yet */ if (set_ub_dns_transport(context, value) != GETDNS_RETURN_GOOD) { return GETDNS_RETURN_CONTEXT_UPDATE_FAIL; } if (value != context->dns_transport) { + /*TODO[TLS]: remove this line*/ context->dns_transport = value; + if (priv_set_base_dns_transports(context->dns_base_transports, value) != GETDNS_RETURN_GOOD) + return GETDNS_RETURN_CONTEXT_UPDATE_FAIL; dispatch_updated(context, GETDNS_CONTEXT_CODE_DNS_TRANSPORT); } @@ -1752,10 +1762,10 @@ ub_setup_stub(struct ub_ctx *ctx, getdns_context *context) getdns_upstreams *upstreams = context->upstreams; (void) ub_ctx_set_fwd(ctx, NULL); - /*TODO[TLS]: Order the upstreams so the TLS ones are first if doing TLS*/ + /*TODO[TLS]: Use only the subset of upstreams that match the first transport */ for (i = 0; i < upstreams->count; i++) { upstream = &upstreams->upstreams[i]; - upstream_ntop_buf(upstream, context->dns_transport, addr, 1024); + upstream_ntop_buf(upstream, addr, 1024); ub_ctx_set_fwd(ctx, addr); } @@ -1850,27 +1860,22 @@ getdns_context_prepare_for_resolution(struct getdns_context *context, } /* Transport can in theory be set per query in stub mode */ - /* TODO: move this transport logic to a separate functions*/ if (context->resolution_type == GETDNS_RESOLUTION_STUB) { - switch (context->dns_transport) { - case GETDNS_TRANSPORT_TLS_ONLY_KEEP_CONNECTIONS_OPEN: - case GETDNS_TRANSPORT_TLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN: - if (context->tls_ctx == NULL) { + /*TODO[TLS]: Check if TLS is in the list of transports.*/ + if (context->tls_ctx == NULL) { #ifdef HAVE_LIBTLS1_2 - /* Create client context, use TLS v1.2 only for now */ - context->tls_ctx = SSL_CTX_new(TLSv1_2_client_method()); + /* Create client context, use TLS v1.2 only for now */ + context->tls_ctx = SSL_CTX_new(TLSv1_2_client_method()); #endif - if(!context->tls_ctx && context->dns_transport == - GETDNS_TRANSPORT_TLS_ONLY_KEEP_CONNECTIONS_OPEN) { - return GETDNS_RETURN_BAD_CONTEXT; - } - } - break; - default: - break; + /* TODO[TLS]: Check if TLS is the only option in the list*/ + // if(!context->tls_ctx && context->dns_transport == + // GETDNS_TRANSPORT_TLS_ONLY_KEEP_CONNECTIONS_OPEN) { + // return GETDNS_RETURN_BAD_CONTEXT; + // } } } /* Block use of TLS ONLY in recursive mode as it won't work */ + /* TODO[TLS]: Check if TLS is the only option in the list*/ if (context->resolution_type == GETDNS_RESOLUTION_RECURSING && context->dns_transport == GETDNS_TRANSPORT_TLS_ONLY_KEEP_CONNECTIONS_OPEN) return GETDNS_RETURN_BAD_CONTEXT; diff --git a/src/context.h b/src/context.h index f2c1f698..75d266f7 100644 --- a/src/context.h +++ b/src/context.h @@ -75,14 +75,6 @@ struct filechg { struct stat *prevstat; }; -typedef enum getdns_base_transport { - GETDNS_TRANSPORT_NONE, - GETDNS_TRANSPORT_UDP, - GETDNS_TRANSPORT_TCP_SINGLE, - GETDNS_TRANSPORT_TCP, - GETDNS_TRANSPORT_TLS -} getdns_base_transport_t; - typedef enum getdns_port_type { GETDNS_PORT_FIRST = 0, GETDNS_PORT_TCP = 0, @@ -111,7 +103,7 @@ typedef struct getdns_upstream { /* For sharing a TCP socket to this upstream */ int fd; SSL* tls_obj; - getdns_base_transport_t base_transport; + getdns_base_transport_t dns_base_transport; getdns_tls_hs_state_t tls_hs_state; getdns_eventloop_event event; getdns_eventloop *loop; @@ -156,6 +148,7 @@ struct getdns_context { struct getdns_list *dnssec_trust_anchors; getdns_upstreams *upstreams; getdns_transport_t dns_transport; + getdns_base_transport_t dns_base_transports[GETDNS_BASE_TRANSPORT_MAX]; uint16_t limit_outstanding_queries; uint32_t dnssec_allowed_skew; @@ -251,6 +244,9 @@ int filechg_check(struct getdns_context *context, struct filechg *fchg); void priv_getdns_context_ub_read_cb(void *userarg); +getdns_return_t priv_set_base_dns_transports(getdns_base_transport_t *, + getdns_transport_t); + getdns_base_transport_t priv_get_base_transport(getdns_transport_t transport, int level); void priv_getdns_upstreams_dereference(getdns_upstreams *upstreams); diff --git a/src/request-internal.c b/src/request-internal.c index e9bb3b22..9b1fb81e 100644 --- a/src/request-internal.c +++ b/src/request-internal.c @@ -89,7 +89,9 @@ network_req_init(getdns_network_req *net_req, getdns_dns_req *owner, net_req->upstream = NULL; net_req->fd = -1; - net_req->transport = GETDNS_TRANSPORT_UDP_FIRST_AND_FALL_BACK_TO_TCP; + priv_set_base_dns_transports(net_req->dns_base_transports, + GETDNS_TRANSPORT_UDP_FIRST_AND_FALL_BACK_TO_TCP); + net_req->dns_base_transport = net_req->dns_base_transports; memset(&net_req->event, 0, sizeof(net_req->event)); memset(&net_req->tcp, 0, sizeof(net_req->tcp)); net_req->query_id = 0; diff --git a/src/stub.c b/src/stub.c index 775ff9ad..f7077e21 100755 --- a/src/stub.c +++ b/src/stub.c @@ -407,6 +407,7 @@ stub_udp_read_cb(void *userarg) return; /* Client cookie didn't match? */ close(netreq->fd); + /*TODO[TLS]: Switch this to use the transport fallback list*/ if (GLDNS_TC_WIRE(netreq->response) && dnsreq->context->dns_transport == GETDNS_TRANSPORT_UDP_FIRST_AND_FALL_BACK_TO_TCP) { @@ -478,51 +479,49 @@ stub_udp_write_cb(void *userarg) static int -transport_matches(struct getdns_upstream *upstream, getdns_base_transport_t transport) { - if (upstream->base_transport != transport) +transport_valid(struct getdns_upstream *upstream, getdns_base_transport_t transport) { + if (upstream->dns_base_transport != transport) return 0; - if (transport == GETDNS_TRANSPORT_TLS && + if (transport == GETDNS_BASE_TRANSPORT_TLS && upstream->tls_hs_state == GETDNS_HS_FAILED) return 0; return 1; } static getdns_upstream * -pick_upstream(getdns_dns_req *dnsreq, int level) +pick_upstream(getdns_network_req *netreq, getdns_base_transport_t transport) { getdns_upstream *upstream; + getdns_upstreams *upstreams = netreq->owner->context->upstreams; size_t i; - if (!dnsreq->upstreams->count) + if (!upstreams->count) return NULL; - getdns_base_transport_t transport = priv_get_base_transport( - dnsreq->context->dns_transport, level); + for (i = 0; i < upstreams->count; i++) + if (upstreams->upstreams[i].to_retry <= 0) + upstreams->upstreams[i].to_retry++; - for (i = 0; i < dnsreq->upstreams->count; i++) - if (dnsreq->upstreams->upstreams[i].to_retry <= 0) - dnsreq->upstreams->upstreams[i].to_retry++; - - i = dnsreq->upstreams->current; + i = upstreams->current; do { - if (dnsreq->upstreams->upstreams[i].to_retry > 0 && - transport_matches(&dnsreq->upstreams->upstreams[i], transport)) { - dnsreq->upstreams->current = i; - return &dnsreq->upstreams->upstreams[i]; + if (upstreams->upstreams[i].to_retry > 0 && + transport_valid(&upstreams->upstreams[i], transport)) { + upstreams->current = i; + return &upstreams->upstreams[i]; } - if (++i > dnsreq->upstreams->count) + if (++i > upstreams->count) i = 0; - } while (i != dnsreq->upstreams->current); + } while (i != upstreams->current); - upstream = dnsreq->upstreams->upstreams; - for (i = 1; i < dnsreq->upstreams->count; i++) - if (dnsreq->upstreams->upstreams[i].back_off < upstream->back_off && - transport_matches(&dnsreq->upstreams->upstreams[i], transport)) - upstream = &dnsreq->upstreams->upstreams[i]; + upstream = upstreams->upstreams; + for (i = 1; i < upstreams->count; i++) + if (upstreams->upstreams[i].back_off < upstream->back_off && + transport_valid(&upstreams->upstreams[i], transport)) + upstream = &upstreams->upstreams[i]; upstream->back_off++; upstream->to_retry = 1; - dnsreq->upstreams->current = upstream - dnsreq->upstreams->upstreams; + upstreams->current = upstream - upstreams->upstreams; return upstream; } @@ -698,58 +697,61 @@ do_tls_handshake(getdns_upstream *upstream) return 0; } -/* TODO[TLS]: Could think about fallback on read error aswell.*/ +/* TODO[TLS]: Make generic function for switching transport */ +/* TODO[TLS]: Should think about fallback on read error aswell.*/ static int -fallback_on_write(getdns_network_req *netreq) { +fallback_on_tls_write_error(getdns_network_req *netreq) { - /* This should really check if any request in the queue can fallback...*/ - if (netreq->transport != GETDNS_TRANSPORT_TLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN) - return STUB_TCP_ERROR; + fprintf(stderr,"[TLS]: method: fallback_on_tls_write_error\n"); + getdns_base_transport_t *next_transport = netreq->dns_base_transport; + if (*(++next_transport) != GETDNS_BASE_TRANSPORT_TCP) + /* TODO[TLS]: Fallback through upstreams....?*/ + return STUB_TCP_ERROR; - /* Deal with old upstream */ getdns_upstream *upstream = netreq->upstream; - upstream->write_queue = NULL; - upstream->write_queue_last = NULL; - upstream->event.write_cb = NULL; - GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); + /* Remove from queue, clearing event if we are the last*/ + 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); + } - /* Now set up new upstream */ - getdns_upstream *new_upstream = pick_upstream(netreq->owner, 1); + getdns_upstream *new_upstream = pick_upstream(netreq, *next_transport); + /* TODO[TLS]: Fallback through upstreams....?*/ if (!new_upstream) - return STUB_TCP_ERROR; - - /* get transport generically*/ - int fd = connect_to_upstream(new_upstream, GETDNS_TRANSPORT_TCP, netreq->owner->context); + return STUB_TCP_ERROR; + int fd = connect_to_upstream(new_upstream, *next_transport, netreq->owner->context); if (fd == -1) return STUB_TCP_ERROR; fprintf(stderr,"[TLS]: tcp_fallback to %d \n", new_upstream->fd); - getdns_network_req *next_req; - while (netreq != NULL) { - next_req = netreq->write_queue_tail; - if (netreq->transport == GETDNS_TRANSPORT_TLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN) { - netreq->upstream = new_upstream; - upstream_schedule_netreq(new_upstream, netreq); - /* TODO: Timout need to be adjusted and rescheduled on the new fd ....*/ - /* Note, setup timeout should be shorter than message timeout for - * messages with fallback or don't have time to re-try. */ - } - /*else.... leave request to timeout?*/ - netreq = next_req; - } + netreq->upstream = new_upstream; + upstream_schedule_netreq(new_upstream, netreq); + + /* TODO[TLS]: Timout need to be adjusted and rescheduled on the new fd ....*/ + /* Note, setup timeout should be shorter than message timeout for + * messages with fallback or don't have time to re-try. */ + // GETDNS_SCHEDULE_EVENT( + // dnsreq->loop, upstream->fd, dnsreq->context->timeout, + // getdns_eventloop_event_init(&netreq->event, netreq, NULL, + // ( dnsreq->loop != upstream->loop /* Synchronous lookup? */ + // ? netreq_upstream_write_cb : NULL), stub_timeout_cb)); return STUB_TCP_AGAIN; } static int -setup_tls(getdns_upstream* upstream) +check_tls(getdns_upstream* upstream) { - int ret; /* Already have a connection*/ if (upstream->tls_hs_state == GETDNS_HS_DONE && (upstream->tls_obj != NULL) && (upstream->fd != -1)) return 0; + /* This upstream can't be used, so let the fallback code take care of things */ + if (upstream->tls_hs_state == GETDNS_HS_FAILED) + return STUB_TLS_SETUP_ERROR; + /* Lets make sure the connection is up before we try a handshake*/ int error = 0; socklen_t len = (socklen_t)sizeof(error); @@ -774,17 +776,7 @@ setup_tls(getdns_upstream* upstream) return STUB_TLS_SETUP_ERROR; } - ret = do_tls_handshake(upstream); - switch (ret) { - case STUB_TCP_AGAIN: - return ret; - case STUB_TCP_ERROR: - fprintf(stderr,"[TLS]: W: Handshake has failed %d\n", upstream->tls_hs_state); - return STUB_TLS_SETUP_ERROR; - default: - fprintf(stderr,"[TLS]: W:after handshake %d, %s\n", upstream->tls_hs_state, upstream->tls_obj== NULL? "NULL":"Not NULL" ); - return 0; - } + return do_tls_handshake(upstream); } static int @@ -795,7 +787,7 @@ stub_tls_read(getdns_upstream *upstream, getdns_tcp_state *tcp, struct mem_funcs size_t buf_size; SSL* tls_obj = upstream->tls_obj; - int q = setup_tls(upstream); + int q = check_tls(upstream); if (q != 0) return q; @@ -883,7 +875,7 @@ upstream_read_cb(void *userarg) fprintf(stderr,"[TLS]: upstream_read_cb on %d\n", upstream->fd); - if (upstream->tls_obj) + if (upstream->dns_base_transport == GETDNS_BASE_TRANSPORT_TLS) q = stub_tls_read(upstream, &upstream->tcp, &upstream->upstreams->mf); else @@ -1114,7 +1106,7 @@ stub_tls_write(getdns_upstream *upstream, getdns_tcp_state *tcp, getdns_network_ intptr_t query_id_intptr; SSL* tls_obj = upstream->tls_obj; - int q = setup_tls(upstream); + int q = check_tls(upstream); if (q != 0) return q; @@ -1169,7 +1161,7 @@ upstream_write_cb(void *userarg) fprintf(stderr,"[TLS]: method: upstream_write_cb %d\n", upstream->fd); - if (upstream->tls_obj) + if (upstream->dns_base_transport == GETDNS_BASE_TRANSPORT_TLS) q = stub_tls_write(upstream, &upstream->tcp, netreq); else q = stub_tcp_write(upstream->fd, &upstream->tcp, netreq); @@ -1185,13 +1177,14 @@ upstream_write_cb(void *userarg) case STUB_TLS_SETUP_ERROR: /* Could not complete the TLS set up. Need to fallback on this upstream * if possible.*/ - if (fallback_on_write(netreq) == STUB_TCP_ERROR) + if (fallback_on_tls_write_error(netreq) == STUB_TCP_ERROR) + //TODO[TLS]: Need a different error case here for msg_erred? stub_erred(netreq); return; default: netreq->query_id = (uint16_t) q; - fprintf(stderr,"[TLS]: method: upstream_write_cb, successfull write %d\n", upstream->fd); + fprintf(stderr,"[TLS]: method: upstream_write_cb, successfull write %d\n", upstream->fd); /* Unqueue the netreq from the write_queue */ if (!(upstream->write_queue = netreq->write_queue_tail)) { @@ -1231,6 +1224,7 @@ upstream_write_cb(void *userarg) static void netreq_upstream_write_cb(void *userarg) { + fprintf(stderr,"[TLS]: method: SYNC netreq_upstream_write_cb \n"); upstream_write_cb(((getdns_network_req *)userarg)->upstream); } @@ -1267,8 +1261,8 @@ tcp_connect(getdns_upstream *upstream, getdns_base_transport_t transport) getdns_sock_nonblock(fd); #ifdef USE_TCP_FASTOPEN /* Leave the connect to the later call to sendto() if using TCP*/ - if (transport == GETDNS_TRANSPORT_TCP || - transport == GETDNS_TRANSPORT_TCP_SINGLE) + if (transport == GETDNS_BASE_TRANSPORT_TCP || + transport == GETDNS_BASE_TRANSPORT_TCP_SINGLE) return fd; #endif if (connect(fd, (struct sockaddr *)&upstream->addr, @@ -1286,31 +1280,33 @@ connect_to_upstream(getdns_upstream *upstream, getdns_base_transport_t transport getdns_context *context) { - if ((transport == GETDNS_TRANSPORT_TCP || - transport == GETDNS_TRANSPORT_TLS) + if ((transport == GETDNS_BASE_TRANSPORT_TCP || + transport == GETDNS_BASE_TRANSPORT_TLS) && upstream->fd != -1) { fprintf(stderr,"[TLS]: method: tcp_connect using existing fd %d\n", upstream->fd); return upstream->fd; } - int fd; + int fd = -1; switch(transport) { - case GETDNS_TRANSPORT_UDP: + case GETDNS_BASE_TRANSPORT_UDP: if ((fd = socket( upstream->addr.ss_family, SOCK_DGRAM, IPPROTO_UDP)) == -1) return -1; getdns_sock_nonblock(fd); return fd; - case GETDNS_TRANSPORT_TCP_SINGLE: - case GETDNS_TRANSPORT_TCP: + case GETDNS_BASE_TRANSPORT_TCP_SINGLE: + case GETDNS_BASE_TRANSPORT_TCP: fd = tcp_connect(upstream, transport); break; - case GETDNS_TRANSPORT_TLS: + case GETDNS_BASE_TRANSPORT_TLS: fd = tcp_connect(upstream, transport); - if (fd == -1 || - (upstream->tls_obj = create_tls_object(context, fd)) == NULL ) { + if (fd == -1) return -1; + upstream->tls_obj = create_tls_object(context, fd); + if (upstream->tls_obj == NULL) { + fprintf(stderr,"[TLS]: could not create tls object\n"); close(fd); return -1; } @@ -1331,50 +1327,47 @@ connect_to_upstream(getdns_upstream *upstream, getdns_base_transport_t transport getdns_return_t priv_getdns_submit_stub_request(getdns_network_req *netreq) { - getdns_dns_req *dnsreq = netreq->owner; + int fd = -1; + int i; + getdns_dns_req *dnsreq = netreq->owner; + getdns_upstream *upstream = NULL; - /* TODO[TLS - 1]: This will become a double while loop trying all the upstreams on all the - * transports for a connection since we need a fd to schedule on, using previous known capabilities - * All other set up is done async*/ - /* Work out the primary and fallback transport options */ - getdns_base_transport_t transport = priv_get_base_transport( - dnsreq->context->dns_transport,0); - getdns_base_transport_t fb_transport = priv_get_base_transport( - dnsreq->context->dns_transport,1); - getdns_upstream *upstream = pick_upstream(dnsreq, 0); - if (!upstream) - return GETDNS_RETURN_GENERIC_ERROR; - int fd = connect_to_upstream(upstream, transport, dnsreq->context); - if (fd == -1) { - if (fb_transport == GETDNS_TRANSPORT_NONE) - return GETDNS_RETURN_GENERIC_ERROR; - upstream = pick_upstream(dnsreq, 1); - if ((fd = connect_to_upstream(upstream, fb_transport, dnsreq->context)) == -1) - return GETDNS_RETURN_GENERIC_ERROR; + /* This loop does a best effort to get a initial fd falling back through + * transport (then upstream?). All other set up is done async*/ + for (i = 0; i < GETDNS_BASE_TRANSPORT_MAX; i++) + netreq->dns_base_transports[i] = dnsreq->context->dns_base_transports[i]; + for (i = 0; i < GETDNS_BASE_TRANSPORT_MAX && + netreq->dns_base_transports[i] != GETDNS_BASE_TRANSPORT_NONE; i++) { + /*TODO[TLS]: Loop over upstreams, but don't loop more than once*/ + upstream = pick_upstream(netreq, netreq->dns_base_transports[i]); + if (!upstream) { + continue; + } + fd = connect_to_upstream(upstream, netreq->dns_base_transports[i], + dnsreq->context); + if (fd != -1) + break; } + if (fd == -1) + return GETDNS_RETURN_GENERIC_ERROR; netreq->upstream = upstream; - netreq->transport = dnsreq->context->dns_transport; + netreq->dns_base_transport = &(netreq->dns_base_transports[i]); - switch(transport) { - case GETDNS_TRANSPORT_UDP: - case GETDNS_TRANSPORT_TCP_SINGLE: + switch(*netreq->dns_base_transport) { + case GETDNS_BASE_TRANSPORT_UDP: + case GETDNS_BASE_TRANSPORT_TCP_SINGLE: netreq->fd = fd; GETDNS_SCHEDULE_EVENT( dnsreq->loop, netreq->fd, dnsreq->context->timeout, getdns_eventloop_event_init(&netreq->event, netreq, - NULL, (transport == GETDNS_TRANSPORT_UDP ? stub_udp_write_cb: - stub_tcp_write_cb), stub_timeout_cb)); + NULL, (*netreq->dns_base_transport == GETDNS_BASE_TRANSPORT_UDP ? + stub_udp_write_cb: stub_tcp_write_cb), stub_timeout_cb)); return GETDNS_RETURN_GOOD; - case GETDNS_TRANSPORT_TCP: - case GETDNS_TRANSPORT_TLS: + case GETDNS_BASE_TRANSPORT_TCP: + case GETDNS_BASE_TRANSPORT_TLS: - /* In coming comments, "global" means "context wide" */ - if (upstream->fd == -1) { - upstream->loop = dnsreq->context->extension; - upstream->fd = fd; - } upstream_schedule_netreq(upstream, netreq); /* TODO[TLS]: Timeout handling for async calls must change.... * Maybe even change scheduling for sync calls here too*/ diff --git a/src/types-internal.h b/src/types-internal.h index 7ca48d7b..249e94c7 100644 --- a/src/types-internal.h +++ b/src/types-internal.h @@ -164,6 +164,16 @@ typedef struct getdns_tcp_state { } getdns_tcp_state; +typedef enum getdns_base_transport { + GETDNS_BASE_TRANSPORT_NONE, + GETDNS_BASE_TRANSPORT_UDP, + GETDNS_BASE_TRANSPORT_TCP_SINGLE, /* To be removed? */ + GETDNS_BASE_TRANSPORT_TCP, + GETDNS_BASE_TRANSPORT_TLS, + GETDNS_BASE_TRANSPORT_STARTTLS, /* Not yet implemented*/ + GETDNS_BASE_TRANSPORT_MAX +} getdns_base_transport_t; + /** * Request data **/ @@ -191,7 +201,8 @@ typedef struct getdns_network_req /* For stub resolving */ struct getdns_upstream *upstream; int fd; - getdns_transport_t transport; + getdns_base_transport_t dns_base_transports[GETDNS_BASE_TRANSPORT_MAX]; + getdns_base_transport_t *dns_base_transport; getdns_eventloop_event event; getdns_tcp_state tcp; uint16_t query_id; From 4e6e66fc776967ab6e03cc2c830067e5ef33c932 Mon Sep 17 00:00:00 2001 From: Sara Dickinson Date: Mon, 27 Apr 2015 15:32:57 +0100 Subject: [PATCH 03/12] Get sync messages working with new async code. --- src/context.c | 14 ++--- src/stub.c | 168 ++++++++++++++++++++++++++++++++++---------------- 2 files changed, 122 insertions(+), 60 deletions(-) diff --git a/src/context.c b/src/context.c index fb3140d5..9ea8c6c1 100644 --- a/src/context.c +++ b/src/context.c @@ -68,11 +68,11 @@ getdns_port_array[GETDNS_PORT_LAST] = { GETDNS_PORT_NUM_TLS }; -// char* -// getdns_port_str_array[] = { -// "53", -// "1021" -// }; +char* +getdns_port_str_array[] = { + GETDNS_PORT_STR_TCP, + GETDNS_PORT_STR_TLS +}; /* Private functions */ getdns_return_t create_default_namespaces(struct getdns_context *context); @@ -672,9 +672,7 @@ set_os_defaults(struct getdns_context *context) getdns_port_type_t port_type = GETDNS_PORT_FIRST; for (; port_type < GETDNS_PORT_LAST; port_type++) { - // TODO[TLS]: Seeing strange crash in ub_create_ctx when using the loop here.... - fprintf(stderr,"creating upstream %s\n", parse); - if ((s = getaddrinfo(parse, "53", /*getdns_port_str_array[port_type],*/ &hints, &result))) + if ((s = getaddrinfo(parse, getdns_port_str_array[port_type], &hints, &result))) continue; /* No lookups, so maximal 1 result */ diff --git a/src/stub.c b/src/stub.c index f7077e21..464f5790 100755 --- a/src/stub.c +++ b/src/stub.c @@ -45,6 +45,9 @@ #define STUB_TCP_AGAIN -2 #define STUB_TCP_ERROR -1 +/*TODO[TLS]: REMOVE!!!!!*/ +#define TLS_DEBUG 1 + static time_t secret_rollover_time = 0; static uint32_t secret = 0; static uint32_t prev_secret = 0; @@ -57,6 +60,8 @@ static int connect_to_upstream(getdns_upstream *upstream, getdns_context *context); static void upstream_schedule_netreq(getdns_upstream *upstream, getdns_network_req *netreq); +static void netreq_upstream_read_cb(void *userarg); +static void netreq_upstream_write_cb(void *userarg); static void rollover_secret() @@ -318,7 +323,9 @@ static void upstream_erred(getdns_upstream *upstream) { getdns_network_req *netreq; - fprintf(stderr,"[TLS]: upstream_erred\n"); +#ifdef TLS_DEBUG + fprintf(stderr,"[TLS]: ERROR(upstream_erred)\n"); +#endif while ((netreq = upstream->write_queue)) { stub_cleanup(netreq); netreq->state = NET_REQ_FINISHED; @@ -351,7 +358,9 @@ priv_getdns_cancel_stub_request(getdns_network_req *netreq) static void stub_erred(getdns_network_req *netreq) { - fprintf(stderr,"[TLS]: stub_erred\n"); +#ifdef TLS_DEBUG + fprintf(stderr,"[TLS]: ERROR(stub_erred)\n"); +#endif stub_next_upstream(netreq); stub_cleanup(netreq); /* TODO[TLS]: When we get an error (which is probably a timeout) and are @@ -364,7 +373,9 @@ stub_erred(getdns_network_req *netreq) static void stub_timeout_cb(void *userarg) { - fprintf(stderr,"[TLS]: stub_timeout_cb\n"); +#ifdef TLS_DEBUG + fprintf(stderr,"[TLS]: TIMEOUT(stub_timeout_cb)\n"); +#endif getdns_network_req *netreq = (getdns_network_req *)userarg; stub_next_upstream(netreq); @@ -652,8 +663,9 @@ create_tls_object(getdns_context *context, int fd) static int do_tls_handshake(getdns_upstream *upstream) { - - fprintf(stderr,"[TLS]: do_tls_handshake\n"); +#ifdef TLS_DEBUG + fprintf(stderr,"[TLS]: TLS(do_tls_handshake)\n"); +#endif int r; int want; @@ -684,7 +696,6 @@ do_tls_handshake(getdns_upstream *upstream) SSL_free(upstream->tls_obj); upstream->tls_obj = NULL; upstream->tls_hs_state = GETDNS_HS_FAILED; - upstream->fd = -1; return STUB_TLS_SETUP_ERROR; } } @@ -699,44 +710,83 @@ do_tls_handshake(getdns_upstream *upstream) /* TODO[TLS]: Make generic function for switching transport */ /* TODO[TLS]: Should think about fallback on read error aswell.*/ -static int -fallback_on_tls_write_error(getdns_network_req *netreq) { - - fprintf(stderr,"[TLS]: method: fallback_on_tls_write_error\n"); +static getdns_upstream* +pick_and_connect_to_fallback_upstream(getdns_network_req *netreq) +{ +#ifdef TLS_DEBUG + fprintf(stderr,"[TLS]: FALLBACK(pick_and_connect_to_fallback_upstream)\n"); +#endif getdns_base_transport_t *next_transport = netreq->dns_base_transport; if (*(++next_transport) != GETDNS_BASE_TRANSPORT_TCP) /* TODO[TLS]: Fallback through upstreams....?*/ - return STUB_TCP_ERROR; + return NULL; + getdns_upstream *new_upstream = pick_upstream(netreq, *next_transport); + /* TODO[TLS]: Fallback through upstreams....?*/ + if (!new_upstream) + return NULL; + int fd = connect_to_upstream(new_upstream, *next_transport, netreq->owner->context); + if (fd == -1) + return NULL; +#ifdef TLS_DEBUG + fprintf(stderr,"[TLS]: FALLBACK(pick_and_connect_to_fallback_upstream): now using fd %d \n", new_upstream->fd); +#endif + return new_upstream; +} - getdns_upstream *upstream = netreq->upstream; - /* Remove from queue, clearing event if we are the last*/ + +static int +move_netreq(getdns_network_req *netreq, getdns_upstream *upstream, getdns_upstream *new_upstream) +{ + /* Remove from queue, clearing event and fd if we are the last*/ +#ifdef TLS_DEBUG + fprintf(stderr,"[TLS]: FALLBACK(move_netreq)\n"); +#endif 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); + close(upstream->fd); + upstream->fd = -1; } + netreq->write_queue_tail = NULL; - getdns_upstream *new_upstream = pick_upstream(netreq, *next_transport); - /* TODO[TLS]: Fallback through upstreams....?*/ - if (!new_upstream) - return STUB_TCP_ERROR; - int fd = connect_to_upstream(new_upstream, *next_transport, netreq->owner->context); - if (fd == -1) - return STUB_TCP_ERROR; - - fprintf(stderr,"[TLS]: tcp_fallback to %d \n", new_upstream->fd); + /* Schedule with the new upstream */ netreq->upstream = new_upstream; upstream_schedule_netreq(new_upstream, netreq); /* TODO[TLS]: Timout need to be adjusted and rescheduled on the new fd ....*/ /* Note, setup timeout should be shorter than message timeout for * messages with fallback or don't have time to re-try. */ - // GETDNS_SCHEDULE_EVENT( - // dnsreq->loop, upstream->fd, dnsreq->context->timeout, - // getdns_eventloop_event_init(&netreq->event, netreq, NULL, - // ( dnsreq->loop != upstream->loop /* Synchronous lookup? */ - // ? netreq_upstream_write_cb : NULL), stub_timeout_cb)); + /* For sync messages we must re-schedule the events here.*/ + if (netreq->owner->loop != upstream->loop) { + /* Create an event for the new upstream*/ + GETDNS_CLEAR_EVENT(netreq->owner->loop, &netreq->event); + GETDNS_SCHEDULE_EVENT( + netreq->owner->loop, new_upstream->fd, netreq->owner->context->timeout, + getdns_eventloop_event_init(&netreq->event, netreq, + ( new_upstream->netreq_by_query_id.count ? + netreq_upstream_read_cb : NULL ), + ( new_upstream->write_queue ? + netreq_upstream_write_cb : NULL), + stub_timeout_cb)); + + /* Now one for the old upstream. Must schedule this last to make sure + * it is called back first....?*/ + if (upstream->write_queue) { + GETDNS_CLEAR_EVENT(netreq->owner->loop, &upstream->write_queue->event); + GETDNS_SCHEDULE_EVENT( + upstream->write_queue->owner->loop, upstream->fd, + upstream->write_queue->owner->context->timeout, + getdns_eventloop_event_init(&upstream->write_queue->event, + upstream->write_queue, + ( upstream->netreq_by_query_id.count ? + netreq_upstream_read_cb : NULL ), + ( upstream->write_queue ? + netreq_upstream_write_cb : NULL), + stub_timeout_cb)); + } + } return STUB_TCP_AGAIN; } @@ -757,22 +807,17 @@ check_tls(getdns_upstream* upstream) socklen_t len = (socklen_t)sizeof(error); /* TODO: This doesn't handle the case where the far end doesn't do a reset * as is the case with e.g. 8.8.8.8. For that case the timeout kicks in - * and the user callback fails the message without the chance to fallback... - * Note that acutally the TCP code doesn't check the connection state before - * doing a first write either.... - * Perhaps we should have a write_timeout_cb on the write and then schedule - * the stub_timeout_cb for matching the response??? */ + * and the user callback fails the message without the chance to fallback.*/ getsockopt(upstream->fd, SOL_SOCKET, SO_ERROR, (void*)&error, &len); - if (error == EINPROGRESS || error == EWOULDBLOCK) { - fprintf(stderr,"[TLS]: blocking.......\n"); + if (error == EINPROGRESS || error == EWOULDBLOCK) return STUB_TCP_AGAIN; /* try again */ - } else if (error != 0) { - fprintf(stderr,"[TLS]: died gettting connection\n"); +#ifdef TLS_DEBUG + fprintf(stderr,"[TLS]: TLS(check_tls): died gettting connection\n"); +#endif SSL_free(upstream->tls_obj); upstream->tls_obj = NULL; upstream->tls_hs_state = GETDNS_HS_FAILED; - upstream->fd = -1; return STUB_TLS_SETUP_ERROR; } @@ -860,8 +905,6 @@ stub_tls_read(getdns_upstream *upstream, getdns_tcp_state *tcp, struct mem_funcs return GLDNS_ID_WIRE(tcp->read_buf); } -static void netreq_upstream_read_cb(void *userarg); -static void netreq_upstream_write_cb(void *userarg); static void upstream_read_cb(void *userarg) { @@ -872,8 +915,10 @@ upstream_read_cb(void *userarg) uint16_t query_id; intptr_t query_id_intptr; - - fprintf(stderr,"[TLS]: upstream_read_cb on %d\n", upstream->fd); +#ifdef TLS_DEBUG + fprintf(stderr,"[TLS]: **********CALLBACK***********\n"); + fprintf(stderr,"[TLS]: READ(upstream_read_cb): on %d\n", upstream->fd); +#endif if (upstream->dns_base_transport == GETDNS_BASE_TRANSPORT_TLS) q = stub_tls_read(upstream, &upstream->tcp, @@ -1154,12 +1199,15 @@ static void upstream_write_cb(void *userarg) { getdns_upstream *upstream = (getdns_upstream *)userarg; + getdns_upstream *new_upstream; getdns_network_req *netreq = upstream->write_queue; getdns_dns_req *dnsreq = netreq->owner; int q; - - fprintf(stderr,"[TLS]: method: upstream_write_cb %d\n", upstream->fd); +#ifdef TLS_DEBUG + fprintf(stderr,"[TLS]: **********CALLBACK***********\n"); + fprintf(stderr,"[TLS]: WRITE(upstream_write_cb): upstream fd %d, SEND netreq %p \n", upstream->fd, netreq); +#endif if (upstream->dns_base_transport == GETDNS_BASE_TRANSPORT_TLS) q = stub_tls_write(upstream, &upstream->tcp, netreq); @@ -1177,14 +1225,18 @@ upstream_write_cb(void *userarg) case STUB_TLS_SETUP_ERROR: /* Could not complete the TLS set up. Need to fallback on this upstream * if possible.*/ - if (fallback_on_tls_write_error(netreq) == STUB_TCP_ERROR) + new_upstream = pick_and_connect_to_fallback_upstream(netreq); + if (!new_upstream) + //TODO[TLS]: Need a different error case here for msg_erred? + stub_erred(netreq); + if (move_netreq(netreq, upstream, new_upstream) == STUB_TCP_ERROR) //TODO[TLS]: Need a different error case here for msg_erred? stub_erred(netreq); return; default: netreq->query_id = (uint16_t) q; - fprintf(stderr,"[TLS]: method: upstream_write_cb, successfull write %d\n", upstream->fd); + fprintf(stderr, "[TLS]: WRITE(upstream_write_cb): successfull write on fd %d\n", upstream->fd); /* Unqueue the netreq from the write_queue */ if (!(upstream->write_queue = netreq->write_queue_tail)) { @@ -1224,7 +1276,6 @@ upstream_write_cb(void *userarg) static void netreq_upstream_write_cb(void *userarg) { - fprintf(stderr,"[TLS]: method: SYNC netreq_upstream_write_cb \n"); upstream_write_cb(((getdns_network_req *)userarg)->upstream); } @@ -1235,7 +1286,9 @@ upstream_schedule_netreq(getdns_upstream *upstream, getdns_network_req *netreq) assert(upstream->fd >= 0); assert(upstream->loop); - fprintf(stderr,"[TLS]: method: upstream_schedule_netreq %d\n", upstream->fd); +#ifdef TLS_DEBUG + fprintf(stderr,"[TLS]: SCHEDULE(upstream_schedule_netreq): fd %d\n", upstream->fd); +#endif /* Append netreq to write_queue */ if (!upstream->write_queue) { @@ -1280,10 +1333,19 @@ connect_to_upstream(getdns_upstream *upstream, getdns_base_transport_t transport getdns_context *context) { - if ((transport == GETDNS_BASE_TRANSPORT_TCP || - transport == GETDNS_BASE_TRANSPORT_TLS) - && upstream->fd != -1) { - fprintf(stderr,"[TLS]: method: tcp_connect using existing fd %d\n", upstream->fd); + if (transport == GETDNS_BASE_TRANSPORT_TCP && upstream->fd != -1) { +#ifdef TLS_DEBUG + fprintf(stderr,"[TLS]: CONNECT(connect_to_upstream): tcp_connect using existing TCP fd %d\n", upstream->fd); +#endif + return upstream->fd; + } + + if (transport == GETDNS_BASE_TRANSPORT_TLS && + !(upstream->tls_hs_state == GETDNS_HS_FAILED + || upstream->tls_hs_state == GETDNS_HS_NONE)) { +#ifdef TLS_DEBUG + fprintf(stderr,"[TLS]: CONNECT(connect_to_upstream): tcp_connect using existing TLS fd %d\n", upstream->fd); +#endif return upstream->fd; } @@ -1320,7 +1382,9 @@ connect_to_upstream(getdns_upstream *upstream, getdns_base_transport_t transport upstream->loop = context->extension; upstream->fd = fd; } - fprintf(stderr,"[TLS]: method: tcp_connect created new connection %d\n", fd); +#ifdef TLS_DEBUG + fprintf(stderr,"[TLS]: CONNECT(connect_to_upstream): created new connection %d\n", fd); +#endif return fd; } From b533bc59c5690b0a2a542f151145ce23a98734c8 Mon Sep 17 00:00:00 2001 From: Sara Dickinson Date: Mon, 27 Apr 2015 16:37:16 +0100 Subject: [PATCH 04/12] Fix bug when fallback not available --- src/context.c | 9 +++++++-- src/stub.c | 5 +++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/context.c b/src/context.c index 9ea8c6c1..0169d4d1 100644 --- a/src/context.c +++ b/src/context.c @@ -1228,7 +1228,7 @@ getdns_context_set_dns_transport(struct getdns_context *context, return GETDNS_RETURN_CONTEXT_UPDATE_FAIL; } if (value != context->dns_transport) { - /*TODO[TLS]: remove this line*/ + /*TODO[TLS]: remove this line when API updated*/ context->dns_transport = value; if (priv_set_base_dns_transports(context->dns_base_transports, value) != GETDNS_RETURN_GOOD) return GETDNS_RETURN_CONTEXT_UPDATE_FAIL; @@ -1760,9 +1760,14 @@ ub_setup_stub(struct ub_ctx *ctx, getdns_context *context) getdns_upstreams *upstreams = context->upstreams; (void) ub_ctx_set_fwd(ctx, NULL); - /*TODO[TLS]: Use only the subset of upstreams that match the first transport */ for (i = 0; i < upstreams->count; i++) { upstream = &upstreams->upstreams[i]; + /*[TLS]: Use only the subset of upstreams that match the first transport */ + if (context->dns_transport == GETDNS_TRANSPORT_TLS_ONLY_KEEP_CONNECTIONS_OPEN) { + if (upstream_port(upstream) != GETDNS_PORT_NUM_TLS) + continue; + } else if (upstream_port(upstream) != GETDNS_PORT_NUM_TCP) + continue; upstream_ntop_buf(upstream, addr, 1024); ub_ctx_set_fwd(ctx, addr); } diff --git a/src/stub.c b/src/stub.c index 464f5790..9945e80a 100755 --- a/src/stub.c +++ b/src/stub.c @@ -709,7 +709,6 @@ do_tls_handshake(getdns_upstream *upstream) } /* TODO[TLS]: Make generic function for switching transport */ -/* TODO[TLS]: Should think about fallback on read error aswell.*/ static getdns_upstream* pick_and_connect_to_fallback_upstream(getdns_network_req *netreq) { @@ -1226,9 +1225,11 @@ upstream_write_cb(void *userarg) /* Could not complete the TLS set up. Need to fallback on this upstream * if possible.*/ new_upstream = pick_and_connect_to_fallback_upstream(netreq); - if (!new_upstream) + if (!new_upstream) { //TODO[TLS]: Need a different error case here for msg_erred? stub_erred(netreq); + return; + } if (move_netreq(netreq, upstream, new_upstream) == STUB_TCP_ERROR) //TODO[TLS]: Need a different error case here for msg_erred? stub_erred(netreq); From 79b3412fbf560923dc34c799cabed1056a951f5b Mon Sep 17 00:00:00 2001 From: Sara Dickinson Date: Wed, 29 Apr 2015 19:20:25 +0100 Subject: [PATCH 05/12] Add another transport option as proof of concept for STARTTLS. --- src/const-info.c | 1 + src/context.c | 72 ++++-- src/context.h | 14 +- src/getdns/getdns.h.in | 4 +- src/request-internal.c | 4 +- src/stub.c | 543 +++++++++++++++++++++++++++------------- src/test/getdns_query.c | 5 + src/types-internal.h | 5 +- 8 files changed, 434 insertions(+), 214 deletions(-) diff --git a/src/const-info.c b/src/const-info.c index 5c5a3d99..026d5da8 100755 --- a/src/const-info.c +++ b/src/const-info.c @@ -41,6 +41,7 @@ static struct const_info consts_info[] = { { 543, "GETDNS_TRANSPORT_TCP_ONLY_KEEP_CONNECTIONS_OPEN", GETDNS_TRANSPORT_TCP_ONLY_KEEP_CONNECTIONS_OPEN_TEXT }, { 544, "GETDNS_TRANSPORT_TLS_ONLY_KEEP_CONNECTIONS_OPEN", GETDNS_TRANSPORT_TLS_ONLY_KEEP_CONNECTIONS_OPEN_TEXT }, { 545, "GETDNS_TRANSPORT_TLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN", GETDNS_TRANSPORT_TLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN_TEXT }, + { 546, "GETDNS_TRANSPORT_STARTTLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN", GETDNS_TRANSPORT_STARTTLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN_TEXT }, { 550, "GETDNS_APPEND_NAME_ALWAYS", GETDNS_APPEND_NAME_ALWAYS_TEXT }, { 551, "GETDNS_APPEND_NAME_ONLY_TO_SINGLE_LABEL_AFTER_FAILURE", GETDNS_APPEND_NAME_ONLY_TO_SINGLE_LABEL_AFTER_FAILURE_TEXT }, { 552, "GETDNS_APPEND_NAME_ONLY_TO_MULTIPLE_LABEL_NAME_AFTER_FAILURE", GETDNS_APPEND_NAME_ONLY_TO_MULTIPLE_LABEL_NAME_AFTER_FAILURE_TEXT }, diff --git a/src/context.c b/src/context.c index 0169d4d1..ab075d2d 100644 --- a/src/context.c +++ b/src/context.c @@ -53,6 +53,13 @@ #include "stub.h" #include "list.h" +#define GETDNS_PORT_ZERO 0 +#define GETDNS_PORT_TCP 53 +#define GETDNS_PORT_TLS 1021 +#define GETDNS_STR_PORT_ZERO "0" +#define GETDNS_STR_PORT_TCP "53" +#define GETDNS_STR_PORT_TLS "1021" + void *plain_mem_funcs_user_arg = MF_PLAIN; typedef struct host_name_addrs { @@ -63,15 +70,23 @@ typedef struct host_name_addrs { } host_name_addrs; static in_port_t -getdns_port_array[GETDNS_PORT_LAST] = { - GETDNS_PORT_NUM_TCP, - GETDNS_PORT_NUM_TLS +getdns_port_array[GETDNS_BASE_TRANSPORT_MAX] = { + GETDNS_PORT_ZERO, + GETDNS_PORT_ZERO, + GETDNS_PORT_ZERO, + GETDNS_PORT_TCP, + GETDNS_PORT_TLS, + GETDNS_PORT_TCP }; char* getdns_port_str_array[] = { - GETDNS_PORT_STR_TCP, - GETDNS_PORT_STR_TLS + GETDNS_STR_PORT_ZERO, + GETDNS_STR_PORT_ZERO, + GETDNS_STR_PORT_ZERO, + GETDNS_STR_PORT_TCP, + GETDNS_STR_PORT_TLS, + GETDNS_STR_PORT_TCP }; /* Private functions */ @@ -252,7 +267,7 @@ sockaddr_dict(getdns_context *context, struct sockaddr *sa) break; port = ntohs(((struct sockaddr_in *)sa)->sin_port); - if (port != 0 && port != GETDNS_PORT_NUM_TCP && + if (port != 0 && port != GETDNS_PORT_TCP && getdns_dict_set_int(address, "port", (uint32_t)port)) break; @@ -268,7 +283,7 @@ sockaddr_dict(getdns_context *context, struct sockaddr *sa) break; port = ntohs(((struct sockaddr_in6 *)sa)->sin6_port); - if (port != 0 && port != GETDNS_PORT_NUM_TCP && + if (port != 0 && port != GETDNS_PORT_TCP && getdns_dict_set_int(address, "port", (uint32_t)port)) break; @@ -538,7 +553,7 @@ upstream_ntop_buf(getdns_upstream *upstream, char *buf, size_t len) if (upstream_scope_id(upstream)) (void) snprintf(buf + strlen(buf), len - strlen(buf), "%%%d", (int)*upstream_scope_id(upstream)); - else if (upstream_port(upstream) != GETDNS_PORT_NUM_TCP && upstream_port(upstream) != 0) + else if (upstream_port(upstream) != GETDNS_PORT_TCP && upstream_port(upstream) != 0) (void) snprintf(buf + strlen(buf), len - strlen(buf), "@%d", (int)upstream_port(upstream)); } @@ -565,8 +580,8 @@ upstream_init(getdns_upstream *upstream, /* For sharing a socket to this upstream with TCP */ upstream->fd = -1; upstream->tls_obj = NULL; - upstream->dns_base_transport = (upstream_port(upstream) == GETDNS_PORT_NUM_TLS ? - GETDNS_BASE_TRANSPORT_TLS : GETDNS_BASE_TRANSPORT_TCP); + upstream->starttls_req = NULL; + upstream->dns_base_transport = GETDNS_BASE_TRANSPORT_TCP; upstream->tls_hs_state = GETDNS_HS_NONE; upstream->loop = NULL; (void) getdns_eventloop_event_init( @@ -670,9 +685,12 @@ set_os_defaults(struct getdns_context *context) token = parse + strcspn(parse, " \t\r\n"); *token = 0; - getdns_port_type_t port_type = GETDNS_PORT_FIRST; - for (; port_type < GETDNS_PORT_LAST; port_type++) { - if ((s = getaddrinfo(parse, getdns_port_str_array[port_type], &hints, &result))) + getdns_base_transport_t base_transport = GETDNS_BASE_TRANSPORT_MIN; + for (; base_transport < GETDNS_BASE_TRANSPORT_MAX; base_transport++) { + char * port_str = getdns_port_str_array[base_transport]; + if (strncmp(port_str, GETDNS_STR_PORT_ZERO, 1) == 0) + continue; + if ((s = getaddrinfo(parse, port_str, &hints, &result))) continue; /* No lookups, so maximal 1 result */ @@ -686,6 +704,7 @@ set_os_defaults(struct getdns_context *context) upstream = &context->upstreams-> upstreams[context->upstreams->count++]; upstream_init(upstream, context->upstreams, result); + upstream->dns_base_transport = base_transport; } freeaddrinfo(result); } @@ -1167,6 +1186,11 @@ priv_set_base_dns_transports(getdns_base_transport_t *dns_base_transports, dns_base_transports[0] = GETDNS_BASE_TRANSPORT_TLS; dns_base_transports[1] = GETDNS_BASE_TRANSPORT_TCP; break; + case GETDNS_TRANSPORT_STARTTLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN: + dns_base_transports[0] = GETDNS_BASE_TRANSPORT_STARTTLS; + dns_base_transports[1] = GETDNS_BASE_TRANSPORT_TCP; + break; + default: return GETDNS_RETURN_CONTEXT_UPDATE_FAIL; } @@ -1197,6 +1221,7 @@ set_ub_dns_transport(struct getdns_context* context, set_ub_string_opt(context, "ssl-upstream:", "yes"); /* Fall through*/ case GETDNS_TRANSPORT_TLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN: + case GETDNS_TRANSPORT_STARTTLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN: /* Note: no 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"); @@ -1482,11 +1507,12 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, hints.ai_addr = NULL; hints.ai_next = NULL; - upstreams = upstreams_create(context, count*2); + /* TODO[TLS]: Resize on the fly to avoid hardcoding this*/ + upstreams = upstreams_create(context, count*3); for (i = 0; i < count; i++) { - /* Loop twice to create TCP and TLS upstreams*/ - getdns_port_type_t port_type = GETDNS_PORT_FIRST; - for (; port_type < GETDNS_PORT_LAST; port_type++) { + /* Loop to create upstreams as needed*/ + getdns_base_transport_t base_transport = GETDNS_BASE_TRANSPORT_MIN; + for (; base_transport < GETDNS_BASE_TRANSPORT_MAX; base_transport++) { getdns_dict *dict; getdns_bindata *address_type; getdns_bindata *address_data; @@ -1495,6 +1521,11 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, struct addrinfo *ai; getdns_upstream *upstream; + /* So should we be throwing away the port the user set?*/ + port = getdns_port_array[base_transport]; + if (port == GETDNS_PORT_ZERO) + continue; + upstream = &upstreams->upstreams[upstreams->count]; if ((r = getdns_list_get_dict(upstream_list, i, &dict))) goto error; @@ -1522,8 +1553,6 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, addrstr, 1024) == NULL) goto invalid_parameter; - /* So should we be throwing away the port the user set?*/ - port = (uint32_t)(int)getdns_port_array[port_type]; (void) getdns_dict_get_int(dict, "port", &port); (void) snprintf(portstr, 1024, "%d", (int)port); @@ -1541,6 +1570,7 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, goto invalid_parameter; upstream_init(upstream, upstreams, ai); + upstream->dns_base_transport = base_transport; upstreams->count++; freeaddrinfo(ai); } @@ -1764,9 +1794,9 @@ ub_setup_stub(struct ub_ctx *ctx, getdns_context *context) upstream = &upstreams->upstreams[i]; /*[TLS]: Use only the subset of upstreams that match the first transport */ if (context->dns_transport == GETDNS_TRANSPORT_TLS_ONLY_KEEP_CONNECTIONS_OPEN) { - if (upstream_port(upstream) != GETDNS_PORT_NUM_TLS) + if (upstream_port(upstream) != GETDNS_PORT_TLS) continue; - } else if (upstream_port(upstream) != GETDNS_PORT_NUM_TCP) + } else if (upstream_port(upstream) != GETDNS_PORT_TCP) continue; upstream_ntop_buf(upstream, addr, 1024); ub_ctx_set_fwd(ctx, addr); diff --git a/src/context.h b/src/context.h index 75d266f7..78488f77 100644 --- a/src/context.h +++ b/src/context.h @@ -49,10 +49,6 @@ struct ub_ctx; #define GETDNS_FN_RESOLVCONF "/etc/resolv.conf" #define GETDNS_FN_HOSTS "/etc/hosts" -#define GETDNS_PORT_NUM_TCP 53 -#define GETDNS_PORT_NUM_TLS 1021 -#define GETDNS_PORT_STR_TCP "53" -#define GETDNS_PORT_STR_TLS "1021" enum filechgs { GETDNS_FCHG_ERRORS = -1 , GETDNS_FCHG_NOERROR = 0 @@ -75,13 +71,6 @@ struct filechg { struct stat *prevstat; }; -typedef enum getdns_port_type { - GETDNS_PORT_FIRST = 0, - GETDNS_PORT_TCP = 0, - GETDNS_PORT_TLS = 1, - GETDNS_PORT_LAST = 2 -} getdns_port_type_t; - typedef enum getdns_tls_hs_state { GETDNS_HS_NONE, GETDNS_HS_WRITE, @@ -102,9 +91,10 @@ typedef struct getdns_upstream { /* For sharing a TCP socket to this upstream */ int fd; - SSL* tls_obj; getdns_base_transport_t dns_base_transport; + SSL* tls_obj; getdns_tls_hs_state_t tls_hs_state; + getdns_dns_req * starttls_req; getdns_eventloop_event event; getdns_eventloop *loop; getdns_tcp_state tcp; diff --git a/src/getdns/getdns.h.in b/src/getdns/getdns.h.in index f69a2dfc..a2d6ea49 100755 --- a/src/getdns/getdns.h.in +++ b/src/getdns/getdns.h.in @@ -165,7 +165,8 @@ typedef enum getdns_transport_t { GETDNS_TRANSPORT_TCP_ONLY = 542, GETDNS_TRANSPORT_TCP_ONLY_KEEP_CONNECTIONS_OPEN = 543, GETDNS_TRANSPORT_TLS_ONLY_KEEP_CONNECTIONS_OPEN = 544, - GETDNS_TRANSPORT_TLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN = 545 + GETDNS_TRANSPORT_TLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN = 545, + GETDNS_TRANSPORT_STARTTLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN = 546 } getdns_transport_t; /** @@ -178,6 +179,7 @@ typedef enum getdns_transport_t { #define GETDNS_TRANSPORT_TCP_ONLY_KEEP_CONNECTIONS_OPEN_TEXT "See getdns_context_set_dns_transport()" #define GETDNS_TRANSPORT_TLS_ONLY_KEEP_CONNECTIONS_OPEN_TEXT "See getdns_context_set_dns_transport()" #define GETDNS_TRANSPORT_TLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN_TEXT "See getdns_context_set_dns_transport()" +#define GETDNS_TRANSPORT_STARTTLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN_TEXT "See getdns_context_set_dns_transport()" /** @} */ diff --git a/src/request-internal.c b/src/request-internal.c index 9b1fb81e..f83d0805 100644 --- a/src/request-internal.c +++ b/src/request-internal.c @@ -89,8 +89,8 @@ network_req_init(getdns_network_req *net_req, getdns_dns_req *owner, net_req->upstream = NULL; net_req->fd = -1; - priv_set_base_dns_transports(net_req->dns_base_transports, - GETDNS_TRANSPORT_UDP_FIRST_AND_FALL_BACK_TO_TCP); + for (i = 0; i < GETDNS_BASE_TRANSPORT_MAX; i++) + net_req->dns_base_transports[i] = owner->context->dns_base_transports[i]; net_req->dns_base_transport = net_req->dns_base_transports; memset(&net_req->event, 0, sizeof(net_req->event)); memset(&net_req->tcp, 0, sizeof(net_req->tcp)); diff --git a/src/stub.c b/src/stub.c index 9945e80a..bcdadce0 100755 --- a/src/stub.c +++ b/src/stub.c @@ -36,17 +36,17 @@ #include "stub.h" #include "gldns/gbuffer.h" #include "gldns/pkthdr.h" +#include "gldns/rrdef.h" +#include "gldns/str2wire.h" +#include "rr-iter.h" #include "context.h" #include #include "util-internal.h" #include "general.h" -#define STUB_TLS_SETUP_ERROR -3 -#define STUB_TCP_AGAIN -2 -#define STUB_TCP_ERROR -1 - -/*TODO[TLS]: REMOVE!!!!!*/ -#define TLS_DEBUG 1 +#define STUB_TLS_SETUP_ERROR -4 +#define STUB_TCP_AGAIN -3 +#define STUB_TCP_ERROR -2 static time_t secret_rollover_time = 0; static uint32_t secret = 0; @@ -54,14 +54,11 @@ static uint32_t prev_secret = 0; static void upstream_read_cb(void *userarg); static void upstream_write_cb(void *userarg); -static int tcp_connect (getdns_upstream *upstream, getdns_base_transport_t transport); -static int connect_to_upstream(getdns_upstream *upstream, - getdns_base_transport_t transport, - getdns_context *context); static void upstream_schedule_netreq(getdns_upstream *upstream, getdns_network_req *netreq); static void netreq_upstream_read_cb(void *userarg); static void netreq_upstream_write_cb(void *userarg); +static int fallback_on_write(getdns_network_req *netreq); static void rollover_secret() @@ -233,6 +230,108 @@ match_and_process_server_cookie( return 0; } +static int +create_starttls_request(getdns_dns_req *dnsreq, getdns_upstream *upstream, + getdns_eventloop *loop) +{ + getdns_return_t r = GETDNS_RETURN_GOOD; + getdns_dict* extensions = getdns_dict_create_with_context(dnsreq->context); + if (!extensions) { + return 0; + } + r = getdns_dict_set_int(extensions, "specify_class", GLDNS_RR_CLASS_CH); + if (r != GETDNS_RETURN_GOOD) { + getdns_dict_destroy(extensions); + return 0; + } + upstream->starttls_req = dns_req_new(dnsreq->context, loop, + "STARTTLS", GETDNS_RRTYPE_TXT, extensions); + /*TODO[STARTTLS]: TO BIT*/ + if (upstream->starttls_req == NULL) + return 0; + getdns_dict_destroy(extensions); + + upstream->starttls_req->netreqs[0]->upstream = upstream; + return 1; + +} + +static int +dname_equal(uint8_t *s1, uint8_t *s2) +{ + uint8_t i; + for (;;) { + if (*s1 != *s2) + return 0; + else if (!*s1) + return 1; + for (i = *s1++, s2++; i > 0; i--, s1++, s2++) + if ((*s1 & 0xDF) != (*s2 & 0xDF)) + return 0; + } +} + +static int +is_starttls_response(getdns_network_req *netreq) { + + priv_getdns_rr_iter rr_iter_storage, *rr_iter; + priv_getdns_rdf_iter rdf_iter_storage, *rdf_iter; + uint16_t rr_type; + gldns_pkt_section section; + uint8_t starttls_name_space[256], + *starttls_name = starttls_name_space; + uint8_t owner_name_space[256], *owner_name; + size_t starttls_name_len = 256, owner_name_len; + + /* Servers that are not STARTTLS aware will refuse the CH query*/ + if (LDNS_RCODE_NOERROR != + GLDNS_RCODE_WIRE(netreq->response)) { + fprintf(stderr, "[STARTTLS] STARTTLS response had error %d\n", GLDNS_RCODE_WIRE(netreq->response)); + return 0; + } + + if (GLDNS_ANCOUNT(netreq->response) != 1) + return 0; + + (void) gldns_str2wire_dname_buf( + netreq->owner->name, starttls_name_space, &starttls_name_len); + + for ( rr_iter = priv_getdns_rr_iter_init(&rr_iter_storage + , netreq->response + , netreq->response_len) + ; rr_iter + ; rr_iter = priv_getdns_rr_iter_next(rr_iter)) { + + section = priv_getdns_rr_iter_section(rr_iter); + rr_type = gldns_read_uint16(rr_iter->rr_type); + if (section != GLDNS_SECTION_ANSWER || rr_type != GETDNS_RRTYPE_TXT) + continue; + + owner_name = priv_getdns_owner_if_or_as_decompressed( + rr_iter, owner_name_space, &owner_name_len); + if (!dname_equal(starttls_name, owner_name)) + continue; + + if (!(rdf_iter = priv_getdns_rdf_iter_init( + &rdf_iter_storage, rr_iter))) + continue; + /* re-use the starttls_name for the response dname*/ + starttls_name = priv_getdns_rdf_if_or_as_decompressed( + rdf_iter,starttls_name_space,&starttls_name_len); + if (dname_equal(starttls_name, owner_name)) { + fprintf(stderr, "[STARTTLS] STARTTLS response received :%s:\n", (char*)starttls_name); + return 1; + } else { + fprintf(stderr, "[STARTTLS] NO_TLS response received :%s:\n", (char*)starttls_name); + return 0; + } + continue; + } + return 0; +} + + + /** best effort to set nonblocking */ static void getdns_sock_nonblock(int sockfd) @@ -323,9 +422,9 @@ static void upstream_erred(getdns_upstream *upstream) { getdns_network_req *netreq; -#ifdef TLS_DEBUG + fprintf(stderr,"[TLS]: ERROR(upstream_erred)\n"); -#endif + while ((netreq = upstream->write_queue)) { stub_cleanup(netreq); netreq->state = NET_REQ_FINISHED; @@ -347,6 +446,13 @@ upstream_erred(getdns_upstream *upstream) upstream->fd = -1; /*TODO[TLS]: Upstream errors don't trigger the user callback....*/ } +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) @@ -358,9 +464,9 @@ priv_getdns_cancel_stub_request(getdns_network_req *netreq) static void stub_erred(getdns_network_req *netreq) { -#ifdef TLS_DEBUG + fprintf(stderr,"[TLS]: ERROR(stub_erred)\n"); -#endif + stub_next_upstream(netreq); stub_cleanup(netreq); /* TODO[TLS]: When we get an error (which is probably a timeout) and are @@ -373,9 +479,9 @@ stub_erred(getdns_network_req *netreq) static void stub_timeout_cb(void *userarg) { -#ifdef TLS_DEBUG + fprintf(stderr,"[TLS]: TIMEOUT(stub_timeout_cb)\n"); -#endif + getdns_network_req *netreq = (getdns_network_req *)userarg; stub_next_upstream(netreq); @@ -488,13 +594,17 @@ stub_udp_write_cb(void *userarg) stub_udp_read_cb, NULL, stub_timeout_cb)); } - +/* TODO[TLS]: Optimise to re-use TCP (or failed STARTTLS) where possible.*/ static int transport_valid(struct getdns_upstream *upstream, getdns_base_transport_t transport) { + if (transport == GETDNS_BASE_TRANSPORT_UDP || + transport == GETDNS_BASE_TRANSPORT_TCP_SINGLE) + return 1; if (upstream->dns_base_transport != transport) return 0; - if (transport == GETDNS_BASE_TRANSPORT_TLS && - upstream->tls_hs_state == GETDNS_HS_FAILED) + if ((transport == GETDNS_BASE_TRANSPORT_TLS || + transport == GETDNS_BASE_TRANSPORT_STARTTLS) + && upstream->tls_hs_state == GETDNS_HS_FAILED) return 0; return 1; } @@ -663,9 +773,8 @@ create_tls_object(getdns_context *context, int fd) static int do_tls_handshake(getdns_upstream *upstream) { -#ifdef TLS_DEBUG + fprintf(stderr,"[TLS]: TLS(do_tls_handshake)\n"); -#endif int r; int want; @@ -708,85 +817,11 @@ do_tls_handshake(getdns_upstream *upstream) return 0; } -/* TODO[TLS]: Make generic function for switching transport */ -static getdns_upstream* -pick_and_connect_to_fallback_upstream(getdns_network_req *netreq) -{ -#ifdef TLS_DEBUG - fprintf(stderr,"[TLS]: FALLBACK(pick_and_connect_to_fallback_upstream)\n"); -#endif - getdns_base_transport_t *next_transport = netreq->dns_base_transport; - if (*(++next_transport) != GETDNS_BASE_TRANSPORT_TCP) - /* TODO[TLS]: Fallback through upstreams....?*/ - return NULL; - getdns_upstream *new_upstream = pick_upstream(netreq, *next_transport); - /* TODO[TLS]: Fallback through upstreams....?*/ - if (!new_upstream) - return NULL; - int fd = connect_to_upstream(new_upstream, *next_transport, netreq->owner->context); - if (fd == -1) - return NULL; -#ifdef TLS_DEBUG - fprintf(stderr,"[TLS]: FALLBACK(pick_and_connect_to_fallback_upstream): now using fd %d \n", new_upstream->fd); -#endif - return new_upstream; -} - - static int -move_netreq(getdns_network_req *netreq, getdns_upstream *upstream, getdns_upstream *new_upstream) +tls_handshake_active(getdns_tls_hs_state_t hs_state) { - /* Remove from queue, clearing event and fd if we are the last*/ -#ifdef TLS_DEBUG - fprintf(stderr,"[TLS]: FALLBACK(move_netreq)\n"); -#endif - 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); - close(upstream->fd); - upstream->fd = -1; - } - netreq->write_queue_tail = NULL; - - /* Schedule with the new upstream */ - netreq->upstream = new_upstream; - upstream_schedule_netreq(new_upstream, netreq); - - /* TODO[TLS]: Timout need to be adjusted and rescheduled on the new fd ....*/ - /* Note, setup timeout should be shorter than message timeout for - * messages with fallback or don't have time to re-try. */ - - /* For sync messages we must re-schedule the events here.*/ - if (netreq->owner->loop != upstream->loop) { - /* Create an event for the new upstream*/ - GETDNS_CLEAR_EVENT(netreq->owner->loop, &netreq->event); - GETDNS_SCHEDULE_EVENT( - netreq->owner->loop, new_upstream->fd, netreq->owner->context->timeout, - getdns_eventloop_event_init(&netreq->event, netreq, - ( new_upstream->netreq_by_query_id.count ? - netreq_upstream_read_cb : NULL ), - ( new_upstream->write_queue ? - netreq_upstream_write_cb : NULL), - stub_timeout_cb)); - - /* Now one for the old upstream. Must schedule this last to make sure - * it is called back first....?*/ - if (upstream->write_queue) { - GETDNS_CLEAR_EVENT(netreq->owner->loop, &upstream->write_queue->event); - GETDNS_SCHEDULE_EVENT( - upstream->write_queue->owner->loop, upstream->fd, - upstream->write_queue->owner->context->timeout, - getdns_eventloop_event_init(&upstream->write_queue->event, - upstream->write_queue, - ( upstream->netreq_by_query_id.count ? - netreq_upstream_read_cb : NULL ), - ( upstream->write_queue ? - netreq_upstream_write_cb : NULL), - stub_timeout_cb)); - } - } - return STUB_TCP_AGAIN; + return (hs_state == GETDNS_HS_FAILED || + hs_state == GETDNS_HS_NONE) ? 0 : 1; } static int @@ -811,9 +846,8 @@ check_tls(getdns_upstream* upstream) if (error == EINPROGRESS || error == EWOULDBLOCK) return STUB_TCP_AGAIN; /* try again */ else if (error != 0) { -#ifdef TLS_DEBUG + fprintf(stderr,"[TLS]: TLS(check_tls): died gettting connection\n"); -#endif SSL_free(upstream->tls_obj); upstream->tls_obj = NULL; upstream->tls_hs_state = GETDNS_HS_FAILED; @@ -914,12 +948,13 @@ upstream_read_cb(void *userarg) uint16_t query_id; intptr_t query_id_intptr; -#ifdef TLS_DEBUG + fprintf(stderr,"[TLS]: **********CALLBACK***********\n"); fprintf(stderr,"[TLS]: READ(upstream_read_cb): on %d\n", upstream->fd); -#endif - if (upstream->dns_base_transport == GETDNS_BASE_TRANSPORT_TLS) + if (upstream->dns_base_transport == GETDNS_BASE_TRANSPORT_TLS || + (upstream->dns_base_transport == GETDNS_BASE_TRANSPORT_STARTTLS && + tls_handshake_active(upstream->tls_hs_state))) q = stub_tls_read(upstream, &upstream->tcp, &upstream->upstreams->mf); else @@ -978,7 +1013,32 @@ upstream_read_cb(void *userarg) netreq_upstream_write_cb : NULL), stub_timeout_cb)); } - priv_getdns_check_dns_req_complete(netreq->owner); + + if (netreq->owner == upstream->starttls_req) { + dnsreq = netreq->owner; + fprintf(stderr, "[STARTTLS] processing STARTTLS response!\n"); + if (is_starttls_response(netreq)) { + upstream->tls_obj = create_tls_object(dnsreq->context, upstream->fd); + if (upstream->tls_obj == NULL) { + fprintf(stderr,"[TLS]: could not create tls object\n"); + upstream->tls_hs_state = GETDNS_HS_FAILED; + } + upstream->tls_hs_state = GETDNS_HS_WRITE; + } else + upstream->tls_hs_state = GETDNS_HS_FAILED; + dns_req_free(upstream->starttls_req); + upstream->starttls_req = NULL; + + // Now reschedule the writes on this connection + upstream->event.write_cb = upstream_write_cb; + fprintf(stderr, "[STARTTLS] method: upstream_schedule_netreq -> re-instating writes\n"); + GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); + GETDNS_SCHEDULE_EVENT(upstream->loop, + upstream->fd, TIMEOUT_FOREVER, &upstream->event); + } else { + fprintf(stderr, "[STARTTLS] processing standard response....\n"); + priv_getdns_check_dns_req_complete(netreq->owner); + } /* Nothing more to read? Then deschedule the reads.*/ if (! upstream->netreq_by_query_id.count) { @@ -1005,7 +1065,6 @@ netreq_upstream_read_cb(void *userarg) static int stub_tcp_write(int fd, getdns_tcp_state *tcp, getdns_network_req *netreq) { - getdns_dns_req *dnsreq = netreq->owner; size_t pkt_len = netreq->response - netreq->query; ssize_t written; @@ -1024,9 +1083,8 @@ stub_tcp_write(int fd, getdns_tcp_state *tcp, getdns_network_req *netreq) * the write_queue) for that upstream. Register this netreq * by query_id in the process. */ - if ((dnsreq->context->dns_transport == GETDNS_TRANSPORT_TCP_ONLY) || - (dnsreq->context->dns_transport == GETDNS_TRANSPORT_UDP_ONLY) || - (dnsreq->context->dns_transport == GETDNS_TRANSPORT_UDP_FIRST_AND_FALL_BACK_TO_TCP)) + if ((*netreq->dns_base_transport == GETDNS_BASE_TRANSPORT_TCP_SINGLE) || + (*netreq->dns_base_transport == GETDNS_BASE_TRANSPORT_UDP)) query_id = arc4random(); else do { query_id = arc4random(); @@ -1142,7 +1200,8 @@ stub_tcp_write_cb(void *userarg) } static int -stub_tls_write(getdns_upstream *upstream, getdns_tcp_state *tcp, getdns_network_req *netreq) +stub_tls_write(getdns_upstream *upstream, getdns_tcp_state *tcp, + getdns_network_req *netreq) { size_t pkt_len = netreq->response - netreq->query; ssize_t written; @@ -1198,17 +1257,18 @@ static void upstream_write_cb(void *userarg) { getdns_upstream *upstream = (getdns_upstream *)userarg; - getdns_upstream *new_upstream; getdns_network_req *netreq = upstream->write_queue; getdns_dns_req *dnsreq = netreq->owner; int q; -#ifdef TLS_DEBUG - fprintf(stderr,"[TLS]: **********CALLBACK***********\n"); - fprintf(stderr,"[TLS]: WRITE(upstream_write_cb): upstream fd %d, SEND netreq %p \n", upstream->fd, netreq); -#endif - if (upstream->dns_base_transport == GETDNS_BASE_TRANSPORT_TLS) + fprintf(stderr,"[TLS]: **********CALLBACK***********\n"); + fprintf(stderr,"[TLS]: WRITE(upstream_write_cb): upstream fd %d, SEND" + " netreq %p \n", upstream->fd, netreq); + + if (*netreq->dns_base_transport == GETDNS_BASE_TRANSPORT_TLS || + (*netreq->dns_base_transport == GETDNS_BASE_TRANSPORT_STARTTLS && + upstream->tls_hs_state != GETDNS_HS_NONE)) q = stub_tls_write(upstream, &upstream->tcp, netreq); else q = stub_tcp_write(upstream->fd, &upstream->tcp, netreq); @@ -1222,17 +1282,11 @@ upstream_write_cb(void *userarg) return; case STUB_TLS_SETUP_ERROR: - /* Could not complete the TLS set up. Need to fallback on this upstream - * if possible.*/ - new_upstream = pick_and_connect_to_fallback_upstream(netreq); - if (!new_upstream) { - //TODO[TLS]: Need a different error case here for msg_erred? - stub_erred(netreq); - return; + /* Could not complete the TLS set up. Need to fallback.*/ + if (fallback_on_write(netreq) == STUB_TCP_ERROR) { + fprintf(stderr,"[TLS]: Fallback failed.\n"); + message_erred(netreq); } - if (move_netreq(netreq, upstream, new_upstream) == STUB_TCP_ERROR) - //TODO[TLS]: Need a different error case here for msg_erred? - stub_erred(netreq); return; default: @@ -1259,6 +1313,16 @@ upstream_write_cb(void *userarg) GETDNS_SCHEDULE_EVENT(upstream->loop, upstream->fd, TIMEOUT_FOREVER, &upstream->event); } + if (upstream->starttls_req) { + /* Now deschedule any further writes on this connection until we get + the STARTTLS answer*/ + fprintf(stderr, "[STARTTLS] method: upstream_write_cb -> STARTTTLS -" + "clearing upstream->event.write_cb\n"); + upstream->event.write_cb = NULL; + GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); + GETDNS_SCHEDULE_EVENT(upstream->loop, + upstream->fd, TIMEOUT_FOREVER, &upstream->event); + } /* With synchonous lookups, schedule the read locally too */ if (netreq->event.write_cb) { GETDNS_CLEAR_EVENT(dnsreq->loop, &netreq->event); @@ -1266,7 +1330,7 @@ upstream_write_cb(void *userarg) dnsreq->loop, upstream->fd, dnsreq->context->timeout, getdns_eventloop_event_init(&netreq->event, netreq, netreq_upstream_read_cb, - ( upstream->write_queue ? + (upstream->write_queue && !upstream->starttls_req ? netreq_upstream_write_cb : NULL), stub_timeout_cb)); } @@ -1287,9 +1351,8 @@ upstream_schedule_netreq(getdns_upstream *upstream, getdns_network_req *netreq) assert(upstream->fd >= 0); assert(upstream->loop); -#ifdef TLS_DEBUG + fprintf(stderr,"[TLS]: SCHEDULE(upstream_schedule_netreq): fd %d\n", upstream->fd); -#endif /* Append netreq to write_queue */ if (!upstream->write_queue) { @@ -1331,25 +1394,40 @@ tcp_connect(getdns_upstream *upstream, getdns_base_transport_t transport) int connect_to_upstream(getdns_upstream *upstream, getdns_base_transport_t transport, - getdns_context *context) + getdns_dns_req *dnsreq) { - - if (transport == GETDNS_BASE_TRANSPORT_TCP && upstream->fd != -1) { -#ifdef TLS_DEBUG - fprintf(stderr,"[TLS]: CONNECT(connect_to_upstream): tcp_connect using existing TCP fd %d\n", upstream->fd); -#endif - return upstream->fd; - } - - if (transport == GETDNS_BASE_TRANSPORT_TLS && - !(upstream->tls_hs_state == GETDNS_HS_FAILED - || upstream->tls_hs_state == GETDNS_HS_NONE)) { -#ifdef TLS_DEBUG - fprintf(stderr,"[TLS]: CONNECT(connect_to_upstream): tcp_connect using existing TLS fd %d\n", upstream->fd); -#endif - return upstream->fd; + /* First check if existing connection can be used, which may still be being + * set up. */ + switch(transport) { + case GETDNS_BASE_TRANSPORT_TCP: + if (upstream->fd != -1) { + fprintf(stderr,"[TLS]: CONNECT(connect_to_upstream):" + "tcp_connect using existing TCP fd %d\n", upstream->fd); + return upstream->fd; + } + break; + case GETDNS_BASE_TRANSPORT_TLS: + if (tls_handshake_active(upstream->tls_hs_state)) { + fprintf(stderr,"[TLS]: CONNECT(connect_to_upstream):" + "tcp_connect using existing TLS fd %d\n", upstream->fd); + return upstream->fd; + } + break; + case GETDNS_BASE_TRANSPORT_STARTTLS: + /* Either negotiating, or doing handshake*/ + if ((upstream->starttls_req != NULL) || + (upstream->starttls_req == NULL && + tls_handshake_active(upstream->tls_hs_state))) { + fprintf(stderr,"[TLS]: CONNECT(connect_to_upstream):" + "tcp_connect using existing STARTTLS fd %d\n", upstream->fd); + return upstream->fd; + } + break; + default: + break; } + /* If not, create a new one */ int fd = -1; switch(transport) { case GETDNS_BASE_TRANSPORT_UDP: @@ -1362,63 +1440,175 @@ connect_to_upstream(getdns_upstream *upstream, getdns_base_transport_t transport case GETDNS_BASE_TRANSPORT_TCP_SINGLE: case GETDNS_BASE_TRANSPORT_TCP: fd = tcp_connect(upstream, transport); + upstream->loop = dnsreq->context->extension; + upstream->fd = fd; break; case GETDNS_BASE_TRANSPORT_TLS: fd = tcp_connect(upstream, transport); if (fd == -1) return -1; - upstream->tls_obj = create_tls_object(context, fd); + upstream->tls_obj = create_tls_object(dnsreq->context, fd); if (upstream->tls_obj == NULL) { fprintf(stderr,"[TLS]: could not create tls object\n"); close(fd); return -1; } upstream->tls_hs_state = GETDNS_HS_WRITE; + upstream->loop = dnsreq->context->extension; + upstream->fd = fd; + break; + case GETDNS_BASE_TRANSPORT_STARTTLS: + fd = tcp_connect(upstream, transport); + if (fd == -1) return -1; + if (!create_starttls_request(dnsreq, upstream, dnsreq->loop)) + return GETDNS_RETURN_GENERIC_ERROR; + getdns_network_req *starttls_netreq = upstream->starttls_req->netreqs[0]; + upstream->loop = dnsreq->context->extension; + upstream->fd = fd; + upstream_schedule_netreq(upstream, starttls_netreq); + /* Schedule at least the timeout locally. + * And also the write if we perform a synchronous lookup */ + /* TODO[TLS]: How should we handle timeout on STARTTLS negotiation?*/ + GETDNS_SCHEDULE_EVENT( + dnsreq->loop, upstream->fd, dnsreq->context->timeout, + getdns_eventloop_event_init(&starttls_netreq->event, + starttls_netreq, NULL, (dnsreq->loop != upstream->loop + ? netreq_upstream_write_cb : NULL), stub_timeout_cb)); break; default: return -1; /* Nothing to do*/ } - if (fd != -1) { - upstream->loop = context->extension; - upstream->fd = fd; - } -#ifdef TLS_DEBUG fprintf(stderr,"[TLS]: CONNECT(connect_to_upstream): created new connection %d\n", fd); -#endif return fd; } +static getdns_upstream* +pick_and_connect_to_upstream(getdns_network_req *netreq, + getdns_base_transport_t transport, + int *fd) +{ + /* TODO[TLS]: Fallback through upstreams....?*/ + getdns_upstream *upstream = pick_upstream(netreq, transport); + if (!upstream) + return NULL; + *fd = connect_to_upstream(upstream, transport, netreq->owner); + return upstream; +} + +static int +find_upstream_for_netreq(getdns_network_req *netreq) +{ + int fd = -1; + for (int i = 0; i < GETDNS_BASE_TRANSPORT_MAX && + netreq->dns_base_transports[i] != GETDNS_BASE_TRANSPORT_NONE; i++) { + netreq->upstream = pick_and_connect_to_upstream(netreq, + netreq->dns_base_transports[i], + &fd); + if (fd == -1) + continue; + return fd; + } + return -1; +} + +static int +move_netreq(getdns_network_req *netreq, getdns_upstream *upstream, + getdns_upstream *new_upstream) +{ + /* Remove from queue, clearing event and fd if we are the last*/ + + fprintf(stderr,"[TLS]: FALLBACK(move_netreq)\n"); + + 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); + close(upstream->fd); + upstream->fd = -1; + } + netreq->write_queue_tail = NULL; + + /* Schedule with the new upstream */ + netreq->upstream = new_upstream; + upstream_schedule_netreq(new_upstream, netreq); + + /* TODO[TLS]: Timout need to be adjusted and rescheduled on the new fd ...*/ + /* Note, setup timeout should be shorter than message timeout for + * messages with fallback or don't have time to re-try. */ + + /* For sync messages we must re-schedule the events here.*/ + if (netreq->owner->loop != upstream->loop) { + /* Create an event for the new upstream*/ + GETDNS_CLEAR_EVENT(netreq->owner->loop, &netreq->event); + GETDNS_SCHEDULE_EVENT( + netreq->owner->loop, new_upstream->fd, netreq->owner->context->timeout, + getdns_eventloop_event_init(&netreq->event, netreq, + ( new_upstream->netreq_by_query_id.count ? + netreq_upstream_read_cb : NULL ), + ( new_upstream->write_queue ? + netreq_upstream_write_cb : NULL), + stub_timeout_cb)); + + /* Now one for the old upstream. Must schedule this last to make sure + * it is called back first....?*/ + if (upstream->write_queue) { + GETDNS_CLEAR_EVENT(netreq->owner->loop, &upstream->write_queue->event); + GETDNS_SCHEDULE_EVENT( + upstream->write_queue->owner->loop, upstream->fd, + upstream->write_queue->owner->context->timeout, + getdns_eventloop_event_init(&upstream->write_queue->event, + upstream->write_queue, + ( upstream->netreq_by_query_id.count ? + netreq_upstream_read_cb : NULL ), + ( upstream->write_queue ? + netreq_upstream_write_cb : NULL), + stub_timeout_cb)); + } + } + netreq->dns_base_transport++; + return upstream->fd; +} + +static int +fallback_on_write(getdns_network_req *netreq) +{ + + fprintf(stderr,"[TLS]: FALLBACK(fallback_on_write)\n"); + + /* TODO[TLS]: Fallback through all transports.*/ + getdns_base_transport_t *next_transport = netreq->dns_base_transport; + if (*(++next_transport) == GETDNS_BASE_TRANSPORT_NONE) + return STUB_TCP_ERROR; + + if (*netreq->dns_base_transport == GETDNS_BASE_TRANSPORT_STARTTLS && + *next_transport == GETDNS_BASE_TRANSPORT_TCP) { + fprintf(stderr,"[TLS]: FALLBACK(fallback_on_write) STARTTLS->TCP\n"); + /* Special case where can stay on same upstream*/ + netreq->dns_base_transport++; + return netreq->upstream->fd; + } + getdns_upstream *upstream = netreq->upstream; + int fd; + getdns_upstream *new_upstream = + pick_and_connect_to_upstream(netreq, *next_transport, &fd); + if (!new_upstream) + return STUB_TCP_ERROR; + return move_netreq(netreq, upstream, new_upstream); +} + getdns_return_t priv_getdns_submit_stub_request(getdns_network_req *netreq) { int fd = -1; - int i; - getdns_dns_req *dnsreq = netreq->owner; - getdns_upstream *upstream = NULL; + getdns_dns_req *dnsreq = netreq->owner; - /* This loop does a best effort to get a initial fd falling back through - * transport (then upstream?). All other set up is done async*/ - for (i = 0; i < GETDNS_BASE_TRANSPORT_MAX; i++) - netreq->dns_base_transports[i] = dnsreq->context->dns_base_transports[i]; - for (i = 0; i < GETDNS_BASE_TRANSPORT_MAX && - netreq->dns_base_transports[i] != GETDNS_BASE_TRANSPORT_NONE; i++) { - /*TODO[TLS]: Loop over upstreams, but don't loop more than once*/ - upstream = pick_upstream(netreq, netreq->dns_base_transports[i]); - if (!upstream) { - continue; - } - fd = connect_to_upstream(upstream, netreq->dns_base_transports[i], - dnsreq->context); - if (fd != -1) - break; - } + /* This does a best effort to get a initial fd. + * All other set up is done async*/ + fd = find_upstream_for_netreq(netreq); if (fd == -1) return GETDNS_RETURN_GENERIC_ERROR; - netreq->upstream = upstream; - netreq->dns_base_transport = &(netreq->dns_base_transports[i]); - switch(*netreq->dns_base_transport) { case GETDNS_BASE_TRANSPORT_UDP: case GETDNS_BASE_TRANSPORT_TCP_SINGLE: @@ -1430,16 +1620,17 @@ priv_getdns_submit_stub_request(getdns_network_req *netreq) stub_udp_write_cb: stub_tcp_write_cb), stub_timeout_cb)); return GETDNS_RETURN_GOOD; + case GETDNS_BASE_TRANSPORT_STARTTLS: case GETDNS_BASE_TRANSPORT_TCP: case GETDNS_BASE_TRANSPORT_TLS: - upstream_schedule_netreq(upstream, netreq); + upstream_schedule_netreq(netreq->upstream, netreq); /* TODO[TLS]: Timeout handling for async calls must change.... * Maybe even change scheduling for sync calls here too*/ GETDNS_SCHEDULE_EVENT( - dnsreq->loop, upstream->fd, dnsreq->context->timeout, + dnsreq->loop, netreq->upstream->fd, dnsreq->context->timeout, getdns_eventloop_event_init(&netreq->event, netreq, NULL, - ( dnsreq->loop != upstream->loop /* Synchronous lookup? */ + ( dnsreq->loop != netreq->upstream->loop /* Synchronous lookup? */ ? netreq_upstream_write_cb : NULL), stub_timeout_cb)); return GETDNS_RETURN_GOOD; diff --git a/src/test/getdns_query.c b/src/test/getdns_query.c index abe21529..009949f5 100644 --- a/src/test/getdns_query.c +++ b/src/test/getdns_query.c @@ -121,6 +121,7 @@ print_usage(FILE *out, const char *progname) fprintf(out, "\t-O\tSet transport to TCP only keep connections open\n"); fprintf(out, "\t-L\tSet transport to TLS only keep connections open\n"); fprintf(out, "\t-E\tSet transport to TLS with TCP fallback only keep connections open\n"); + fprintf(out, "\t-R\tSet transport to STARTTLS with TCP fallback only keep connections open\n"); fprintf(out, "\t-u\tSet transport to UDP with TCP fallback\n"); fprintf(out, "\t-U\tSet transport to UDP only\n"); fprintf(out, "\t-B\tBatch mode. Schedule all messages before processing responses.\n"); @@ -369,6 +370,10 @@ getdns_return_t parse_args(int argc, char **argv) getdns_context_set_dns_transport(context, GETDNS_TRANSPORT_TLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN); break; + case 'R': + getdns_context_set_dns_transport(context, + GETDNS_TRANSPORT_STARTTLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN); + break; case 'u': getdns_context_set_dns_transport(context, GETDNS_TRANSPORT_UDP_FIRST_AND_FALL_BACK_TO_TCP); diff --git a/src/types-internal.h b/src/types-internal.h index 249e94c7..e8d6627b 100644 --- a/src/types-internal.h +++ b/src/types-internal.h @@ -165,12 +165,13 @@ typedef struct getdns_tcp_state { } getdns_tcp_state; typedef enum getdns_base_transport { - GETDNS_BASE_TRANSPORT_NONE, + GETDNS_BASE_TRANSPORT_MIN = 0, + GETDNS_BASE_TRANSPORT_NONE = 0, GETDNS_BASE_TRANSPORT_UDP, GETDNS_BASE_TRANSPORT_TCP_SINGLE, /* To be removed? */ GETDNS_BASE_TRANSPORT_TCP, GETDNS_BASE_TRANSPORT_TLS, - GETDNS_BASE_TRANSPORT_STARTTLS, /* Not yet implemented*/ + GETDNS_BASE_TRANSPORT_STARTTLS, GETDNS_BASE_TRANSPORT_MAX } getdns_base_transport_t; From 7905eda8b702eb45d4a211bb08edcc848e588d81 Mon Sep 17 00:00:00 2001 From: Sara Dickinson Date: Thu, 30 Apr 2015 12:24:13 +0100 Subject: [PATCH 06/12] Some clean up of connection handling. Still a problem with STARTTLS fallback that needs fixing. --- src/context.c | 20 +++++++++------- src/context.h | 2 -- src/stub.c | 65 +++++++++++++++++++-------------------------------- 3 files changed, 35 insertions(+), 52 deletions(-) diff --git a/src/context.c b/src/context.c index ab075d2d..56d08476 100644 --- a/src/context.c +++ b/src/context.c @@ -267,7 +267,7 @@ sockaddr_dict(getdns_context *context, struct sockaddr *sa) break; port = ntohs(((struct sockaddr_in *)sa)->sin_port); - if (port != 0 && port != GETDNS_PORT_TCP && + if (port != GETDNS_PORT_ZERO && port != GETDNS_PORT_TCP && getdns_dict_set_int(address, "port", (uint32_t)port)) break; @@ -283,7 +283,7 @@ sockaddr_dict(getdns_context *context, struct sockaddr *sa) break; port = ntohs(((struct sockaddr_in6 *)sa)->sin6_port); - if (port != 0 && port != GETDNS_PORT_TCP && + if (port != GETDNS_PORT_TCP && port != GETDNS_PORT_TCP && getdns_dict_set_int(address, "port", (uint32_t)port)) break; @@ -553,7 +553,7 @@ upstream_ntop_buf(getdns_upstream *upstream, char *buf, size_t len) if (upstream_scope_id(upstream)) (void) snprintf(buf + strlen(buf), len - strlen(buf), "%%%d", (int)*upstream_scope_id(upstream)); - else if (upstream_port(upstream) != GETDNS_PORT_TCP && upstream_port(upstream) != 0) + else if (upstream_port(upstream) != GETDNS_PORT_TCP && upstream_port(upstream) != GETDNS_PORT_ZERO) (void) snprintf(buf + strlen(buf), len - strlen(buf), "@%d", (int)upstream_port(upstream)); } @@ -687,7 +687,7 @@ set_os_defaults(struct getdns_context *context) getdns_base_transport_t base_transport = GETDNS_BASE_TRANSPORT_MIN; for (; base_transport < GETDNS_BASE_TRANSPORT_MAX; base_transport++) { - char * port_str = getdns_port_str_array[base_transport]; + char *port_str = getdns_port_str_array[base_transport]; if (strncmp(port_str, GETDNS_STR_PORT_ZERO, 1) == 0) continue; if ((s = getaddrinfo(parse, port_str, &hints, &result))) @@ -1219,7 +1219,7 @@ set_ub_dns_transport(struct getdns_context* context, /* Note: If TLS is used in recursive mode this will try TLS on port * 53... So this is prohibited when preparing for resolution.*/ set_ub_string_opt(context, "ssl-upstream:", "yes"); - /* Fall through*/ + /* Fall through */ case GETDNS_TRANSPORT_TLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN: case GETDNS_TRANSPORT_STARTTLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN: /* Note: no fallback to TCP available directly in unbound, so we just @@ -1792,11 +1792,13 @@ ub_setup_stub(struct ub_ctx *ctx, getdns_context *context) (void) ub_ctx_set_fwd(ctx, NULL); for (i = 0; i < upstreams->count; i++) { upstream = &upstreams->upstreams[i]; - /*[TLS]: Use only the subset of upstreams that match the first transport */ - if (context->dns_transport == GETDNS_TRANSPORT_TLS_ONLY_KEEP_CONNECTIONS_OPEN) { - if (upstream_port(upstream) != GETDNS_PORT_TLS) + /*[TLS]: Use only the TLS subset of upstreams when only TLS is used. + * All other cases must currently fallback to TCP for libunbound. */ + if (context->dns_base_transports[0] == GETDNS_BASE_TRANSPORT_TLS && + context->dns_base_transports[0] == GETDNS_BASE_TRANSPORT_NONE && + upstream_port(upstream) != GETDNS_PORT_TLS) continue; - } else if (upstream_port(upstream) != GETDNS_PORT_TCP) + else if (upstream_port(upstream) != GETDNS_PORT_TCP) continue; upstream_ntop_buf(upstream, addr, 1024); ub_ctx_set_fwd(ctx, addr); diff --git a/src/context.h b/src/context.h index 78488f77..3ad6b342 100644 --- a/src/context.h +++ b/src/context.h @@ -237,8 +237,6 @@ void priv_getdns_context_ub_read_cb(void *userarg); getdns_return_t priv_set_base_dns_transports(getdns_base_transport_t *, getdns_transport_t); -getdns_base_transport_t priv_get_base_transport(getdns_transport_t transport, int level); - void priv_getdns_upstreams_dereference(getdns_upstreams *upstreams); #endif /* _GETDNS_CONTEXT_H_ */ diff --git a/src/stub.c b/src/stub.c index bcdadce0..1e1724f5 100755 --- a/src/stub.c +++ b/src/stub.c @@ -757,9 +757,8 @@ create_tls_object(getdns_context *context, int fd) if (context->tls_ctx == NULL) return NULL; SSL* ssl = SSL_new(context->tls_ctx); - if(!ssl) { + if(!ssl) return NULL; - } /* Connect the SSL object with a file descriptor */ if(!SSL_set_fd(ssl,fd)) { SSL_free(ssl); @@ -1084,7 +1083,7 @@ stub_tcp_write(int fd, getdns_tcp_state *tcp, getdns_network_req *netreq) * by query_id in the process. */ if ((*netreq->dns_base_transport == GETDNS_BASE_TRANSPORT_TCP_SINGLE) || - (*netreq->dns_base_transport == GETDNS_BASE_TRANSPORT_UDP)) + (*netreq->dns_base_transport == GETDNS_BASE_TRANSPORT_UDP)) query_id = arc4random(); else do { query_id = arc4random(); @@ -1379,7 +1378,8 @@ tcp_connect(getdns_upstream *upstream, getdns_base_transport_t transport) #ifdef USE_TCP_FASTOPEN /* Leave the connect to the later call to sendto() if using TCP*/ if (transport == GETDNS_BASE_TRANSPORT_TCP || - transport == GETDNS_BASE_TRANSPORT_TCP_SINGLE) + transport == GETDNS_BASE_TRANSPORT_TCP_SINGLE || + transport == GETDNS_BASE_TRANSPORT_STARTTLS) return fd; #endif if (connect(fd, (struct sockaddr *)&upstream->addr, @@ -1396,38 +1396,6 @@ int connect_to_upstream(getdns_upstream *upstream, getdns_base_transport_t transport, getdns_dns_req *dnsreq) { - /* First check if existing connection can be used, which may still be being - * set up. */ - switch(transport) { - case GETDNS_BASE_TRANSPORT_TCP: - if (upstream->fd != -1) { - fprintf(stderr,"[TLS]: CONNECT(connect_to_upstream):" - "tcp_connect using existing TCP fd %d\n", upstream->fd); - return upstream->fd; - } - break; - case GETDNS_BASE_TRANSPORT_TLS: - if (tls_handshake_active(upstream->tls_hs_state)) { - fprintf(stderr,"[TLS]: CONNECT(connect_to_upstream):" - "tcp_connect using existing TLS fd %d\n", upstream->fd); - return upstream->fd; - } - break; - case GETDNS_BASE_TRANSPORT_STARTTLS: - /* Either negotiating, or doing handshake*/ - if ((upstream->starttls_req != NULL) || - (upstream->starttls_req == NULL && - tls_handshake_active(upstream->tls_hs_state))) { - fprintf(stderr,"[TLS]: CONNECT(connect_to_upstream):" - "tcp_connect using existing STARTTLS fd %d\n", upstream->fd); - return upstream->fd; - } - break; - default: - break; - } - - /* If not, create a new one */ int fd = -1; switch(transport) { case GETDNS_BASE_TRANSPORT_UDP: @@ -1437,14 +1405,21 @@ connect_to_upstream(getdns_upstream *upstream, getdns_base_transport_t transport getdns_sock_nonblock(fd); return fd; - case GETDNS_BASE_TRANSPORT_TCP_SINGLE: case GETDNS_BASE_TRANSPORT_TCP: + /* Use existing if available*/ + if (upstream->fd != -1) + return upstream->fd; + /* Otherwise, fall through */ + case GETDNS_BASE_TRANSPORT_TCP_SINGLE: fd = tcp_connect(upstream, transport); upstream->loop = dnsreq->context->extension; upstream->fd = fd; break; case GETDNS_BASE_TRANSPORT_TLS: + /* Use existing if available*/ + if (upstream->fd != 1 && tls_handshake_active(upstream->tls_hs_state)) + return upstream->fd; fd = tcp_connect(upstream, transport); if (fd == -1) return -1; upstream->tls_obj = create_tls_object(dnsreq->context, fd); @@ -1458,6 +1433,12 @@ connect_to_upstream(getdns_upstream *upstream, getdns_base_transport_t transport upstream->fd = fd; break; case GETDNS_BASE_TRANSPORT_STARTTLS: + /* Use existing if available. May be either negotiating or doing TLS */ + if (upstream->fd != 1 && + (upstream->starttls_req != NULL) || + (upstream->starttls_req == NULL && + tls_handshake_active(upstream->tls_hs_state))) + return upstream->fd; fd = tcp_connect(upstream, transport); if (fd == -1) return -1; if (!create_starttls_request(dnsreq, upstream, dnsreq->loop)) @@ -1479,12 +1460,13 @@ connect_to_upstream(getdns_upstream *upstream, getdns_base_transport_t transport return -1; /* Nothing to do*/ } - fprintf(stderr,"[TLS]: CONNECT(connect_to_upstream): created new connection %d\n", fd); + fprintf(stderr,"[TLS]: CONNECT(connect_to_upstream):" + " created new connection %d\n", fd); return fd; } static getdns_upstream* -pick_and_connect_to_upstream(getdns_network_req *netreq, +find_upstream_for_specific_transport(getdns_network_req *netreq, getdns_base_transport_t transport, int *fd) { @@ -1502,7 +1484,7 @@ find_upstream_for_netreq(getdns_network_req *netreq) int fd = -1; for (int i = 0; i < GETDNS_BASE_TRANSPORT_MAX && netreq->dns_base_transports[i] != GETDNS_BASE_TRANSPORT_NONE; i++) { - netreq->upstream = pick_and_connect_to_upstream(netreq, + netreq->upstream = find_upstream_for_specific_transport(netreq, netreq->dns_base_transports[i], &fd); if (fd == -1) @@ -1524,6 +1506,7 @@ move_netreq(getdns_network_req *netreq, getdns_upstream *upstream, upstream->write_queue_last = NULL; upstream->event.write_cb = NULL; GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); + close(upstream->fd); upstream->fd = -1; } @@ -1591,7 +1574,7 @@ fallback_on_write(getdns_network_req *netreq) getdns_upstream *upstream = netreq->upstream; int fd; getdns_upstream *new_upstream = - pick_and_connect_to_upstream(netreq, *next_transport, &fd); + find_upstream_for_specific_transport(netreq, *next_transport, &fd); if (!new_upstream) return STUB_TCP_ERROR; return move_netreq(netreq, upstream, new_upstream); From 450a3bc6ff80ef1e4fd57369e28e1feeaaad4a52 Mon Sep 17 00:00:00 2001 From: Sara Dickinson Date: Thu, 30 Apr 2015 14:52:16 +0100 Subject: [PATCH 07/12] Fix STARTTLS fallback. --- src/context.c | 16 ++++++++-------- src/stub.c | 18 ++++++++++++------ src/types-internal.h | 2 +- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/context.c b/src/context.c index 56d08476..3683c749 100644 --- a/src/context.c +++ b/src/context.c @@ -75,8 +75,8 @@ getdns_port_array[GETDNS_BASE_TRANSPORT_MAX] = { GETDNS_PORT_ZERO, GETDNS_PORT_ZERO, GETDNS_PORT_TCP, - GETDNS_PORT_TLS, - GETDNS_PORT_TCP + GETDNS_PORT_TCP, + GETDNS_PORT_TLS }; char* @@ -84,9 +84,9 @@ getdns_port_str_array[] = { GETDNS_STR_PORT_ZERO, GETDNS_STR_PORT_ZERO, GETDNS_STR_PORT_ZERO, - GETDNS_STR_PORT_TCP, - GETDNS_STR_PORT_TLS, - GETDNS_STR_PORT_TCP + GETDNS_STR_PORT_TCP, + GETDNS_STR_PORT_TCP, + GETDNS_STR_PORT_TLS }; /* Private functions */ @@ -1792,10 +1792,10 @@ ub_setup_stub(struct ub_ctx *ctx, getdns_context *context) (void) ub_ctx_set_fwd(ctx, NULL); for (i = 0; i < upstreams->count; i++) { upstream = &upstreams->upstreams[i]; - /*[TLS]: Use only the TLS subset of upstreams when only TLS is used. - * All other cases must currently fallback to TCP for libunbound. */ + /*[TLS]: Use only the TLS subset of upstreams when TLS is the only thing + * used. All other cases must currently fallback to TCP for libunbound.*/ if (context->dns_base_transports[0] == GETDNS_BASE_TRANSPORT_TLS && - context->dns_base_transports[0] == GETDNS_BASE_TRANSPORT_NONE && + context->dns_base_transports[1] == GETDNS_BASE_TRANSPORT_NONE && upstream_port(upstream) != GETDNS_PORT_TLS) continue; else if (upstream_port(upstream) != GETDNS_PORT_TCP) diff --git a/src/stub.c b/src/stub.c index 1e1724f5..9542205b 100755 --- a/src/stub.c +++ b/src/stub.c @@ -594,14 +594,22 @@ stub_udp_write_cb(void *userarg) stub_udp_read_cb, NULL, stub_timeout_cb)); } -/* TODO[TLS]: Optimise to re-use TCP (or failed STARTTLS) where possible.*/ static int transport_valid(struct getdns_upstream *upstream, getdns_base_transport_t transport) { + /* For single shot transports, use any upstream. */ if (transport == GETDNS_BASE_TRANSPORT_UDP || transport == GETDNS_BASE_TRANSPORT_TCP_SINGLE) return 1; + /* Allow TCP messages to be sent on a STARTTLS upstream that hasn't upgraded + * to avoid opening a new connection if one is aleady open. */ + if (transport == GETDNS_BASE_TRANSPORT_TCP && + upstream->dns_base_transport == GETDNS_BASE_TRANSPORT_STARTTLS && + upstream->tls_hs_state == GETDNS_HS_FAILED) + return 1; + /* Otherwise, transport must match */ if (upstream->dns_base_transport != transport) return 0; + /* But don't use if upgrade failed for (START)TLS*/ if ((transport == GETDNS_BASE_TRANSPORT_TLS || transport == GETDNS_BASE_TRANSPORT_STARTTLS) && upstream->tls_hs_state == GETDNS_HS_FAILED) @@ -1433,11 +1441,9 @@ connect_to_upstream(getdns_upstream *upstream, getdns_base_transport_t transport upstream->fd = fd; break; case GETDNS_BASE_TRANSPORT_STARTTLS: - /* Use existing if available. May be either negotiating or doing TLS */ - if (upstream->fd != 1 && - (upstream->starttls_req != NULL) || - (upstream->starttls_req == NULL && - tls_handshake_active(upstream->tls_hs_state))) + /* Use existing if available. Let the fallback code handle it if STARTTLS + * isn't availble. */ + if (upstream->fd != -1) return upstream->fd; fd = tcp_connect(upstream, transport); if (fd == -1) return -1; diff --git a/src/types-internal.h b/src/types-internal.h index e8d6627b..96216b06 100644 --- a/src/types-internal.h +++ b/src/types-internal.h @@ -169,9 +169,9 @@ typedef enum getdns_base_transport { GETDNS_BASE_TRANSPORT_NONE = 0, GETDNS_BASE_TRANSPORT_UDP, GETDNS_BASE_TRANSPORT_TCP_SINGLE, /* To be removed? */ + GETDNS_BASE_TRANSPORT_STARTTLS, /* Define before TCP to allow fallback when scheduling*/ GETDNS_BASE_TRANSPORT_TCP, GETDNS_BASE_TRANSPORT_TLS, - GETDNS_BASE_TRANSPORT_STARTTLS, GETDNS_BASE_TRANSPORT_MAX } getdns_base_transport_t; From d6d83b219dfa023e0648d8e78d7e780ba6c132aa Mon Sep 17 00:00:00 2001 From: Sara Dickinson Date: Thu, 30 Apr 2015 19:07:49 +0100 Subject: [PATCH 08/12] Make sure UDP only uses 1 upstream per IP address. Fix a couple of other bugs. --- src/context.c | 33 +++++++++++++++++---------------- src/stub.c | 18 +++++++++++++----- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/context.c b/src/context.c index 3683c749..8dfde4c9 100644 --- a/src/context.c +++ b/src/context.c @@ -54,11 +54,11 @@ #include "list.h" #define GETDNS_PORT_ZERO 0 -#define GETDNS_PORT_TCP 53 -#define GETDNS_PORT_TLS 1021 +#define GETDNS_PORT_DNS 53 +#define GETDNS_PORT_DNS_OVER_TLS 1021 #define GETDNS_STR_PORT_ZERO "0" -#define GETDNS_STR_PORT_TCP "53" -#define GETDNS_STR_PORT_TLS "1021" +#define GETDNS_STR_PORT_DNS "53" +#define GETDNS_STR_PORT_DNS_OVER_TLS "1021" void *plain_mem_funcs_user_arg = MF_PLAIN; @@ -74,9 +74,9 @@ getdns_port_array[GETDNS_BASE_TRANSPORT_MAX] = { GETDNS_PORT_ZERO, GETDNS_PORT_ZERO, GETDNS_PORT_ZERO, - GETDNS_PORT_TCP, - GETDNS_PORT_TCP, - GETDNS_PORT_TLS + GETDNS_PORT_DNS, + GETDNS_PORT_DNS, + GETDNS_PORT_DNS_OVER_TLS }; char* @@ -84,9 +84,9 @@ getdns_port_str_array[] = { GETDNS_STR_PORT_ZERO, GETDNS_STR_PORT_ZERO, GETDNS_STR_PORT_ZERO, - GETDNS_STR_PORT_TCP, - GETDNS_STR_PORT_TCP, - GETDNS_STR_PORT_TLS + GETDNS_STR_PORT_DNS, + GETDNS_STR_PORT_DNS, + GETDNS_STR_PORT_DNS_OVER_TLS }; /* Private functions */ @@ -267,7 +267,7 @@ sockaddr_dict(getdns_context *context, struct sockaddr *sa) break; port = ntohs(((struct sockaddr_in *)sa)->sin_port); - if (port != GETDNS_PORT_ZERO && port != GETDNS_PORT_TCP && + if (port != GETDNS_PORT_ZERO && port != GETDNS_PORT_DNS && getdns_dict_set_int(address, "port", (uint32_t)port)) break; @@ -283,7 +283,7 @@ sockaddr_dict(getdns_context *context, struct sockaddr *sa) break; port = ntohs(((struct sockaddr_in6 *)sa)->sin6_port); - if (port != GETDNS_PORT_TCP && port != GETDNS_PORT_TCP && + if (port != GETDNS_PORT_DNS && port != GETDNS_PORT_DNS && getdns_dict_set_int(address, "port", (uint32_t)port)) break; @@ -553,7 +553,7 @@ upstream_ntop_buf(getdns_upstream *upstream, char *buf, size_t len) if (upstream_scope_id(upstream)) (void) snprintf(buf + strlen(buf), len - strlen(buf), "%%%d", (int)*upstream_scope_id(upstream)); - else if (upstream_port(upstream) != GETDNS_PORT_TCP && upstream_port(upstream) != GETDNS_PORT_ZERO) + else if (upstream_port(upstream) != GETDNS_PORT_DNS && upstream_port(upstream) != GETDNS_PORT_ZERO) (void) snprintf(buf + strlen(buf), len - strlen(buf), "@%d", (int)upstream_port(upstream)); } @@ -1521,7 +1521,6 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, struct addrinfo *ai; getdns_upstream *upstream; - /* So should we be throwing away the port the user set?*/ port = getdns_port_array[base_transport]; if (port == GETDNS_PORT_ZERO) continue; @@ -1569,6 +1568,8 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, if (getaddrinfo(addrstr, portstr, &hints, &ai)) goto invalid_parameter; + /* TODO[TLS]: Should probably check that the upstream doesn't + * already exist (in case user has specified port explicitly)*/ upstream_init(upstream, upstreams, ai); upstream->dns_base_transport = base_transport; upstreams->count++; @@ -1796,9 +1797,9 @@ ub_setup_stub(struct ub_ctx *ctx, getdns_context *context) * used. All other cases must currently fallback to TCP for libunbound.*/ if (context->dns_base_transports[0] == GETDNS_BASE_TRANSPORT_TLS && context->dns_base_transports[1] == GETDNS_BASE_TRANSPORT_NONE && - upstream_port(upstream) != GETDNS_PORT_TLS) + upstream_port(upstream) != GETDNS_PORT_DNS_OVER_TLS) continue; - else if (upstream_port(upstream) != GETDNS_PORT_TCP) + else if (upstream_port(upstream) != GETDNS_PORT_DNS) continue; upstream_ntop_buf(upstream, addr, 1024); ub_ctx_set_fwd(ctx, addr); diff --git a/src/stub.c b/src/stub.c index 9542205b..e4dfc910 100755 --- a/src/stub.c +++ b/src/stub.c @@ -596,10 +596,14 @@ stub_udp_write_cb(void *userarg) static int transport_valid(struct getdns_upstream *upstream, getdns_base_transport_t transport) { - /* For single shot transports, use any upstream. */ + /* For single shot transports, use only the TCP upstream. */ if (transport == GETDNS_BASE_TRANSPORT_UDP || - transport == GETDNS_BASE_TRANSPORT_TCP_SINGLE) - return 1; + transport == GETDNS_BASE_TRANSPORT_TCP_SINGLE) { + if (upstream->dns_base_transport == GETDNS_BASE_TRANSPORT_TCP) + return 1; + else + return 0; + } /* Allow TCP messages to be sent on a STARTTLS upstream that hasn't upgraded * to avoid opening a new connection if one is aleady open. */ if (transport == GETDNS_BASE_TRANSPORT_TCP && @@ -621,7 +625,7 @@ static getdns_upstream * pick_upstream(getdns_network_req *netreq, getdns_base_transport_t transport) { getdns_upstream *upstream; - getdns_upstreams *upstreams = netreq->owner->context->upstreams; + getdns_upstreams *upstreams = netreq->owner->upstreams; size_t i; if (!upstreams->count) @@ -648,6 +652,9 @@ pick_upstream(getdns_network_req *netreq, getdns_base_transport_t transport) transport_valid(&upstreams->upstreams[i], transport)) upstream = &upstreams->upstreams[i]; + /* Need to check again that the transport is valid */ + if (!transport_valid(upstream, transport)) + return NULL; upstream->back_off++; upstream->to_retry = 1; upstreams->current = upstream - upstreams->upstreams; @@ -1493,8 +1500,9 @@ find_upstream_for_netreq(getdns_network_req *netreq) netreq->upstream = find_upstream_for_specific_transport(netreq, netreq->dns_base_transports[i], &fd); - if (fd == -1) + if (fd == -1 || !netreq->upstream) continue; + netreq->dns_base_transport = &netreq->dns_base_transports[i]; return fd; } return -1; From 01adce8299c8fff1c23e56b247d5836378de5b3f Mon Sep 17 00:00:00 2001 From: Sara Dickinson Date: Sat, 2 May 2015 18:08:45 +0100 Subject: [PATCH 09/12] Organise code in stub.c and add some utility methods. --- src/context.c | 112 ++-- src/request-internal.c | 2 +- src/stub.c | 1265 +++++++++++++++++++++------------------- src/types-internal.h | 4 +- 4 files changed, 740 insertions(+), 643 deletions(-) diff --git a/src/context.c b/src/context.c index 8dfde4c9..8dc571fa 100644 --- a/src/context.c +++ b/src/context.c @@ -703,6 +703,7 @@ set_os_defaults(struct getdns_context *context) upstream = &context->upstreams-> upstreams[context->upstreams->count++]; + fprintf(stderr, "[TLS]: OS: creating upstream %d, %p, with port %s with transport %d\n", (int)context->upstreams->count, (void*)upstream, port_str, base_transport); upstream_init(upstream, context->upstreams, result); upstream->dns_base_transport = base_transport; } @@ -1487,6 +1488,7 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, getdns_return_t r; size_t count = 0; size_t i; + //size_t upstreams_limit; getdns_upstreams *upstreams; char addrstr[1024], portstr[1024], *eos; struct addrinfo hints; @@ -1507,70 +1509,84 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, hints.ai_addr = NULL; hints.ai_next = NULL; - /* TODO[TLS]: Resize on the fly to avoid hardcoding this*/ upstreams = upstreams_create(context, count*3); + //upstreams_limit = count; for (i = 0; i < count; i++) { + getdns_dict *dict; + getdns_bindata *address_type; + getdns_bindata *address_data; + struct sockaddr_storage addr; + + getdns_bindata *scope_id; + getdns_upstream *upstream; + + if ((r = getdns_list_get_dict(upstream_list, i, &dict))) + goto error; + + if ((r = getdns_dict_get_bindata( + dict, "address_type",&address_type))) + goto error; + if (address_type->size < 4) + goto invalid_parameter; + if (strncmp((char *)address_type->data, "IPv4", 4) == 0) + addr.ss_family = AF_INET; + else if (strncmp((char *)address_type->data, "IPv6", 4) == 0) + addr.ss_family = AF_INET6; + else goto invalid_parameter; + + if ((r = getdns_dict_get_bindata( + dict, "address_data", &address_data))) + goto error; + if ((addr.ss_family == AF_INET && + address_data->size != 4) || + (addr.ss_family == AF_INET6 && + address_data->size != 16)) + goto invalid_parameter; + if (inet_ntop(addr.ss_family, address_data->data, + addrstr, 1024) == NULL) + goto invalid_parameter; + + if (getdns_dict_get_bindata(dict, "scope_id", &scope_id) == + GETDNS_RETURN_GOOD) { + if (strlen(addrstr) + scope_id->size > 1022) + goto invalid_parameter; + eos = &addrstr[strlen(addrstr)]; + *eos++ = '%'; + (void) memcpy(eos, scope_id->data, scope_id->size); + eos[scope_id->size] = 0; + } + /* Loop to create upstreams as needed*/ getdns_base_transport_t base_transport = GETDNS_BASE_TRANSPORT_MIN; for (; base_transport < GETDNS_BASE_TRANSPORT_MAX; base_transport++) { - getdns_dict *dict; - getdns_bindata *address_type; - getdns_bindata *address_data; uint32_t port; - getdns_bindata *scope_id; struct addrinfo *ai; - getdns_upstream *upstream; - port = getdns_port_array[base_transport]; if (port == GETDNS_PORT_ZERO) continue; - upstream = &upstreams->upstreams[upstreams->count]; - if ((r = getdns_list_get_dict(upstream_list, i, &dict))) - goto error; - - if ((r = getdns_dict_get_bindata( - dict, "address_type",&address_type))) - goto error; - if (address_type->size < 4) - goto invalid_parameter; - if (strncmp((char *)address_type->data, "IPv4", 4) == 0) - upstream->addr.ss_family = AF_INET; - else if (strncmp((char *)address_type->data, "IPv6", 4) == 0) - upstream->addr.ss_family = AF_INET6; - else goto invalid_parameter; - - if ((r = getdns_dict_get_bindata( - dict, "address_data", &address_data))) - goto error; - if ((upstream->addr.ss_family == AF_INET && - address_data->size != 4) || - (upstream->addr.ss_family == AF_INET6 && - address_data->size != 16)) - goto invalid_parameter; - if (inet_ntop(upstream->addr.ss_family, address_data->data, - addrstr, 1024) == NULL) - goto invalid_parameter; - - (void) getdns_dict_get_int(dict, "port", &port); + /* TODO[TLS]:Respect the user port for TCP and STARTTLS, but for + * now hardcode the TLS port */ + if (base_transport != GETDNS_BASE_TRANSPORT_TLS) + (void) getdns_dict_get_int(dict, "port", &port); (void) snprintf(portstr, 1024, "%d", (int)port); - if (getdns_dict_get_bindata(dict, "scope_id", &scope_id) == - GETDNS_RETURN_GOOD) { - if (strlen(addrstr) + scope_id->size > 1022) - goto invalid_parameter; - eos = &addrstr[strlen(addrstr)]; - *eos++ = '%'; - (void) memcpy(eos, scope_id->data, scope_id->size); - eos[scope_id->size] = 0; - } - if (getaddrinfo(addrstr, portstr, &hints, &ai)) goto invalid_parameter; /* TODO[TLS]: Should probably check that the upstream doesn't - * already exist (in case user has specified port explicitly)*/ + * already exist (in case user has specified TLS port explicitly and + * to prevent duplicates) */ + + /* TODO[TLS]: Grow array when needed. This causes a crash later.... + if (upstreams->count == upstreams_limit) + upstreams = upstreams_resize( + upstreams, (upstreams_limit *= 2)); */ + + upstream = &upstreams->upstreams[upstreams->count]; + upstream->addr.ss_family = addr.ss_family; upstream_init(upstream, upstreams, ai); + fprintf(stderr, "[TLS]: creating upstream %d, %p, with port %d with transport %d\n", (int)upstreams->count, (void*)upstream,(int)port, base_transport); upstream->dns_base_transport = base_transport; upstreams->count++; freeaddrinfo(ai); @@ -1912,8 +1928,8 @@ getdns_context_prepare_for_resolution(struct getdns_context *context, } /* Block use of TLS ONLY in recursive mode as it won't work */ /* TODO[TLS]: Check if TLS is the only option in the list*/ - if (context->resolution_type == GETDNS_RESOLUTION_RECURSING - && context->dns_transport == GETDNS_TRANSPORT_TLS_ONLY_KEEP_CONNECTIONS_OPEN) + if (context->resolution_type == GETDNS_RESOLUTION_RECURSING && + context->dns_transport == GETDNS_TRANSPORT_TLS_ONLY_KEEP_CONNECTIONS_OPEN) return GETDNS_RETURN_BAD_CONTEXT; if (context->resolution_type_set == context->resolution_type) diff --git a/src/request-internal.c b/src/request-internal.c index f83d0805..b4267022 100644 --- a/src/request-internal.c +++ b/src/request-internal.c @@ -91,7 +91,7 @@ network_req_init(getdns_network_req *net_req, getdns_dns_req *owner, net_req->fd = -1; for (i = 0; i < GETDNS_BASE_TRANSPORT_MAX; i++) net_req->dns_base_transports[i] = owner->context->dns_base_transports[i]; - net_req->dns_base_transport = net_req->dns_base_transports; + net_req->transport = 0; memset(&net_req->event, 0, sizeof(net_req->event)); memset(&net_req->tcp, 0, sizeof(net_req->tcp)); net_req->query_id = 0; diff --git a/src/stub.c b/src/stub.c index e4dfc910..c22a68f4 100755 --- a/src/stub.c +++ b/src/stub.c @@ -58,7 +58,13 @@ static void upstream_schedule_netreq(getdns_upstream *upstream, getdns_network_req *netreq); static void netreq_upstream_read_cb(void *userarg); static void netreq_upstream_write_cb(void *userarg); -static int fallback_on_write(getdns_network_req *netreq); +static int fallback_on_write(getdns_network_req *netreq); + +static void stub_tcp_write_cb(void *userarg); + +/*****************************/ +/* General utility functions */ +/*****************************/ static void rollover_secret() @@ -231,7 +237,7 @@ match_and_process_server_cookie( } static int -create_starttls_request(getdns_dns_req *dnsreq, getdns_upstream *upstream, +create_starttls_request(getdns_dns_req *dnsreq, getdns_upstream *upstream, getdns_eventloop *loop) { getdns_return_t r = GETDNS_RETURN_GOOD; @@ -246,14 +252,13 @@ create_starttls_request(getdns_dns_req *dnsreq, getdns_upstream *upstream, } upstream->starttls_req = dns_req_new(dnsreq->context, loop, "STARTTLS", GETDNS_RRTYPE_TXT, extensions); - /*TODO[STARTTLS]: TO BIT*/ + /*TODO[TLS]: TO BIT*/ if (upstream->starttls_req == NULL) return 0; getdns_dict_destroy(extensions); upstream->starttls_req->netreqs[0]->upstream = upstream; return 1; - } static int @@ -272,8 +277,8 @@ dname_equal(uint8_t *s1, uint8_t *s2) } static int -is_starttls_response(getdns_network_req *netreq) { - +is_starttls_response(getdns_network_req *netreq) +{ priv_getdns_rr_iter rr_iter_storage, *rr_iter; priv_getdns_rdf_iter rdf_iter_storage, *rdf_iter; uint16_t rr_type; @@ -286,7 +291,8 @@ is_starttls_response(getdns_network_req *netreq) { /* Servers that are not STARTTLS aware will refuse the CH query*/ if (LDNS_RCODE_NOERROR != GLDNS_RCODE_WIRE(netreq->response)) { - fprintf(stderr, "[STARTTLS] STARTTLS response had error %d\n", GLDNS_RCODE_WIRE(netreq->response)); + fprintf(stderr, "[TLS] STARTTLS response had error %d\n", + GLDNS_RCODE_WIRE(netreq->response)); return 0; } @@ -319,10 +325,12 @@ is_starttls_response(getdns_network_req *netreq) { starttls_name = priv_getdns_rdf_if_or_as_decompressed( rdf_iter,starttls_name_space,&starttls_name_len); if (dname_equal(starttls_name, owner_name)) { - fprintf(stderr, "[STARTTLS] STARTTLS response received :%s:\n", (char*)starttls_name); + fprintf(stderr, "[TLS] STARTTLS response received :%s:\n", + (char*)starttls_name); return 1; } else { - fprintf(stderr, "[STARTTLS] NO_TLS response received :%s:\n", (char*)starttls_name); + fprintf(stderr, "[TLS] NO_TLS response received :%s:\n", + (char*)starttls_name); return 0; } continue; @@ -330,8 +338,6 @@ is_starttls_response(getdns_network_req *netreq) { return 0; } - - /** best effort to set nonblocking */ static void getdns_sock_nonblock(int sockfd) @@ -352,6 +358,36 @@ getdns_sock_nonblock(int sockfd) #endif } +static int +tcp_connect(getdns_upstream *upstream, getdns_base_transport_t transport) +{ + + int fd = -1; + if ((fd = socket(upstream->addr.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) + return -1; + + getdns_sock_nonblock(fd); +#ifdef USE_TCP_FASTOPEN + /* Leave the connect to the later call to sendto() if using TCP*/ + if (transport == GETDNS_BASE_TRANSPORT_TCP || + transport == GETDNS_BASE_TRANSPORT_TCP_SINGLE || + transport == GETDNS_BASE_TRANSPORT_STARTTLS) + return fd; +#endif + if (connect(fd, (struct sockaddr *)&upstream->addr, + upstream->addr_len) == -1) { + if (errno != EINPROGRESS) { + close(fd); + return -1; + } + } + return fd; +} + +/**************************/ +/* Error/cleanup functions*/ +/**************************/ + static void stub_next_upstream(getdns_network_req *netreq) { @@ -444,10 +480,10 @@ upstream_erred(getdns_upstream *upstream) } close(upstream->fd); upstream->fd = -1; - /*TODO[TLS]: Upstream errors don't trigger the user callback....*/ } + static void -message_erred(getdns_network_req *netreq) +message_erred(getdns_network_req *netreq) { stub_cleanup(netreq); netreq->state = NET_REQ_FINISHED; @@ -483,6 +519,14 @@ stub_timeout_cb(void *userarg) fprintf(stderr,"[TLS]: TIMEOUT(stub_timeout_cb)\n"); 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_response(netreq)) { + netreq->upstream->tls_hs_state = GETDNS_HS_FAILED; + stub_next_upstream(netreq); + stub_cleanup(netreq); + } stub_next_upstream(netreq); stub_cleanup(netreq); @@ -490,7 +534,471 @@ stub_timeout_cb(void *userarg) (void) getdns_context_request_timed_out(netreq->owner); } -static void stub_tcp_write_cb(void *userarg); +/****************************/ +/* 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, tcp->read_pos, tcp->to_read, 0); + if (read == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return STUB_TCP_AGAIN; + else + return STUB_TCP_ERROR; + } else if (read == 0) { + /* Remote end closed the socket */ + /* TODO: Try to reconnect */ + return STUB_TCP_ERROR; + } + tcp->to_read -= read; + tcp->read_pos += read; + + if ((int)tcp->to_read > 0) + return STUB_TCP_AGAIN; + + 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_AGAIN; + } + return GLDNS_ID_WIRE(tcp->read_buf); +} + +/* stub_tcp_write(fd, tcp, netreq) + * will return STUB_TCP_AGAIN when we need to come back again, + * STUB_TCP_ERROR on error and a query_id on successfull sent. + */ +static int +stub_tcp_write(int fd, getdns_tcp_state *tcp, getdns_network_req *netreq) +{ + + size_t pkt_len = netreq->response - netreq->query; + ssize_t written; + uint16_t query_id; + intptr_t query_id_intptr; + + /* Do we have remaining data that we could not write before? */ + if (! tcp->write_buf) { + /* No, this is an initial write. Try to send + */ + + /* Not keeping connections open? Then the first random number + * will do as the query id. + * + * Otherwise 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. + */ + if ((netreq->dns_base_transports[netreq->transport] == + GETDNS_BASE_TRANSPORT_TCP_SINGLE) || + (netreq->dns_base_transports[netreq->transport] == + GETDNS_BASE_TRANSPORT_UDP)) + query_id = arc4random(); + else 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)); + + GLDNS_ID_SET(netreq->query, query_id); + if (netreq->opt) { + /* no limits on the max udp payload size with tcp */ + gldns_write_uint16(netreq->opt + 3, 65535); + + if (netreq->owner->edns_cookies) { + netreq->response = attach_edns_cookie( + netreq->upstream, netreq->opt); + pkt_len = netreq->response - netreq->query; + gldns_write_uint16(netreq->query - 2, pkt_len); + } + } + /* We have an initialized packet buffer. + * Lets see how much of it we can write + */ +#ifdef USE_TCP_FASTOPEN + /* We use sendto() here which will do both a connect and send */ + 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 && errno == EISCONN) + written = write(fd, netreq->query - 2, pkt_len + 2); + + if ((written == -1 && (errno == EAGAIN || + errno == EWOULDBLOCK || + /* Add the error case where the connection is in progress which is when + a cookie is not available (e.g. when doing the first request to an + upstream). We must let the handshake complete since non-blocking. */ + errno == EINPROGRESS)) || + written < pkt_len + 2) { +#else + written = write(fd, netreq->query - 2, pkt_len + 2); + if ((written == -1 && (errno == EAGAIN || + errno == EWOULDBLOCK)) || + written < pkt_len + 2) { +#endif + /* We couldn't write the whole packet. + * We have to return with STUB_TCP_AGAIN. + * 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 STUB_TCP_AGAIN; + + } else if (written == -1) + 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 = write(fd, tcp->write_buf + tcp->written, + tcp->write_buf_len - tcp->written); + if (written == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return STUB_TCP_AGAIN; + else + return STUB_TCP_ERROR; + } + tcp->written += written; + if (tcp->written < tcp->write_buf_len) + /* Still more to send */ + return STUB_TCP_AGAIN; + + 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->dns_base_transports[netreq->transport] == + GETDNS_BASE_TRANSPORT_TLS || + netreq->dns_base_transports[netreq->transport] == + GETDNS_BASE_TRANSPORT_STARTTLS) ? + 1 : 0; +} + +static int +tls_should_write(getdns_upstream *upstream) +{ + /* Should messages be written on TLS upstream. Remember that for STARTTLS + * the first message should got over TCP as the handshake isn't started yet.*/ + return ((upstream->dns_base_transport == GETDNS_BASE_TRANSPORT_TLS || + upstream->dns_base_transport == GETDNS_BASE_TRANSPORT_STARTTLS) && + upstream->tls_hs_state != GETDNS_HS_NONE) ? 1 : 0; +} + +static int +tls_should_read(getdns_upstream *upstream) +{ + return ((upstream->dns_base_transport == GETDNS_BASE_TRANSPORT_TLS || + upstream->dns_base_transport == GETDNS_BASE_TRANSPORT_STARTTLS) && + !(upstream->tls_hs_state == GETDNS_HS_FAILED || + upstream->tls_hs_state == GETDNS_HS_NONE)) ? 1 : 0; +} + +static int +tls_failed(getdns_upstream *upstream) +{ + /* No messages should be scheduled onto an upstream in this state */ + return ((upstream->dns_base_transport == GETDNS_BASE_TRANSPORT_TLS || + upstream->dns_base_transport == GETDNS_BASE_TRANSPORT_STARTTLS) && + upstream->tls_hs_state == GETDNS_HS_FAILED) ? 1: 0; +} + +static SSL* +tls_create_object(getdns_context *context, int fd) +{ + /* Create SSL instance */ + if (context->tls_ctx == NULL) + return NULL; + SSL* ssl = SSL_new(context->tls_ctx); + if(!ssl) + return NULL; + /* Connect the SSL object with a file descriptor */ + if(!SSL_set_fd(ssl,fd)) { + SSL_free(ssl); + return NULL; + } + SSL_set_connect_state(ssl); + (void) SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); + return ssl; +} + +static int +tls_do_handshake(getdns_upstream *upstream) +{ + fprintf(stderr,"[TLS]: TLS(tls_do_handshake)\n"); + + int r; + int want; + ERR_clear_error(); + while ((r = SSL_do_handshake(upstream->tls_obj)) != 1) + { + want = SSL_get_error(upstream->tls_obj, r); + switch (want) { + case SSL_ERROR_WANT_READ: + fprintf(stderr,"[TLS]: SSL_ERROR_WANT_READ\n"); + upstream->event.read_cb = upstream_read_cb; + upstream->event.write_cb = NULL; + GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); + GETDNS_SCHEDULE_EVENT(upstream->loop, + upstream->fd, TIMEOUT_FOREVER, &upstream->event); + upstream->tls_hs_state = GETDNS_HS_READ; + return STUB_TCP_AGAIN; + case SSL_ERROR_WANT_WRITE: + fprintf(stderr,"[TLS]: SSL_ERROR_WANT_WRITE\n"); + upstream->event.read_cb = NULL; + upstream->event.write_cb = upstream_write_cb; + GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); + GETDNS_SCHEDULE_EVENT(upstream->loop, + upstream->fd, TIMEOUT_FOREVER, &upstream->event); + upstream->tls_hs_state = GETDNS_HS_WRITE; + return STUB_TCP_AGAIN; + default: + SSL_free(upstream->tls_obj); + upstream->tls_obj = NULL; + upstream->tls_hs_state = GETDNS_HS_FAILED; + return STUB_TLS_SETUP_ERROR; + } + } + upstream->tls_hs_state = GETDNS_HS_DONE; + upstream->event.read_cb = NULL; + upstream->event.write_cb = upstream_write_cb; + GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); + GETDNS_SCHEDULE_EVENT(upstream->loop, + upstream->fd, TIMEOUT_FOREVER, &upstream->event); + return 0; +} + +static int +tls_connected(getdns_upstream* upstream) +{ + /* Already have a connection*/ + if (upstream->tls_hs_state == GETDNS_HS_DONE && + (upstream->tls_obj != NULL) && (upstream->fd != -1)) + 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_TLS_SETUP_ERROR; + + /* Lets make sure the connection is up before we try a handshake*/ + int error = 0; + socklen_t len = (socklen_t)sizeof(error); + /* TODO: This doesn't handle the case where the far end doesn't do a reset + * as is the case with e.g. 8.8.8.8. For that case the timeout kicks in + * and the user callback fails the message without the chance to fallback.*/ + getsockopt(upstream->fd, SOL_SOCKET, SO_ERROR, (void*)&error, &len); + if (error == EINPROGRESS || error == EWOULDBLOCK) + return STUB_TCP_AGAIN; /* try again */ + else if (error != 0) { + + fprintf(stderr,"[TLS]: TLS(tls_connected): died gettting connection\n"); + SSL_free(upstream->tls_obj); + upstream->tls_obj = NULL; + upstream->tls_hs_state = GETDNS_HS_FAILED; + return STUB_TLS_SETUP_ERROR; + } + + 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) +{ + ssize_t read; + uint8_t *buf; + size_t buf_size; + SSL* 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 */ + } + + ERR_clear_error(); + read = SSL_read(tls_obj, tcp->read_pos, tcp->to_read); + if (read <= 0) { + /* TODO[TLS]: Handle SSL_ERROR_WANT_WRITE which means handshake + renegotiation. Need to keep handshake state to do that.*/ + int want = SSL_get_error(tls_obj, read); + if (want == SSL_ERROR_WANT_READ) { + return STUB_TCP_AGAIN; /* read more later */ + } else + return STUB_TCP_ERROR; + } + tcp->to_read -= read; + tcp->read_pos += read; + + if ((int)tcp->to_read > 0) + return STUB_TCP_AGAIN; + + 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; + read = SSL_read(tls_obj, tcp->read_pos, tcp->to_read); + if (read <= 0) { + /* TODO[TLS]: Handle SSL_ERROR_WANT_WRITE which means handshake + renegotiation. Need to keep handshake state to do that.*/ + int want = SSL_get_error(tls_obj, read); + if (want == SSL_ERROR_WANT_READ) { + return STUB_TCP_AGAIN; /* read more later */ + } else + return STUB_TCP_ERROR; + } + tcp->to_read -= read; + tcp->read_pos += read; + if ((int)tcp->to_read > 0) + return STUB_TCP_AGAIN; + } + 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 = netreq->response - netreq->query; + ssize_t written; + uint16_t query_id; + intptr_t query_id_intptr; + SSL* tls_obj = upstream->tls_obj; + + int q = tls_connected(upstream); + if (q != 0) + return q; + + /* 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 = ldns_get_random(); + 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)); + + GLDNS_ID_SET(netreq->query, query_id); + if (netreq->opt) + /* no limits on the max udp payload size with tcp */ + gldns_write_uint16(netreq->opt + 3, 65535); + + /* We have an initialized packet buffer. + * Lets see how much of it we can write */ + + /* TODO[TLS]: Handle error cases, partial writes, renegotiation etc. */ + ERR_clear_error(); + written = SSL_write(tls_obj, netreq->query - 2, pkt_len + 2); + if (written <= 0) + return STUB_TCP_ERROR; + + /* We were able to write everything! Start reading. */ + return (int) query_id; + + } + + return STUB_TCP_ERROR; +} + +/**************************/ +/* UDP callback functions */ +/**************************/ + static void stub_udp_read_cb(void *userarg) { @@ -594,133 +1102,9 @@ stub_udp_write_cb(void *userarg) stub_udp_read_cb, NULL, stub_timeout_cb)); } -static int -transport_valid(struct getdns_upstream *upstream, getdns_base_transport_t transport) { - /* For single shot transports, use only the TCP upstream. */ - if (transport == GETDNS_BASE_TRANSPORT_UDP || - transport == GETDNS_BASE_TRANSPORT_TCP_SINGLE) { - if (upstream->dns_base_transport == GETDNS_BASE_TRANSPORT_TCP) - return 1; - else - return 0; - } - /* Allow TCP messages to be sent on a STARTTLS upstream that hasn't upgraded - * to avoid opening a new connection if one is aleady open. */ - if (transport == GETDNS_BASE_TRANSPORT_TCP && - upstream->dns_base_transport == GETDNS_BASE_TRANSPORT_STARTTLS && - upstream->tls_hs_state == GETDNS_HS_FAILED) - return 1; - /* Otherwise, transport must match */ - if (upstream->dns_base_transport != transport) - return 0; - /* But don't use if upgrade failed for (START)TLS*/ - if ((transport == GETDNS_BASE_TRANSPORT_TLS || - transport == GETDNS_BASE_TRANSPORT_STARTTLS) - && upstream->tls_hs_state == GETDNS_HS_FAILED) - return 0; - return 1; -} - -static getdns_upstream * -pick_upstream(getdns_network_req *netreq, getdns_base_transport_t transport) -{ - getdns_upstream *upstream; - getdns_upstreams *upstreams = netreq->owner->upstreams; - size_t i; - - if (!upstreams->count) - return NULL; - - for (i = 0; i < upstreams->count; i++) - if (upstreams->upstreams[i].to_retry <= 0) - upstreams->upstreams[i].to_retry++; - - i = upstreams->current; - do { - if (upstreams->upstreams[i].to_retry > 0 && - transport_valid(&upstreams->upstreams[i], transport)) { - upstreams->current = i; - return &upstreams->upstreams[i]; - } - if (++i > upstreams->count) - i = 0; - } while (i != upstreams->current); - - upstream = upstreams->upstreams; - for (i = 1; i < upstreams->count; i++) - if (upstreams->upstreams[i].back_off < upstream->back_off && - transport_valid(&upstreams->upstreams[i], transport)) - upstream = &upstreams->upstreams[i]; - - /* Need to check again that the transport is valid */ - if (!transport_valid(upstream, transport)) - return NULL; - upstream->back_off++; - upstream->to_retry = 1; - upstreams->current = upstream - upstreams->upstreams; - return upstream; -} - -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, tcp->read_pos, tcp->to_read, 0); - if (read == -1) { - if (errno == EAGAIN || errno == EWOULDBLOCK) - return STUB_TCP_AGAIN; - else - return STUB_TCP_ERROR; - } else if (read == 0) { - /* Remote end closed the socket */ - /* TODO: Try to reconnect */ - return STUB_TCP_ERROR; - } - tcp->to_read -= read; - tcp->read_pos += read; - - if ((int)tcp->to_read > 0) - return STUB_TCP_AGAIN; - - 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_AGAIN; - } - return GLDNS_ID_WIRE(tcp->read_buf); -} +/**************************/ +/* TCP callback functions*/ +/**************************/ static void stub_tcp_read_cb(void *userarg) @@ -765,192 +1149,35 @@ stub_tcp_read_cb(void *userarg) } } -static SSL* -create_tls_object(getdns_context *context, int fd) +static void +stub_tcp_write_cb(void *userarg) { - /* Create SSL instance */ - if (context->tls_ctx == NULL) - return NULL; - SSL* ssl = SSL_new(context->tls_ctx); - if(!ssl) - return NULL; - /* Connect the SSL object with a file descriptor */ - if(!SSL_set_fd(ssl,fd)) { - SSL_free(ssl); - return NULL; + getdns_network_req *netreq = (getdns_network_req *)userarg; + getdns_dns_req *dnsreq = netreq->owner; + int q; + + switch ((q = stub_tcp_write(netreq->fd, &netreq->tcp, netreq))) { + case STUB_TCP_AGAIN: + return; + + case STUB_TCP_ERROR: + stub_erred(netreq); + return; + + default: + netreq->query_id = (uint16_t) q; + GETDNS_CLEAR_EVENT(dnsreq->loop, &netreq->event); + GETDNS_SCHEDULE_EVENT( + dnsreq->loop, netreq->fd, dnsreq->context->timeout, + getdns_eventloop_event_init(&netreq->event, netreq, + stub_tcp_read_cb, NULL, stub_timeout_cb)); + return; } - SSL_set_connect_state(ssl); - (void) SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); - return ssl; } -static int -do_tls_handshake(getdns_upstream *upstream) -{ - - fprintf(stderr,"[TLS]: TLS(do_tls_handshake)\n"); - - int r; - int want; - ERR_clear_error(); - while ((r = SSL_do_handshake(upstream->tls_obj)) != 1) - { - want = SSL_get_error(upstream->tls_obj, r); - switch (want) { - case SSL_ERROR_WANT_READ: - fprintf(stderr,"[TLS]: SSL_ERROR_WANT_READ\n"); - upstream->event.read_cb = upstream_read_cb; - upstream->event.write_cb = NULL; - GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); - GETDNS_SCHEDULE_EVENT(upstream->loop, - upstream->fd, TIMEOUT_FOREVER, &upstream->event); - upstream->tls_hs_state = GETDNS_HS_READ; - return STUB_TCP_AGAIN; - case SSL_ERROR_WANT_WRITE: - fprintf(stderr,"[TLS]: SSL_ERROR_WANT_WRITE\n"); - upstream->event.read_cb = NULL; - upstream->event.write_cb = upstream_write_cb; - GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); - GETDNS_SCHEDULE_EVENT(upstream->loop, - upstream->fd, TIMEOUT_FOREVER, &upstream->event); - upstream->tls_hs_state = GETDNS_HS_WRITE; - return STUB_TCP_AGAIN; - default: - SSL_free(upstream->tls_obj); - upstream->tls_obj = NULL; - upstream->tls_hs_state = GETDNS_HS_FAILED; - return STUB_TLS_SETUP_ERROR; - } - } - upstream->tls_hs_state = GETDNS_HS_DONE; - upstream->event.read_cb = NULL; - upstream->event.write_cb = upstream_write_cb; - GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); - GETDNS_SCHEDULE_EVENT(upstream->loop, - upstream->fd, TIMEOUT_FOREVER, &upstream->event); - return 0; -} - -static int -tls_handshake_active(getdns_tls_hs_state_t hs_state) -{ - return (hs_state == GETDNS_HS_FAILED || - hs_state == GETDNS_HS_NONE) ? 0 : 1; -} - -static int -check_tls(getdns_upstream* upstream) -{ - /* Already have a connection*/ - if (upstream->tls_hs_state == GETDNS_HS_DONE && - (upstream->tls_obj != NULL) && (upstream->fd != -1)) - return 0; - - /* This upstream can't be used, so let the fallback code take care of things */ - if (upstream->tls_hs_state == GETDNS_HS_FAILED) - return STUB_TLS_SETUP_ERROR; - - /* Lets make sure the connection is up before we try a handshake*/ - int error = 0; - socklen_t len = (socklen_t)sizeof(error); - /* TODO: This doesn't handle the case where the far end doesn't do a reset - * as is the case with e.g. 8.8.8.8. For that case the timeout kicks in - * and the user callback fails the message without the chance to fallback.*/ - getsockopt(upstream->fd, SOL_SOCKET, SO_ERROR, (void*)&error, &len); - if (error == EINPROGRESS || error == EWOULDBLOCK) - return STUB_TCP_AGAIN; /* try again */ - else if (error != 0) { - - fprintf(stderr,"[TLS]: TLS(check_tls): died gettting connection\n"); - SSL_free(upstream->tls_obj); - upstream->tls_obj = NULL; - upstream->tls_hs_state = GETDNS_HS_FAILED; - return STUB_TLS_SETUP_ERROR; - } - - return do_tls_handshake(upstream); -} - -static int -stub_tls_read(getdns_upstream *upstream, getdns_tcp_state *tcp, struct mem_funcs *mf) -{ - ssize_t read; - uint8_t *buf; - size_t buf_size; - SSL* tls_obj = upstream->tls_obj; - - int q = check_tls(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 */ - } - - ERR_clear_error(); - read = SSL_read(tls_obj, tcp->read_pos, tcp->to_read); - if (read <= 0) { - /* TODO[TLS]: Handle SSL_ERROR_WANT_WRITE which means handshake - renegotiation. Need to keep handshake state to do that.*/ - int want = SSL_get_error(tls_obj, read); - if (want == SSL_ERROR_WANT_READ) { - return STUB_TCP_AGAIN; /* read more later */ - } else - return STUB_TCP_ERROR; - } - tcp->to_read -= read; - tcp->read_pos += read; - - if ((int)tcp->to_read > 0) - return STUB_TCP_AGAIN; - - 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; - read = SSL_read(tls_obj, tcp->read_pos, tcp->to_read); - if (read <= 0) { - /* TODO[TLS]: Handle SSL_ERROR_WANT_WRITE which means handshake - renegotiation. Need to keep handshake state to do that.*/ - int want = SSL_get_error(tls_obj, read); - if (want == SSL_ERROR_WANT_READ) { - return STUB_TCP_AGAIN; /* read more later */ - } else - return STUB_TCP_ERROR; - } - tcp->to_read -= read; - tcp->read_pos += read; - if ((int)tcp->to_read > 0) - return STUB_TCP_AGAIN; - } - return GLDNS_ID_WIRE(tcp->read_buf); -} +/**************************/ +/* Upstream callback functions*/ +/**************************/ static void upstream_read_cb(void *userarg) @@ -966,9 +1193,7 @@ upstream_read_cb(void *userarg) fprintf(stderr,"[TLS]: **********CALLBACK***********\n"); fprintf(stderr,"[TLS]: READ(upstream_read_cb): on %d\n", upstream->fd); - if (upstream->dns_base_transport == GETDNS_BASE_TRANSPORT_TLS || - (upstream->dns_base_transport == GETDNS_BASE_TRANSPORT_STARTTLS && - tls_handshake_active(upstream->tls_hs_state))) + if (tls_should_read(upstream)) q = stub_tls_read(upstream, &upstream->tcp, &upstream->upstreams->mf); else @@ -1030,9 +1255,9 @@ upstream_read_cb(void *userarg) if (netreq->owner == upstream->starttls_req) { dnsreq = netreq->owner; - fprintf(stderr, "[STARTTLS] processing STARTTLS response!\n"); + fprintf(stderr, "[TLS]: processing STARTTLS response!\n"); if (is_starttls_response(netreq)) { - upstream->tls_obj = create_tls_object(dnsreq->context, upstream->fd); + upstream->tls_obj = tls_create_object(dnsreq->context, upstream->fd); if (upstream->tls_obj == NULL) { fprintf(stderr,"[TLS]: could not create tls object\n"); upstream->tls_hs_state = GETDNS_HS_FAILED; @@ -1045,12 +1270,13 @@ upstream_read_cb(void *userarg) // Now reschedule the writes on this connection upstream->event.write_cb = upstream_write_cb; - fprintf(stderr, "[STARTTLS] method: upstream_schedule_netreq -> re-instating writes\n"); + fprintf(stderr, "[TLS] method: upstream_schedule_netreq ->" + "re-instating writes\n"); GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); GETDNS_SCHEDULE_EVENT(upstream->loop, upstream->fd, TIMEOUT_FOREVER, &upstream->event); } else { - fprintf(stderr, "[STARTTLS] processing standard response....\n"); + fprintf(stderr, "[TLS]: processing standard response....\n"); priv_getdns_check_dns_req_complete(netreq->owner); } @@ -1072,201 +1298,6 @@ netreq_upstream_read_cb(void *userarg) upstream_read_cb(((getdns_network_req *)userarg)->upstream); } -/* stub_tcp_write(fd, tcp, netreq) - * will return STUB_TCP_AGAIN when we need to come back again, - * STUB_TCP_ERROR on error and a query_id on successfull sent. - */ -static int -stub_tcp_write(int fd, getdns_tcp_state *tcp, getdns_network_req *netreq) -{ - - size_t pkt_len = netreq->response - netreq->query; - ssize_t written; - uint16_t query_id; - intptr_t query_id_intptr; - - /* Do we have remaining data that we could not write before? */ - if (! tcp->write_buf) { - /* No, this is an initial write. Try to send - */ - - /* Not keeping connections open? Then the first random number - * will do as the query id. - * - * Otherwise 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. - */ - if ((*netreq->dns_base_transport == GETDNS_BASE_TRANSPORT_TCP_SINGLE) || - (*netreq->dns_base_transport == GETDNS_BASE_TRANSPORT_UDP)) - query_id = arc4random(); - else 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)); - - GLDNS_ID_SET(netreq->query, query_id); - if (netreq->opt) { - /* no limits on the max udp payload size with tcp */ - gldns_write_uint16(netreq->opt + 3, 65535); - - if (netreq->owner->edns_cookies) { - netreq->response = attach_edns_cookie( - netreq->upstream, netreq->opt); - pkt_len = netreq->response - netreq->query; - gldns_write_uint16(netreq->query - 2, pkt_len); - } - } - /* We have an initialized packet buffer. - * Lets see how much of it we can write - */ -#ifdef USE_TCP_FASTOPEN - /* We use sendto() here which will do both a connect and send */ - 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 && errno == EISCONN) - written = write(fd, netreq->query - 2, pkt_len + 2); - - if ((written == -1 && (errno == EAGAIN || - errno == EWOULDBLOCK || - /* Add the error case where the connection is in progress which is when - a cookie is not available (e.g. when doing the first request to an - upstream). We must let the handshake complete since non-blocking. */ - errno == EINPROGRESS)) || - written < pkt_len + 2) { -#else - written = write(fd, netreq->query - 2, pkt_len + 2); - if ((written == -1 && (errno == EAGAIN || - errno == EWOULDBLOCK)) || - written < pkt_len + 2) { -#endif - /* We couldn't write the whole packet. - * We have to return with STUB_TCP_AGAIN. - * 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 STUB_TCP_AGAIN; - - } else if (written == -1) - 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 = write(fd, tcp->write_buf + tcp->written, - tcp->write_buf_len - tcp->written); - if (written == -1) { - if (errno == EAGAIN || errno == EWOULDBLOCK) - return STUB_TCP_AGAIN; - else - return STUB_TCP_ERROR; - } - tcp->written += written; - if (tcp->written < tcp->write_buf_len) - /* Still more to send */ - return STUB_TCP_AGAIN; - - query_id = (int)GLDNS_ID_WIRE(tcp->write_buf + 2); - /* Done. Start reading */ - tcp->write_buf = NULL; - return query_id; - - } /* if (! tcp->write_buf) */ -} - -static void -stub_tcp_write_cb(void *userarg) -{ - getdns_network_req *netreq = (getdns_network_req *)userarg; - getdns_dns_req *dnsreq = netreq->owner; - int q; - - switch ((q = stub_tcp_write(netreq->fd, &netreq->tcp, netreq))) { - case STUB_TCP_AGAIN: - return; - - case STUB_TCP_ERROR: - stub_erred(netreq); - return; - - default: - netreq->query_id = (uint16_t) q; - GETDNS_CLEAR_EVENT(dnsreq->loop, &netreq->event); - GETDNS_SCHEDULE_EVENT( - dnsreq->loop, netreq->fd, dnsreq->context->timeout, - getdns_eventloop_event_init(&netreq->event, netreq, - stub_tcp_read_cb, NULL, stub_timeout_cb)); - return; - } -} - -static int -stub_tls_write(getdns_upstream *upstream, getdns_tcp_state *tcp, - getdns_network_req *netreq) -{ - size_t pkt_len = netreq->response - netreq->query; - ssize_t written; - uint16_t query_id; - intptr_t query_id_intptr; - SSL* tls_obj = upstream->tls_obj; - - int q = check_tls(upstream); - if (q != 0) - return q; - - /* 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 = ldns_get_random(); - 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)); - - GLDNS_ID_SET(netreq->query, query_id); - if (netreq->opt) - /* no limits on the max udp payload size with tcp */ - gldns_write_uint16(netreq->opt + 3, 65535); - - /* We have an initialized packet buffer. - * Lets see how much of it we can write */ - - // TODO[TLS]: Handle error cases, partial writes, renegotiation etc. - ERR_clear_error(); - written = SSL_write(tls_obj, netreq->query - 2, pkt_len + 2); - if (written <= 0) - return STUB_TCP_ERROR; - - /* We were able to write everything! Start reading. */ - return (int) query_id; - - } - - return STUB_TCP_ERROR; -} - - static void upstream_write_cb(void *userarg) { @@ -1275,14 +1306,11 @@ upstream_write_cb(void *userarg) getdns_dns_req *dnsreq = netreq->owner; int q; - fprintf(stderr,"[TLS]: **********CALLBACK***********\n"); fprintf(stderr,"[TLS]: WRITE(upstream_write_cb): upstream fd %d, SEND" " netreq %p \n", upstream->fd, netreq); - if (*netreq->dns_base_transport == GETDNS_BASE_TRANSPORT_TLS || - (*netreq->dns_base_transport == GETDNS_BASE_TRANSPORT_STARTTLS && - upstream->tls_hs_state != GETDNS_HS_NONE)) + if (tls_requested(netreq) && tls_should_write(upstream)) q = stub_tls_write(upstream, &upstream->tcp, netreq); else q = stub_tcp_write(upstream->fd, &upstream->tcp, netreq); @@ -1330,7 +1358,7 @@ upstream_write_cb(void *userarg) if (upstream->starttls_req) { /* Now deschedule any further writes on this connection until we get the STARTTLS answer*/ - fprintf(stderr, "[STARTTLS] method: upstream_write_cb -> STARTTTLS -" + fprintf(stderr, "[TLS] method: upstream_write_cb -> STARTTTLS -" "clearing upstream->event.write_cb\n"); upstream->event.write_cb = NULL; GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); @@ -1358,57 +1386,78 @@ netreq_upstream_write_cb(void *userarg) upstream_write_cb(((getdns_network_req *)userarg)->upstream); } -static void -upstream_schedule_netreq(getdns_upstream *upstream, getdns_network_req *netreq) -{ - /* We have a connected socket and a global event loop */ - assert(upstream->fd >= 0); - assert(upstream->loop); - - - fprintf(stderr,"[TLS]: SCHEDULE(upstream_schedule_netreq): fd %d\n", upstream->fd); - - /* Append netreq to write_queue */ - if (!upstream->write_queue) { - upstream->write_queue = upstream->write_queue_last = netreq; - upstream->event.write_cb = upstream_write_cb; - GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); - GETDNS_SCHEDULE_EVENT(upstream->loop, - upstream->fd, TIMEOUT_FOREVER, &upstream->event); - } else { - upstream->write_queue_last->write_queue_tail = netreq; - upstream->write_queue_last = netreq; - } -} +/*****************************/ +/* Upstream utility functions*/ +/*****************************/ static int -tcp_connect(getdns_upstream *upstream, getdns_base_transport_t transport) +upstream_transport_valid(getdns_upstream *upstream, + getdns_base_transport_t transport) { - - int fd = -1; - if ((fd = socket(upstream->addr.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) - return -1; - - getdns_sock_nonblock(fd); -#ifdef USE_TCP_FASTOPEN - /* Leave the connect to the later call to sendto() if using TCP*/ - if (transport == GETDNS_BASE_TRANSPORT_TCP || - transport == GETDNS_BASE_TRANSPORT_TCP_SINGLE || - transport == GETDNS_BASE_TRANSPORT_STARTTLS) - return fd; -#endif - if (connect(fd, (struct sockaddr *)&upstream->addr, - upstream->addr_len) == -1) { - if (errno != EINPROGRESS) { - close(fd); - return -1; - } + /* For single shot transports, use only the TCP upstream. */ + fprintf(stderr,"[TLS]: upstream_transport_valid checking upstream %p against transport %d\n",(void*)upstream, transport); + if (transport == GETDNS_BASE_TRANSPORT_UDP || + transport == GETDNS_BASE_TRANSPORT_TCP_SINGLE) + return (upstream->dns_base_transport == GETDNS_BASE_TRANSPORT_TCP ? 1:0); + /* Allow TCP messages to be sent on a STARTTLS upstream that hasn't upgraded + * to avoid opening a new connection if one is aleady open. */ + if (transport == GETDNS_BASE_TRANSPORT_TCP && + upstream->dns_base_transport == GETDNS_BASE_TRANSPORT_STARTTLS && + upstream->tls_hs_state == GETDNS_HS_FAILED) + return 1; + /* Otherwise, transport must match, and not have failed */ + if (upstream->dns_base_transport != transport) + return 0; + if (tls_failed(upstream)) { + fprintf(stderr,"[TLS]: tls_failed\n"); + return 0; } - return fd; + return 1; } +static getdns_upstream * +upstream_select(getdns_network_req *netreq, getdns_base_transport_t transport) +{ + getdns_upstream *upstream; + getdns_upstreams *upstreams = netreq->owner->upstreams; + size_t i; + + if (!upstreams->count) + return NULL; + + for (i = 0; i < upstreams->count; i++) + if (upstreams->upstreams[i].to_retry <= 0) + upstreams->upstreams[i].to_retry++; + + i = upstreams->current; + do { + if (upstreams->upstreams[i].to_retry > 0 && + upstream_transport_valid(&upstreams->upstreams[i], transport)) { + upstreams->current = i; + return &upstreams->upstreams[i]; + } + if (++i > upstreams->count) + i = 0; + } while (i != upstreams->current); + + upstream = upstreams->upstreams; + for (i = 0; i < upstreams->count; i++) + if (upstreams->upstreams[i].back_off < upstream->back_off && + upstream_transport_valid(&upstreams->upstreams[i], transport)) + upstream = &upstreams->upstreams[i]; + + /* Need to check again that the transport is valid */ + if (!upstream_transport_valid(upstream, transport)) + return NULL; + upstream->back_off++; + upstream->to_retry = 1; + upstreams->current = upstream - upstreams->upstreams; + return upstream; +} + + int -connect_to_upstream(getdns_upstream *upstream, getdns_base_transport_t transport, +upstream_connect(getdns_upstream *upstream, getdns_base_transport_t transport, getdns_dns_req *dnsreq) { int fd = -1; @@ -1433,11 +1482,11 @@ connect_to_upstream(getdns_upstream *upstream, getdns_base_transport_t transport case GETDNS_BASE_TRANSPORT_TLS: /* Use existing if available*/ - if (upstream->fd != 1 && tls_handshake_active(upstream->tls_hs_state)) + if (upstream->fd != -1 && !tls_failed(upstream)) return upstream->fd; fd = tcp_connect(upstream, transport); if (fd == -1) return -1; - upstream->tls_obj = create_tls_object(dnsreq->context, fd); + upstream->tls_obj = tls_create_object(dnsreq->context, fd); if (upstream->tls_obj == NULL) { fprintf(stderr,"[TLS]: could not create tls object\n"); close(fd); @@ -1460,11 +1509,11 @@ connect_to_upstream(getdns_upstream *upstream, getdns_base_transport_t transport upstream->loop = dnsreq->context->extension; upstream->fd = fd; upstream_schedule_netreq(upstream, starttls_netreq); - /* Schedule at least the timeout locally. + /* Schedule at least the timeout locally, but use half the context value. * And also the write if we perform a synchronous lookup */ /* TODO[TLS]: How should we handle timeout on STARTTLS negotiation?*/ GETDNS_SCHEDULE_EVENT( - dnsreq->loop, upstream->fd, dnsreq->context->timeout, + dnsreq->loop, upstream->fd, dnsreq->context->timeout / 2, getdns_eventloop_event_init(&starttls_netreq->event, starttls_netreq, NULL, (dnsreq->loop != upstream->loop ? netreq_upstream_write_cb : NULL), stub_timeout_cb)); @@ -1473,7 +1522,7 @@ connect_to_upstream(getdns_upstream *upstream, getdns_base_transport_t transport return -1; /* Nothing to do*/ } - fprintf(stderr,"[TLS]: CONNECT(connect_to_upstream):" + fprintf(stderr,"[TLS]: CONNECT(upstream_connect):" " created new connection %d\n", fd); return fd; } @@ -1484,10 +1533,12 @@ find_upstream_for_specific_transport(getdns_network_req *netreq, int *fd) { /* TODO[TLS]: Fallback through upstreams....?*/ - getdns_upstream *upstream = pick_upstream(netreq, transport); + getdns_upstream *upstream = upstream_select(netreq, transport); + fprintf(stderr,"[TLS]: find_upstream_for_specific_transport selected " + "upstream %p for %d\n", (void*)upstream, transport); if (!upstream) return NULL; - *fd = connect_to_upstream(upstream, transport, netreq->owner); + *fd = upstream_connect(upstream, transport, netreq->owner); return upstream; } @@ -1495,19 +1546,24 @@ static int find_upstream_for_netreq(getdns_network_req *netreq) { int fd = -1; - for (int i = 0; i < GETDNS_BASE_TRANSPORT_MAX && + int i = netreq->transport; + for (; i < GETDNS_BASE_TRANSPORT_MAX && netreq->dns_base_transports[i] != GETDNS_BASE_TRANSPORT_NONE; i++) { netreq->upstream = find_upstream_for_specific_transport(netreq, netreq->dns_base_transports[i], &fd); if (fd == -1 || !netreq->upstream) continue; - netreq->dns_base_transport = &netreq->dns_base_transports[i]; + netreq->transport = i; return fd; } return -1; } +/************************/ +/* Scheduling functions */ +/***********************/ + static int move_netreq(getdns_network_req *netreq, getdns_upstream *upstream, getdns_upstream *new_upstream) @@ -1563,7 +1619,7 @@ move_netreq(getdns_network_req *netreq, getdns_upstream *upstream, stub_timeout_cb)); } } - netreq->dns_base_transport++; + netreq->transport++; return upstream->fd; } @@ -1574,26 +1630,50 @@ fallback_on_write(getdns_network_req *netreq) fprintf(stderr,"[TLS]: FALLBACK(fallback_on_write)\n"); /* TODO[TLS]: Fallback through all transports.*/ - getdns_base_transport_t *next_transport = netreq->dns_base_transport; - if (*(++next_transport) == GETDNS_BASE_TRANSPORT_NONE) + getdns_base_transport_t next_transport = + netreq->dns_base_transports[netreq->transport + 1]; + if (next_transport == GETDNS_BASE_TRANSPORT_NONE) return STUB_TCP_ERROR; - if (*netreq->dns_base_transport == GETDNS_BASE_TRANSPORT_STARTTLS && - *next_transport == GETDNS_BASE_TRANSPORT_TCP) { + if (netreq->dns_base_transports[netreq->transport] == + GETDNS_BASE_TRANSPORT_STARTTLS && + next_transport == GETDNS_BASE_TRANSPORT_TCP) { fprintf(stderr,"[TLS]: FALLBACK(fallback_on_write) STARTTLS->TCP\n"); /* Special case where can stay on same upstream*/ - netreq->dns_base_transport++; + netreq->transport++; return netreq->upstream->fd; } getdns_upstream *upstream = netreq->upstream; int fd; getdns_upstream *new_upstream = - find_upstream_for_specific_transport(netreq, *next_transport, &fd); + find_upstream_for_specific_transport(netreq, next_transport, &fd); if (!new_upstream) return STUB_TCP_ERROR; return move_netreq(netreq, upstream, new_upstream); } +static void +upstream_schedule_netreq(getdns_upstream *upstream, getdns_network_req *netreq) +{ + /* We have a connected socket and a global event loop */ + assert(upstream->fd >= 0); + assert(upstream->loop); + + fprintf(stderr,"[TLS]: SCHEDULE(upstream_schedule_netreq): fd %d\n", upstream->fd); + + /* Append netreq to write_queue */ + if (!upstream->write_queue) { + upstream->write_queue = upstream->write_queue_last = netreq; + upstream->event.write_cb = upstream_write_cb; + GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); + GETDNS_SCHEDULE_EVENT(upstream->loop, + upstream->fd, TIMEOUT_FOREVER, &upstream->event); + } else { + upstream->write_queue_last->write_queue_tail = netreq; + upstream->write_queue_last = netreq; + } +} + getdns_return_t priv_getdns_submit_stub_request(getdns_network_req *netreq) { @@ -1606,14 +1686,15 @@ priv_getdns_submit_stub_request(getdns_network_req *netreq) if (fd == -1) return GETDNS_RETURN_GENERIC_ERROR; - switch(*netreq->dns_base_transport) { + getdns_base_transport_t transport = netreq->dns_base_transports[netreq->transport]; + switch(transport) { case GETDNS_BASE_TRANSPORT_UDP: case GETDNS_BASE_TRANSPORT_TCP_SINGLE: netreq->fd = fd; GETDNS_SCHEDULE_EVENT( dnsreq->loop, netreq->fd, dnsreq->context->timeout, getdns_eventloop_event_init(&netreq->event, netreq, - NULL, (*netreq->dns_base_transport == GETDNS_BASE_TRANSPORT_UDP ? + NULL, (transport == GETDNS_BASE_TRANSPORT_UDP ? stub_udp_write_cb: stub_tcp_write_cb), stub_timeout_cb)); return GETDNS_RETURN_GOOD; diff --git a/src/types-internal.h b/src/types-internal.h index 96216b06..ebcd4261 100644 --- a/src/types-internal.h +++ b/src/types-internal.h @@ -169,7 +169,7 @@ typedef enum getdns_base_transport { GETDNS_BASE_TRANSPORT_NONE = 0, GETDNS_BASE_TRANSPORT_UDP, GETDNS_BASE_TRANSPORT_TCP_SINGLE, /* To be removed? */ - GETDNS_BASE_TRANSPORT_STARTTLS, /* Define before TCP to allow fallback when scheduling*/ + GETDNS_BASE_TRANSPORT_STARTTLS, /* Define before TCP to allow fallback */ GETDNS_BASE_TRANSPORT_TCP, GETDNS_BASE_TRANSPORT_TLS, GETDNS_BASE_TRANSPORT_MAX @@ -203,7 +203,7 @@ typedef struct getdns_network_req struct getdns_upstream *upstream; int fd; getdns_base_transport_t dns_base_transports[GETDNS_BASE_TRANSPORT_MAX]; - getdns_base_transport_t *dns_base_transport; + int transport; getdns_eventloop_event event; getdns_tcp_state tcp; uint16_t query_id; From 9d967317d37f42660cf67d065b4a0d594c00793b Mon Sep 17 00:00:00 2001 From: Sara Dickinson Date: Sun, 3 May 2015 15:11:46 +0100 Subject: [PATCH 10/12] Improve the timeout handling for TLS. --- src/context.c | 2 - src/stub.c | 175 +++++++++++++++++++++++++------------------ src/types-internal.h | 1 + 3 files changed, 102 insertions(+), 76 deletions(-) diff --git a/src/context.c b/src/context.c index 8dc571fa..7052a807 100644 --- a/src/context.c +++ b/src/context.c @@ -703,7 +703,6 @@ set_os_defaults(struct getdns_context *context) upstream = &context->upstreams-> upstreams[context->upstreams->count++]; - fprintf(stderr, "[TLS]: OS: creating upstream %d, %p, with port %s with transport %d\n", (int)context->upstreams->count, (void*)upstream, port_str, base_transport); upstream_init(upstream, context->upstreams, result); upstream->dns_base_transport = base_transport; } @@ -1586,7 +1585,6 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, upstream = &upstreams->upstreams[upstreams->count]; upstream->addr.ss_family = addr.ss_family; upstream_init(upstream, upstreams, ai); - fprintf(stderr, "[TLS]: creating upstream %d, %p, with port %d with transport %d\n", (int)upstreams->count, (void*)upstream,(int)port, base_transport); upstream->dns_base_transport = base_transport; upstreams->count++; freeaddrinfo(ai); diff --git a/src/stub.c b/src/stub.c index c22a68f4..29ebfcfb 100755 --- a/src/stub.c +++ b/src/stub.c @@ -48,6 +48,9 @@ #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; static uint32_t secret = 0; static uint32_t prev_secret = 0; @@ -454,13 +457,25 @@ stub_cleanup(getdns_network_req *netreq) } } +static int +tls_cleanup(getdns_upstream *upstream) +{ + SSL_free(upstream->tls_obj); + upstream->tls_obj = NULL; + upstream->tls_hs_state = GETDNS_HS_FAILED; + /* Reset timeout on failure*/ + GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); + GETDNS_SCHEDULE_EVENT(upstream->loop, upstream->fd, TIMEOUT_FOREVER, + getdns_eventloop_event_init(&upstream->event, upstream, + NULL, upstream_write_cb, NULL)); + return STUB_TLS_SETUP_ERROR; +} + static void upstream_erred(getdns_upstream *upstream) { getdns_network_req *netreq; - fprintf(stderr,"[TLS]: ERROR(upstream_erred)\n"); - while ((netreq = upstream->write_queue)) { stub_cleanup(netreq); netreq->state = NET_REQ_FINISHED; @@ -500,9 +515,6 @@ priv_getdns_cancel_stub_request(getdns_network_req *netreq) static void stub_erred(getdns_network_req *netreq) { - - fprintf(stderr,"[TLS]: ERROR(stub_erred)\n"); - stub_next_upstream(netreq); stub_cleanup(netreq); /* TODO[TLS]: When we get an error (which is probably a timeout) and are @@ -515,9 +527,6 @@ stub_erred(getdns_network_req *netreq) static void stub_timeout_cb(void *userarg) { - - fprintf(stderr,"[TLS]: TIMEOUT(stub_timeout_cb)\n"); - getdns_network_req *netreq = (getdns_network_req *)userarg; /* For now, mark a STARTTLS timeout as a failured negotiation and allow @@ -534,6 +543,35 @@ stub_timeout_cb(void *userarg) (void) getdns_context_request_timed_out(netreq->owner); } +static void +upstream_tls_timeout_cb(void *userarg) +{ + fprintf(stderr,"[TLS]: TIMEOUT(upstream_tls_timeout_cb)\n"); + + getdns_upstream *upstream = (getdns_upstream *)userarg; + /* Clean up and trigger a write to let the fallback code to its job */ + tls_cleanup(upstream); + + /* Need to handle the case where the far end doesn't respond to a + * TCP SYN and doesn't do a reset (as is the case with e.g. 8.8.8.8@1021). + * For that case the socket never becomes writable so doesn't trigger any + * callbacks. If so then clear out the queue in one go.*/ + int ret; + fd_set fds; + FD_ZERO(&fds); + FD_SET(FD_SET_T upstream->fd, &fds); + struct timeval tval; + tval.tv_sec = 0; + tval.tv_usec = 0; + ret = select(upstream->fd+1, NULL, &fds, NULL, &tval); + if (ret == 0) { + fprintf(stderr,"[TLS]: TIMEOUT(upstream_tls_timeout_cb)" + " upstream not selectable %d\n", ret); + while (upstream->write_queue) + upstream_write_cb(upstream); + } +} + /****************************/ /* TCP read/write functions */ /****************************/ @@ -778,7 +816,7 @@ tls_create_object(getdns_context *context, int fd) } static int -tls_do_handshake(getdns_upstream *upstream) +tls_do_handshake(getdns_upstream *upstream) { fprintf(stderr,"[TLS]: TLS(tls_do_handshake)\n"); @@ -795,7 +833,7 @@ tls_do_handshake(getdns_upstream *upstream) upstream->event.write_cb = NULL; GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); GETDNS_SCHEDULE_EVENT(upstream->loop, - upstream->fd, TIMEOUT_FOREVER, &upstream->event); + upstream->fd, TIMEOUT_TLS, &upstream->event); upstream->tls_hs_state = GETDNS_HS_READ; return STUB_TCP_AGAIN; case SSL_ERROR_WANT_WRITE: @@ -804,22 +842,21 @@ tls_do_handshake(getdns_upstream *upstream) upstream->event.write_cb = upstream_write_cb; GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); GETDNS_SCHEDULE_EVENT(upstream->loop, - upstream->fd, TIMEOUT_FOREVER, &upstream->event); + upstream->fd, TIMEOUT_TLS, &upstream->event); upstream->tls_hs_state = GETDNS_HS_WRITE; return STUB_TCP_AGAIN; - default: - SSL_free(upstream->tls_obj); - upstream->tls_obj = NULL; - upstream->tls_hs_state = GETDNS_HS_FAILED; - return STUB_TLS_SETUP_ERROR; + default: + return tls_cleanup(upstream); } } upstream->tls_hs_state = GETDNS_HS_DONE; upstream->event.read_cb = NULL; upstream->event.write_cb = upstream_write_cb; + /* Reset timeout on success*/ GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); - GETDNS_SCHEDULE_EVENT(upstream->loop, - upstream->fd, TIMEOUT_FOREVER, &upstream->event); + GETDNS_SCHEDULE_EVENT(upstream->loop, upstream->fd, TIMEOUT_FOREVER, + getdns_eventloop_event_init(&upstream->event, upstream, + NULL, upstream_write_cb, NULL)); return 0; } @@ -838,20 +875,11 @@ tls_connected(getdns_upstream* upstream) /* Lets make sure the connection is up before we try a handshake*/ int error = 0; socklen_t len = (socklen_t)sizeof(error); - /* TODO: This doesn't handle the case where the far end doesn't do a reset - * as is the case with e.g. 8.8.8.8. For that case the timeout kicks in - * and the user callback fails the message without the chance to fallback.*/ getsockopt(upstream->fd, SOL_SOCKET, SO_ERROR, (void*)&error, &len); if (error == EINPROGRESS || error == EWOULDBLOCK) return STUB_TCP_AGAIN; /* try again */ - else if (error != 0) { - - fprintf(stderr,"[TLS]: TLS(tls_connected): died gettting connection\n"); - SSL_free(upstream->tls_obj); - upstream->tls_obj = NULL; - upstream->tls_hs_state = GETDNS_HS_FAILED; - return STUB_TLS_SETUP_ERROR; - } + else if (error != 0) + return tls_cleanup(upstream); return tls_do_handshake(upstream); } @@ -1188,7 +1216,6 @@ upstream_read_cb(void *userarg) int q; uint16_t query_id; intptr_t query_id_intptr; - fprintf(stderr,"[TLS]: **********CALLBACK***********\n"); fprintf(stderr,"[TLS]: READ(upstream_read_cb): on %d\n", upstream->fd); @@ -1255,30 +1282,25 @@ upstream_read_cb(void *userarg) if (netreq->owner == upstream->starttls_req) { dnsreq = netreq->owner; - fprintf(stderr, "[TLS]: processing STARTTLS response!\n"); if (is_starttls_response(netreq)) { - upstream->tls_obj = tls_create_object(dnsreq->context, upstream->fd); - if (upstream->tls_obj == NULL) { - fprintf(stderr,"[TLS]: could not create tls object\n"); + upstream->tls_obj = tls_create_object(dnsreq->context, + upstream->fd); + if (upstream->tls_obj == NULL) upstream->tls_hs_state = GETDNS_HS_FAILED; - } upstream->tls_hs_state = GETDNS_HS_WRITE; } else upstream->tls_hs_state = GETDNS_HS_FAILED; dns_req_free(upstream->starttls_req); upstream->starttls_req = NULL; - // Now reschedule the writes on this connection - upstream->event.write_cb = upstream_write_cb; - fprintf(stderr, "[TLS] method: upstream_schedule_netreq ->" - "re-instating writes\n"); + /* Now reschedule the writes on this connection */ GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); - GETDNS_SCHEDULE_EVENT(upstream->loop, - upstream->fd, TIMEOUT_FOREVER, &upstream->event); - } else { - fprintf(stderr, "[TLS]: processing standard response....\n"); + GETDNS_SCHEDULE_EVENT(upstream->loop, upstream->fd, + netreq->owner->context->timeout, + getdns_eventloop_event_init(&upstream->event, upstream, + NULL, upstream_write_cb, NULL)); + } else priv_getdns_check_dns_req_complete(netreq->owner); - } /* Nothing more to read? Then deschedule the reads.*/ if (! upstream->netreq_by_query_id.count) { @@ -1333,7 +1355,8 @@ upstream_write_cb(void *userarg) default: netreq->query_id = (uint16_t) q; - fprintf(stderr, "[TLS]: WRITE(upstream_write_cb): successfull write on fd %d\n", upstream->fd); + fprintf(stderr, "[TLS]: WRITE(upstream_write_cb): successfull write:" + " on fd %d\n", upstream->fd); /* Unqueue the netreq from the write_queue */ if (!(upstream->write_queue = netreq->write_queue_tail)) { @@ -1357,9 +1380,7 @@ upstream_write_cb(void *userarg) } if (upstream->starttls_req) { /* Now deschedule any further writes on this connection until we get - the STARTTLS answer*/ - fprintf(stderr, "[TLS] method: upstream_write_cb -> STARTTTLS -" - "clearing upstream->event.write_cb\n"); + * the STARTTLS answer*/ upstream->event.write_cb = NULL; GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); GETDNS_SCHEDULE_EVENT(upstream->loop, @@ -1395,12 +1416,11 @@ upstream_transport_valid(getdns_upstream *upstream, getdns_base_transport_t transport) { /* For single shot transports, use only the TCP upstream. */ - fprintf(stderr,"[TLS]: upstream_transport_valid checking upstream %p against transport %d\n",(void*)upstream, transport); if (transport == GETDNS_BASE_TRANSPORT_UDP || transport == GETDNS_BASE_TRANSPORT_TCP_SINGLE) return (upstream->dns_base_transport == GETDNS_BASE_TRANSPORT_TCP ? 1:0); - /* Allow TCP messages to be sent on a STARTTLS upstream that hasn't upgraded - * to avoid opening a new connection if one is aleady open. */ + /* Allow TCP messages to be sent on a STARTTLS upstream that hasn't + * upgraded to avoid opening a new connection if one is aleady open. */ if (transport == GETDNS_BASE_TRANSPORT_TCP && upstream->dns_base_transport == GETDNS_BASE_TRANSPORT_STARTTLS && upstream->tls_hs_state == GETDNS_HS_FAILED) @@ -1408,10 +1428,8 @@ upstream_transport_valid(getdns_upstream *upstream, /* Otherwise, transport must match, and not have failed */ if (upstream->dns_base_transport != transport) return 0; - if (tls_failed(upstream)) { - fprintf(stderr,"[TLS]: tls_failed\n"); + if (tls_failed(upstream)) return 0; - } return 1; } @@ -1488,7 +1506,6 @@ upstream_connect(getdns_upstream *upstream, getdns_base_transport_t transport, if (fd == -1) return -1; upstream->tls_obj = tls_create_object(dnsreq->context, fd); if (upstream->tls_obj == NULL) { - fprintf(stderr,"[TLS]: could not create tls object\n"); close(fd); return -1; } @@ -1497,8 +1514,8 @@ upstream_connect(getdns_upstream *upstream, getdns_base_transport_t transport, upstream->fd = fd; break; case GETDNS_BASE_TRANSPORT_STARTTLS: - /* Use existing if available. Let the fallback code handle it if STARTTLS - * isn't availble. */ + /* Use existing if available. Let the fallback code handle it if + * STARTTLS isn't availble. */ if (upstream->fd != -1) return upstream->fd; fd = tcp_connect(upstream, transport); @@ -1509,11 +1526,11 @@ upstream_connect(getdns_upstream *upstream, getdns_base_transport_t transport, upstream->loop = dnsreq->context->extension; upstream->fd = fd; upstream_schedule_netreq(upstream, starttls_netreq); - /* Schedule at least the timeout locally, but use half the context value. + /* Schedule at least the timeout locally, but use less than half the + * context value so by default this timeouts before the TIMEOUT_TLS. * And also the write if we perform a synchronous lookup */ - /* TODO[TLS]: How should we handle timeout on STARTTLS negotiation?*/ GETDNS_SCHEDULE_EVENT( - dnsreq->loop, upstream->fd, dnsreq->context->timeout / 2, + dnsreq->loop, upstream->fd, dnsreq->context->timeout / 3, getdns_eventloop_event_init(&starttls_netreq->event, starttls_netreq, NULL, (dnsreq->loop != upstream->loop ? netreq_upstream_write_cb : NULL), stub_timeout_cb)); @@ -1534,8 +1551,6 @@ find_upstream_for_specific_transport(getdns_network_req *netreq, { /* TODO[TLS]: Fallback through upstreams....?*/ getdns_upstream *upstream = upstream_select(netreq, transport); - fprintf(stderr,"[TLS]: find_upstream_for_specific_transport selected " - "upstream %p for %d\n", (void*)upstream, transport); if (!upstream) return NULL; *fd = upstream_connect(upstream, transport, netreq->owner); @@ -1594,8 +1609,8 @@ move_netreq(getdns_network_req *netreq, getdns_upstream *upstream, if (netreq->owner->loop != upstream->loop) { /* Create an event for the new upstream*/ GETDNS_CLEAR_EVENT(netreq->owner->loop, &netreq->event); - GETDNS_SCHEDULE_EVENT( - netreq->owner->loop, new_upstream->fd, netreq->owner->context->timeout, + GETDNS_SCHEDULE_EVENT(netreq->owner->loop, new_upstream->fd, + netreq->owner->context->timeout, getdns_eventloop_event_init(&netreq->event, netreq, ( new_upstream->netreq_by_query_id.count ? netreq_upstream_read_cb : NULL ), @@ -1608,7 +1623,7 @@ move_netreq(getdns_network_req *netreq, getdns_upstream *upstream, if (upstream->write_queue) { GETDNS_CLEAR_EVENT(netreq->owner->loop, &upstream->write_queue->event); GETDNS_SCHEDULE_EVENT( - upstream->write_queue->owner->loop, upstream->fd, + upstream->write_queue->owner->loop, upstream->fd, upstream->write_queue->owner->context->timeout, getdns_eventloop_event_init(&upstream->write_queue->event, upstream->write_queue, @@ -1659,15 +1674,28 @@ upstream_schedule_netreq(getdns_upstream *upstream, getdns_network_req *netreq) assert(upstream->fd >= 0); assert(upstream->loop); - fprintf(stderr,"[TLS]: SCHEDULE(upstream_schedule_netreq): fd %d\n", upstream->fd); + fprintf(stderr,"[TLS]: SCHEDULE(upstream_schedule_netreq): fd %d\n", + upstream->fd); /* Append netreq to write_queue */ if (!upstream->write_queue) { upstream->write_queue = upstream->write_queue_last = netreq; - upstream->event.write_cb = upstream_write_cb; GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); - GETDNS_SCHEDULE_EVENT(upstream->loop, - upstream->fd, TIMEOUT_FOREVER, &upstream->event); + if (upstream->tls_hs_state == GETDNS_HS_WRITE || + (upstream->starttls_req && + upstream->starttls_req->netreqs[0] == netreq)) { + /* Set a timeout on the upstream so we can catch failed setup*/ + /* TODO[TLS]: When generic fallback supported, we should decide how + * to split the timeout between transports. */ + GETDNS_SCHEDULE_EVENT(upstream->loop, upstream->fd, + netreq->owner->context->timeout / 2, + getdns_eventloop_event_init(&upstream->event, upstream, + NULL, upstream_write_cb, upstream_tls_timeout_cb)); + } else { + upstream->event.write_cb = upstream_write_cb; + GETDNS_SCHEDULE_EVENT(upstream->loop, + upstream->fd, TIMEOUT_FOREVER, &upstream->event); + } } else { upstream->write_queue_last->write_queue_tail = netreq; upstream->write_queue_last = netreq; @@ -1686,7 +1714,8 @@ priv_getdns_submit_stub_request(getdns_network_req *netreq) if (fd == -1) return GETDNS_RETURN_GENERIC_ERROR; - getdns_base_transport_t transport = netreq->dns_base_transports[netreq->transport]; + getdns_base_transport_t transport = + netreq->dns_base_transports[netreq->transport]; switch(transport) { case GETDNS_BASE_TRANSPORT_UDP: case GETDNS_BASE_TRANSPORT_TCP_SINGLE: @@ -1699,12 +1728,10 @@ priv_getdns_submit_stub_request(getdns_network_req *netreq) return GETDNS_RETURN_GOOD; case GETDNS_BASE_TRANSPORT_STARTTLS: - case GETDNS_BASE_TRANSPORT_TCP: case GETDNS_BASE_TRANSPORT_TLS: - + case GETDNS_BASE_TRANSPORT_TCP: upstream_schedule_netreq(netreq->upstream, netreq); - /* TODO[TLS]: Timeout handling for async calls must change.... - * Maybe even change scheduling for sync calls here too*/ + /* TODO[TLS]: Change scheduling for sync calls. */ GETDNS_SCHEDULE_EVENT( dnsreq->loop, netreq->upstream->fd, dnsreq->context->timeout, getdns_eventloop_event_init(&netreq->event, netreq, NULL, diff --git a/src/types-internal.h b/src/types-internal.h index ebcd4261..53d9db2e 100644 --- a/src/types-internal.h +++ b/src/types-internal.h @@ -164,6 +164,7 @@ typedef struct getdns_tcp_state { } getdns_tcp_state; +/* TODO[TLS]: change this name to getdns_transport when API updated*/ typedef enum getdns_base_transport { GETDNS_BASE_TRANSPORT_MIN = 0, GETDNS_BASE_TRANSPORT_NONE = 0, From 9a7bfdd45b1671394a43825063390f3b80f6261e Mon Sep 17 00:00:00 2001 From: Sara Dickinson Date: Sun, 3 May 2015 15:39:21 +0100 Subject: [PATCH 11/12] Add trivial stub_debug functions. --- src/stub.c | 69 ++++++++++++++++++------------------------------------ 1 file changed, 23 insertions(+), 46 deletions(-) diff --git a/src/stub.c b/src/stub.c index 29ebfcfb..99b49304 100755 --- a/src/stub.c +++ b/src/stub.c @@ -51,6 +51,8 @@ /* Don't currently have access to the context whilst doing handshake */ #define TIMEOUT_TLS 2500 +#define STUB_DEBUG 0 + static time_t secret_rollover_time = 0; static uint32_t secret = 0; static uint32_t prev_secret = 0; @@ -69,6 +71,14 @@ static void stub_tcp_write_cb(void *userarg); /* General utility functions */ /*****************************/ +static void +stub_debug(const char *function_name) +{ +#ifdef STUB_DEBUG + fprintf(stderr,"[STUB DEBUG]: %s\n", function_name); +#endif +} + static void rollover_secret() { @@ -292,12 +302,8 @@ is_starttls_response(getdns_network_req *netreq) size_t starttls_name_len = 256, owner_name_len; /* Servers that are not STARTTLS aware will refuse the CH query*/ - if (LDNS_RCODE_NOERROR != - GLDNS_RCODE_WIRE(netreq->response)) { - fprintf(stderr, "[TLS] STARTTLS response had error %d\n", - GLDNS_RCODE_WIRE(netreq->response)); + if (LDNS_RCODE_NOERROR != GLDNS_RCODE_WIRE(netreq->response)) return 0; - } if (GLDNS_ANCOUNT(netreq->response) != 1) return 0; @@ -327,15 +333,10 @@ is_starttls_response(getdns_network_req *netreq) /* re-use the starttls_name for the response dname*/ starttls_name = priv_getdns_rdf_if_or_as_decompressed( rdf_iter,starttls_name_space,&starttls_name_len); - if (dname_equal(starttls_name, owner_name)) { - fprintf(stderr, "[TLS] STARTTLS response received :%s:\n", - (char*)starttls_name); + if (dname_equal(starttls_name, owner_name)) return 1; - } else { - fprintf(stderr, "[TLS] NO_TLS response received :%s:\n", - (char*)starttls_name); + else return 0; - } continue; } return 0; @@ -364,7 +365,6 @@ getdns_sock_nonblock(int sockfd) static int tcp_connect(getdns_upstream *upstream, getdns_base_transport_t transport) { - int fd = -1; if ((fd = socket(upstream->addr.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) return -1; @@ -546,8 +546,7 @@ stub_timeout_cb(void *userarg) static void upstream_tls_timeout_cb(void *userarg) { - fprintf(stderr,"[TLS]: TIMEOUT(upstream_tls_timeout_cb)\n"); - + stub_debug(__FUNCTION__); getdns_upstream *upstream = (getdns_upstream *)userarg; /* Clean up and trigger a write to let the fallback code to its job */ tls_cleanup(upstream); @@ -565,8 +564,6 @@ upstream_tls_timeout_cb(void *userarg) tval.tv_usec = 0; ret = select(upstream->fd+1, NULL, &fds, NULL, &tval); if (ret == 0) { - fprintf(stderr,"[TLS]: TIMEOUT(upstream_tls_timeout_cb)" - " upstream not selectable %d\n", ret); while (upstream->write_queue) upstream_write_cb(upstream); } @@ -818,8 +815,7 @@ tls_create_object(getdns_context *context, int fd) static int tls_do_handshake(getdns_upstream *upstream) { - fprintf(stderr,"[TLS]: TLS(tls_do_handshake)\n"); - + stub_debug(__FUNCTION__); int r; int want; ERR_clear_error(); @@ -828,7 +824,6 @@ tls_do_handshake(getdns_upstream *upstream) want = SSL_get_error(upstream->tls_obj, r); switch (want) { case SSL_ERROR_WANT_READ: - fprintf(stderr,"[TLS]: SSL_ERROR_WANT_READ\n"); upstream->event.read_cb = upstream_read_cb; upstream->event.write_cb = NULL; GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); @@ -837,7 +832,6 @@ tls_do_handshake(getdns_upstream *upstream) upstream->tls_hs_state = GETDNS_HS_READ; return STUB_TCP_AGAIN; case SSL_ERROR_WANT_WRITE: - fprintf(stderr,"[TLS]: SSL_ERROR_WANT_WRITE\n"); upstream->event.read_cb = NULL; upstream->event.write_cb = upstream_write_cb; GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); @@ -1210,6 +1204,7 @@ stub_tcp_write_cb(void *userarg) static void upstream_read_cb(void *userarg) { + stub_debug(__FUNCTION__); getdns_upstream *upstream = (getdns_upstream *)userarg; getdns_network_req *netreq; getdns_dns_req *dnsreq; @@ -1217,9 +1212,6 @@ upstream_read_cb(void *userarg) uint16_t query_id; intptr_t query_id_intptr; - fprintf(stderr,"[TLS]: **********CALLBACK***********\n"); - fprintf(stderr,"[TLS]: READ(upstream_read_cb): on %d\n", upstream->fd); - if (tls_should_read(upstream)) q = stub_tls_read(upstream, &upstream->tcp, &upstream->upstreams->mf); @@ -1323,15 +1315,12 @@ netreq_upstream_read_cb(void *userarg) static void upstream_write_cb(void *userarg) { + stub_debug(__FUNCTION__); getdns_upstream *upstream = (getdns_upstream *)userarg; getdns_network_req *netreq = upstream->write_queue; getdns_dns_req *dnsreq = netreq->owner; int q; - fprintf(stderr,"[TLS]: **********CALLBACK***********\n"); - fprintf(stderr,"[TLS]: WRITE(upstream_write_cb): upstream fd %d, SEND" - " netreq %p \n", upstream->fd, netreq); - if (tls_requested(netreq) && tls_should_write(upstream)) q = stub_tls_write(upstream, &upstream->tcp, netreq); else @@ -1347,17 +1336,12 @@ upstream_write_cb(void *userarg) case STUB_TLS_SETUP_ERROR: /* Could not complete the TLS set up. Need to fallback.*/ - if (fallback_on_write(netreq) == STUB_TCP_ERROR) { - fprintf(stderr,"[TLS]: Fallback failed.\n"); + if (fallback_on_write(netreq) == STUB_TCP_ERROR) message_erred(netreq); - } return; default: netreq->query_id = (uint16_t) q; - fprintf(stderr, "[TLS]: WRITE(upstream_write_cb): successfull write:" - " on fd %d\n", upstream->fd); - /* Unqueue the netreq from the write_queue */ if (!(upstream->write_queue = netreq->write_queue_tail)) { upstream->write_queue_last = NULL; @@ -1478,6 +1462,7 @@ int upstream_connect(getdns_upstream *upstream, getdns_base_transport_t transport, getdns_dns_req *dnsreq) { + stub_debug(__FUNCTION__); int fd = -1; switch(transport) { case GETDNS_BASE_TRANSPORT_UDP: @@ -1539,8 +1524,6 @@ upstream_connect(getdns_upstream *upstream, getdns_base_transport_t transport, return -1; /* Nothing to do*/ } - fprintf(stderr,"[TLS]: CONNECT(upstream_connect):" - " created new connection %d\n", fd); return fd; } @@ -1583,10 +1566,8 @@ static int move_netreq(getdns_network_req *netreq, getdns_upstream *upstream, getdns_upstream *new_upstream) { + stub_debug(__FUNCTION__); /* Remove from queue, clearing event and fd if we are the last*/ - - fprintf(stderr,"[TLS]: FALLBACK(move_netreq)\n"); - if (!(upstream->write_queue = netreq->write_queue_tail)) { upstream->write_queue_last = NULL; upstream->event.write_cb = NULL; @@ -1641,9 +1622,7 @@ move_netreq(getdns_network_req *netreq, getdns_upstream *upstream, static int fallback_on_write(getdns_network_req *netreq) { - - fprintf(stderr,"[TLS]: FALLBACK(fallback_on_write)\n"); - + stub_debug(__FUNCTION__); /* TODO[TLS]: Fallback through all transports.*/ getdns_base_transport_t next_transport = netreq->dns_base_transports[netreq->transport + 1]; @@ -1653,7 +1632,6 @@ fallback_on_write(getdns_network_req *netreq) if (netreq->dns_base_transports[netreq->transport] == GETDNS_BASE_TRANSPORT_STARTTLS && next_transport == GETDNS_BASE_TRANSPORT_TCP) { - fprintf(stderr,"[TLS]: FALLBACK(fallback_on_write) STARTTLS->TCP\n"); /* Special case where can stay on same upstream*/ netreq->transport++; return netreq->upstream->fd; @@ -1670,13 +1648,11 @@ fallback_on_write(getdns_network_req *netreq) static void upstream_schedule_netreq(getdns_upstream *upstream, getdns_network_req *netreq) { + stub_debug(__FUNCTION__); /* We have a connected socket and a global event loop */ assert(upstream->fd >= 0); assert(upstream->loop); - fprintf(stderr,"[TLS]: SCHEDULE(upstream_schedule_netreq): fd %d\n", - upstream->fd); - /* Append netreq to write_queue */ if (!upstream->write_queue) { upstream->write_queue = upstream->write_queue_last = netreq; @@ -1705,6 +1681,7 @@ upstream_schedule_netreq(getdns_upstream *upstream, getdns_network_req *netreq) getdns_return_t priv_getdns_submit_stub_request(getdns_network_req *netreq) { + stub_debug(__FUNCTION__); int fd = -1; getdns_dns_req *dnsreq = netreq->owner; From 3ac5e660f9516ee208d1da52beaa77e9dfd4bdc7 Mon Sep 17 00:00:00 2001 From: saradickinson Date: Mon, 11 May 2015 22:01:31 +0200 Subject: [PATCH 12/12] Address few minor bugs pointed out by willem --- src/context.c | 8 +++----- src/stub.c | 4 ++++ src/test/getdns_query.c | 7 +++++++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/context.c b/src/context.c index 7052a807..aabcc5e0 100644 --- a/src/context.c +++ b/src/context.c @@ -1564,10 +1564,10 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, if (port == GETDNS_PORT_ZERO) continue; - /* TODO[TLS]:Respect the user port for TCP and STARTTLS, but for - * now hardcode the TLS port */ if (base_transport != GETDNS_BASE_TRANSPORT_TLS) (void) getdns_dict_get_int(dict, "port", &port); + else + (void) getdns_dict_get_int(dict, "tls-port", &port); (void) snprintf(portstr, 1024, "%d", (int)port); if (getaddrinfo(addrstr, portstr, &hints, &ai)) @@ -1811,10 +1811,8 @@ ub_setup_stub(struct ub_ctx *ctx, getdns_context *context) * used. All other cases must currently fallback to TCP for libunbound.*/ if (context->dns_base_transports[0] == GETDNS_BASE_TRANSPORT_TLS && context->dns_base_transports[1] == GETDNS_BASE_TRANSPORT_NONE && - upstream_port(upstream) != GETDNS_PORT_DNS_OVER_TLS) + upstream->dns_base_transport != GETDNS_BASE_TRANSPORT_TLS) continue; - else if (upstream_port(upstream) != GETDNS_PORT_DNS) - continue; upstream_ntop_buf(upstream, addr, 1024); ub_ctx_set_fwd(ctx, addr); } diff --git a/src/stub.c b/src/stub.c index 99b49304..4653620e 100755 --- a/src/stub.c +++ b/src/stub.c @@ -399,6 +399,10 @@ stub_next_upstream(getdns_network_req *netreq) if (! --netreq->upstream->to_retry) netreq->upstream->to_retry = -(netreq->upstream->back_off *= 2); + /*[TLS]:TODO - This works because the next message won't try the exact + * same upstream (and the next message may not use the same transport), + * but the next message will find the next matching one thanks to logic in + * upstream_select, but this could be better */ if (++dnsreq->upstreams->current > dnsreq->upstreams->count) dnsreq->upstreams->current = 0; } diff --git a/src/test/getdns_query.c b/src/test/getdns_query.c index 009949f5..3fcf0920 100644 --- a/src/test/getdns_query.c +++ b/src/test/getdns_query.c @@ -54,6 +54,7 @@ ipaddr_dict(getdns_context *context, char *ipstr) getdns_dict *r = getdns_dict_create_with_context(context); char *s = strchr(ipstr, '%'), *scope_id_str = ""; char *p = strchr(ipstr, '@'), *portstr = ""; + char *t = strchr(ipstr, '#'), *tls_portstr = ""; uint8_t buf[sizeof(struct in6_addr)]; getdns_bindata addr; @@ -68,6 +69,10 @@ ipaddr_dict(getdns_context *context, char *ipstr) *p = 0; portstr = p + 1; } + if (t) { + *t = 0; + tls_portstr = t + 1; + } if (strchr(ipstr, ':')) { getdns_dict_util_set_string(r, "address_type", "IPv6"); addr.size = 16; @@ -86,6 +91,8 @@ ipaddr_dict(getdns_context *context, char *ipstr) getdns_dict_set_bindata(r, "address_data", &addr); if (*portstr) getdns_dict_set_int(r, "port", (int32_t)atoi(portstr)); + if (*tls_portstr) + getdns_dict_set_int(r, "tls-port", (int32_t)atoi(tls_portstr)); if (*scope_id_str) getdns_dict_util_set_string(r, "scope_id", scope_id_str);