First pass at TLS implementation - needs work!

This commit is contained in:
saradickinson 2014-12-07 19:03:34 +00:00 committed by Sara Dickinson
parent 793423b325
commit 99aa79b48f
11 changed files with 634 additions and 84 deletions

6
spec/index.html Normal file → Executable file
View File

@ -2193,8 +2193,10 @@ getdns_context_set_dns_transport(
The value is <span class=default>
<code>GETDNS_TRANSPORT_UDP_FIRST_AND_FALL_BACK_TO_TCP</code></span>,
<code>GETDNS_TRANSPORT_UDP_ONLY</code>,
<code>GETDNS_TRANSPORT_TCP_ONLY</code>, or
<code>GETDNS_TRANSPORT_TCP_ONLY_KEEP_CONNECTIONS_OPEN</code>.</p>
<code>GETDNS_TRANSPORT_TCP_ONLY</code>,
<code>GETDNS_TRANSPORT_TCP_ONLY_KEEP_CONNECTIONS_OPEN></code>,
<code>GETDNS_TRANSPORT_TLS_ONLY_KEEP_CONNECTIONS_OPEN></code>, or
<code>GETDNS_TRANSPORT_TLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN></code>
<div class=forh>
getdns_return_t

2
src/const-info.c Normal file → Executable file
View File

@ -39,6 +39,8 @@ static struct const_info consts_info[] = {
{ 541, "GETDNS_TRANSPORT_UDP_ONLY", GETDNS_TRANSPORT_UDP_ONLY_TEXT },
{ 542, "GETDNS_TRANSPORT_TCP_ONLY", GETDNS_TRANSPORT_TCP_ONLY_TEXT },
{ 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 },
{ 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 },

View File

@ -470,6 +470,24 @@ upstreams_resize(getdns_upstreams *upstreams, size_t size)
return r;
}
static void
upstreams_cleanup(getdns_upstreams *upstreams)
{
if (!upstreams)
return;
for (int i = 0; i < (int)upstreams->count; i++) {
if (upstreams->upstreams[i].tls_obj != NULL) {
SSL_shutdown(upstreams->upstreams[i].tls_obj);
SSL_free(upstreams->upstreams[i].tls_obj);
upstreams->upstreams[i].tls_obj = NULL;
}
if (upstreams->upstreams[i].fd != -1) {
close(upstreams->upstreams[i].fd);
upstreams->upstreams[i].fd = -1;
}
}
}
static void
upstreams_dereference(getdns_upstreams *upstreams)
{
@ -541,6 +559,7 @@ upstream_init(getdns_upstream *upstream,
/* For sharing a socket to this upstream with TCP */
upstream->fd = -1;
upstream->tls_obj = NULL;
upstream->loop = NULL;
(void) getdns_eventloop_event_init(
&upstream->event, upstream, NULL, NULL, NULL);
@ -770,6 +789,7 @@ getdns_context_create_with_extended_memory_functions(
result->edns_extended_rcode = 0;
result->edns_version = 0;
result->edns_do_bit = 0;
result-> tls_ctx = NULL;
result->extension = &result->mini_event.loop;
if ((r = getdns_mini_event_init(result, &result->mini_event)))
@ -876,6 +896,9 @@ getdns_context_destroy(struct getdns_context *context)
GETDNS_FREE(context->my_mf, context->fchg_hosts->prevstat);
GETDNS_FREE(context->my_mf, context->fchg_hosts);
}
if (context->tls_ctx) {
SSL_CTX_free(context->tls_ctx);
}
getdns_list_destroy(context->dns_root_servers);
getdns_list_destroy(context->suffix);
@ -887,6 +910,7 @@ getdns_context_destroy(struct getdns_context *context)
getdns_traverse_postorder(&context->local_hosts,
destroy_local_host, context);
upstreams_cleanup(context->upstreams);
upstreams_dereference(context->upstreams);
GETDNS_FREE(context->my_mf, context);
@ -1114,13 +1138,25 @@ set_ub_dns_transport(struct getdns_context* context,
set_ub_string_opt(context, "do-tcp:", "no");
break;
case GETDNS_TRANSPORT_TCP_ONLY:
case GETDNS_TRANSPORT_TCP_ONLY_KEEP_CONNECTIONS_OPEN:
case GETDNS_TRANSPORT_TCP_ONLY_KEEP_CONNECTIONS_OPEN:
set_ub_string_opt(context, "do-udp:", "no");
set_ub_string_opt(context, "do-tcp:", "yes");
break;
default:
/* TODO GETDNS_CONTEXT_TCP_ONLY_KEEP_CONNECTIONS_OPEN */
return GETDNS_RETURN_CONTEXT_UPDATE_FAIL;
case GETDNS_TRANSPORT_TLS_ONLY_KEEP_CONNECTIONS_OPEN:
case GETDNS_TRANSPORT_TLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN:
/* TODO: Investigate why ssl-upstream in Unbound isn't working (error
that the SSL lib isn't init'ed but that is done in prep_for_res.*/
/* Note: no fallback or pipelining available directly in unbound.*/
set_ub_string_opt(context, "do-udp:", "no");
set_ub_string_opt(context, "do-tcp:", "yes");
//set_ub_string_opt(context, "ssl-upstream:", "yes");
/* TODO: Specifying a different port to do TLS on in unbound is a bit
tricky as it involves modifying each fwd upstream defined on the
unbound ctx... And to support fallback this would have to be reset
from the stub code while trying to connect...*/
break;
default:
return GETDNS_RETURN_CONTEXT_UPDATE_FAIL;
}
return GETDNS_RETURN_GOOD;
}
@ -1134,6 +1170,11 @@ getdns_context_set_dns_transport(struct getdns_context *context,
getdns_transport_t value)
{
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.
However the method returns success as otherwise the transport could not
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;
}
@ -1448,6 +1489,7 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context,
freeaddrinfo(ai);
}
upstreams_dereference(context->upstreams);
/*Don't the existing upstreams need to be handled before overwritting here?*/
context->upstreams = upstreams;
dispatch_updated(context,
GETDNS_CONTEXT_CODE_UPSTREAM_RECURSIVE_SERVERS);
@ -1756,6 +1798,34 @@ getdns_context_prepare_for_resolution(struct getdns_context *context,
if (context->destroying) {
return GETDNS_RETURN_BAD_CONTEXT;
}
/* Transport can in theory be set per query in stub mode so deal with it
here */
printf("[TLS] preparing for resolution, checking transport type\n");
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) {
/* Init the SSL library */
SSL_library_init();
/* Load error messages */
SSL_load_error_strings();
/* Create client context, use TLS v1.2 only for now */
SSL_CTX* tls_ctx = SSL_CTX_new(TLSv1_2_client_method());
if(!tls_ctx) {
ERR_print_errors_fp(stderr);
return GETDNS_RETURN_BAD_CONTEXT;
}
context->tls_ctx = tls_ctx;
}
break;
default:
break;
}
}
if (context->resolution_type_set == context->resolution_type)
/* already set and no config changes
* have caused this to be bad.

View File

@ -83,6 +83,7 @@ typedef struct getdns_upstream {
/* For sharing a TCP socket to this upstream */
int fd;
SSL* tls_obj;
getdns_eventloop_event event;
getdns_eventloop *loop;
getdns_tcp_state tcp;
@ -133,6 +134,7 @@ struct getdns_context {
uint8_t edns_version;
uint8_t edns_do_bit;
int edns_maximum_udp_payload_size; /* -1 is unset */
SSL_CTX* tls_ctx;
getdns_update_callback update_callback;
getdns_update_callback2 update_callback2;

6
src/getdns/getdns.h.in Normal file → Executable file
View File

@ -163,7 +163,9 @@ typedef enum getdns_transport_t {
GETDNS_TRANSPORT_UDP_FIRST_AND_FALL_BACK_TO_TCP = 540,
GETDNS_TRANSPORT_UDP_ONLY = 541,
GETDNS_TRANSPORT_TCP_ONLY = 542,
GETDNS_TRANSPORT_TCP_ONLY_KEEP_CONNECTIONS_OPEN = 543
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_t;
/**
@ -174,6 +176,8 @@ typedef enum getdns_transport_t {
#define GETDNS_TRANSPORT_UDP_ONLY_TEXT "See getdns_context_set_dns_transport()"
#define GETDNS_TRANSPORT_TCP_ONLY_TEXT "See getdns_context_set_dns_transport()"
#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()"
/** @}
*/

View File

@ -98,6 +98,7 @@ network_req_init(getdns_network_req *net_req, getdns_dns_req *owner,
net_req->write_queue_tail = NULL;
net_req->query_len = 0;
net_req->response_len = 0;
net_req->tls_obj = NULL;
net_req->wire_data_sz = wire_data_sz;
if (max_query_sz) {

469
src/stub.c Normal file → Executable file
View File

@ -41,6 +41,8 @@
#include "util-internal.h"
#include "general.h"
#define TLS_PORT 1021
static time_t secret_rollover_time = 0;
static uint32_t secret = 0;
static uint32_t prev_secret = 0;
@ -318,6 +320,13 @@ 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);
upstream->tls_obj = NULL;
}
close(upstream->fd);
upstream->fd = -1;
}
@ -498,6 +507,8 @@ stub_tcp_read(int fd, getdns_tcp_state *tcp, struct mem_funcs *mf)
uint8_t *buf;
size_t buf_size;
fprintf(stderr, "[TLS] method: stub_tcp_read\n");
if (!tcp->read_buf) {
/* First time tcp read, create a buffer for reading */
if (!(tcp->read_buf = GETDNS_XMALLOC(*mf, uint8_t, 4096)))
@ -518,10 +529,11 @@ stub_tcp_read(int fd, getdns_tcp_state *tcp, struct mem_funcs *mf)
/* TODO: Try to reconnect */
return STUB_TCP_ERROR;
}
fprintf(stderr, "[TLS] method: read %d TCP bytes \n", (int)read);
tcp->to_read -= read;
tcp->read_pos += read;
if (tcp->to_read > 0)
if ((int)tcp->to_read > 0)
return STUB_TCP_AGAIN;
read = tcp->read_pos - tcp->read_buf;
@ -563,13 +575,16 @@ stub_tcp_read_cb(void *userarg)
&dnsreq->context->mf))) {
case STUB_TCP_AGAIN:
fprintf(stderr, "[TLS] method: stub_tcp_read_cb -> tcp again\n");
return;
case STUB_TCP_ERROR:
fprintf(stderr, "[TLS] method: stub_tcp_read_cb -> tcp error\n");
stub_erred(netreq);
return;
default:
fprintf(stderr, "[TLS] method: stub_tcp_read_cb -> All done. close fd %d\n", netreq->fd);
GETDNS_CLEAR_EVENT(dnsreq->loop, &netreq->event);
if (q != netreq->query_id)
return;
@ -595,8 +610,200 @@ 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);
struct timeval timeout = {2, 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)
{
fprintf(stderr, "[TLS] connect in progress \n");
/* 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.*/
SSL*
do_tls_handshake(getdns_dns_req *dnsreq, getdns_upstream *upstream)
{
/*Lets make sure the connection is up before we try a handshake*/
if (errno == EINPROGRESS && sock_connected(upstream->fd) == -1) {
fprintf(stderr, "[TLS] connect failed \n");
return NULL;
}
fprintf(stderr, "[TLS] connect done \n");
/* Create SSL instance */
SSL* ssl = SSL_new(dnsreq->context->tls_ctx);
if(!ssl) {
return NULL;
}
/* Connect the SSL object with a file descriptor */
if(!SSL_set_fd(ssl, upstream->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);
fprintf(stderr, "[TLS] in handshake loop %d, want is %d \n", r, want);
switch (want) {
case SSL_ERROR_WANT_READ:
if (select(upstream->fd + 1, &fds, NULL, NULL, &timeout) == 0) {
fprintf(stderr, "[TLS] ssl handshake timeout %d\n", want);
SSL_free(ssl);
return NULL;
}
break;
case SSL_ERROR_WANT_WRITE:
if (select(upstream->fd + 1, NULL, &fds, NULL, &timeout) == 0) {
fprintf(stderr, "[TLS] ssl handshake timeout %d\n", want);
SSL_free(ssl);
return NULL;
}
break;
default:
fprintf(stderr, "[TLS] got ssl error code %d\n", want);
SSL_free(ssl);
return NULL;
}
}
fprintf(stderr, "[TLS] got TLS connection\n");
return ssl;
}
static int
stub_tls_read(SSL* tls_obj, getdns_tcp_state *tcp, struct mem_funcs *mf)
{
ssize_t read;
uint8_t *buf;
size_t buf_size;
fprintf(stderr, "[TLS] method: stub_tls_read\n");
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;
}
fprintf(stderr, "[TLS] method: read %d TLS bytes \n", (int)read);
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);
fprintf(stderr, "[TLS] method: %d TLS bytes to read \n", (int)tcp->to_read);
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 */
fprintf(stderr, "[TLS] method: resetting read_pos \n");
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 void netreq_upstream_read_cb(void *userarg);
static void netreq_upstream_write_cb(void *userarg);
static void upstream_write_cb(void *userarg);
static void
upstream_read_cb(void *userarg)
{
@ -607,9 +814,18 @@ upstream_read_cb(void *userarg)
uint16_t query_id;
intptr_t query_id_intptr;
switch ((q = stub_tcp_read(upstream->fd, &upstream->tcp,
&upstream->upstreams->mf))) {
fprintf(stderr, "[TLS] method: upstream_read_cb\n");
if (upstream->tls_obj)
q = stub_tls_read(upstream->tls_obj, &upstream->tcp,
&upstream->upstreams->mf);
else
q = stub_tcp_read(upstream->fd, &upstream->tcp,
&upstream->upstreams->mf);
switch (q) {
case STUB_TCP_AGAIN:
fprintf(stderr, "[TLS] method: upstream_read_cb -> STUB_TCP_AGAIN\n");
return;
case STUB_TCP_ERROR:
@ -617,6 +833,8 @@ upstream_read_cb(void *userarg)
return;
default:
fprintf(stderr, "[TLS] method: upstream_read_cb -> processing reponse\n");
/* Lookup netreq */
query_id = (uint16_t) q;
query_id_intptr = (intptr_t) query_id;
@ -633,6 +851,7 @@ upstream_read_cb(void *userarg)
netreq->response = upstream->tcp.read_buf;
netreq->response_len =
upstream->tcp.read_pos - upstream->tcp.read_buf;
netreq->tls_obj = upstream->tls_obj;
upstream->tcp.read_buf = NULL;
upstream->upstreams->current = 0;
@ -687,6 +906,7 @@ static int
stub_tcp_write(int fd, getdns_tcp_state *tcp, getdns_network_req *netreq)
{
getdns_dns_req *dnsreq = netreq->owner;
fprintf(stderr, "[TLS] method: stub_tcp_write\n");
size_t pkt_len = netreq->response - netreq->query;
ssize_t written;
@ -705,9 +925,9 @@ 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_KEEP_CONNECTIONS_OPEN)
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))
query_id = arc4random();
else do {
query_id = arc4random();
@ -822,6 +1042,56 @@ stub_tcp_write_cb(void *userarg)
}
}
static int
stub_tls_write(SSL* tls_obj, getdns_tcp_state *tcp, getdns_network_req *netreq)
{
fprintf(stderr, "[TLS] method: stub_tls_write\n");
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
*/
/* 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)
{
@ -830,7 +1100,14 @@ upstream_write_cb(void *userarg)
getdns_dns_req *dnsreq = netreq->owner;
int q;
switch ((q = stub_tcp_write(upstream->fd, &upstream->tcp, netreq))) {
fprintf(stderr, "[TLS] method: upstream_write_cb for %s with class %d\n", dnsreq->name, (int)netreq->request_class);
if (upstream->tls_obj)
q = stub_tls_write(upstream->tls_obj, &upstream->tcp, netreq);
else
q = stub_tcp_write(upstream->fd, &upstream->tcp, netreq);
switch (q) {
case STUB_TCP_AGAIN:
return;
@ -902,18 +1179,110 @@ 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);
}
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));
}
typedef enum getdns_base_transport {
NONE,
UDP,
TCP_SINGLE,
TCP,
TLS
} getdns_base_transport_t;
getdns_transport_t
get_transport(getdns_transport_t transport, int level) {
if (!(level == 0 || level == 1)) return NONE;
switch (transport) {
case GETDNS_TRANSPORT_UDP_FIRST_AND_FALL_BACK_TO_TCP:
if (level == 0) return UDP;
if (level == 1) return TCP;
case GETDNS_TRANSPORT_UDP_ONLY:
if (level == 0) return UDP;
if (level == 1) return NONE;
case GETDNS_TRANSPORT_TCP_ONLY:
if (level == 0) return TCP_SINGLE;
if (level == 1) return NONE;
case GETDNS_TRANSPORT_TCP_ONLY_KEEP_CONNECTIONS_OPEN:
if (level == 0) return TCP;
if (level == 1) return NONE;
case GETDNS_TRANSPORT_TLS_ONLY_KEEP_CONNECTIONS_OPEN:
if (level == 0) return TLS;
if (level == 1) return NONE;
case GETDNS_TRANSPORT_TLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN:
if (level == 0) return TLS;
if (level == 1) return TCP;
default:
return NONE;
}
}
int
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 == TLS && (int)get_port(addr) != TLS_PORT) {
connect_addr = upstream->addr;
addr = &connect_addr;
set_port(addr, TLS_PORT);
fprintf(stderr, "[TLS] Forcing switch to port %d for TLS\n", TLS_PORT);
}
if ((fd = socket(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 == TCP || transport == TCP_SINGLE)
return fd;
#endif
if (connect(fd, (struct sockaddr *)addr,
addr_len) == -1) {
if (errno != EINPROGRESS) {
close(fd);
return -1;
}
}
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);
fprintf(stderr, "[TLS] method: priv_getdns_submit_stub_request\n");
if (!upstream)
return GETDNS_RETURN_GENERIC_ERROR;
switch(dnsreq->context->dns_transport) {
case GETDNS_TRANSPORT_UDP_ONLY:
case GETDNS_TRANSPORT_UDP_FIRST_AND_FALL_BACK_TO_TCP:
// Work out the primary and fallback transport options
getdns_base_transport_t transport = get_transport(
dnsreq->context->dns_transport,0);
getdns_base_transport_t fb_transport = get_transport(
dnsreq->context->dns_transport,1);
switch(transport) {
case UDP:
if ((netreq->fd = socket(
upstream->addr.ss_family, SOCK_DGRAM, IPPROTO_UDP)) == -1)
@ -929,23 +1298,10 @@ priv_getdns_submit_stub_request(getdns_network_req *netreq)
return GETDNS_RETURN_GOOD;
case GETDNS_TRANSPORT_TCP_ONLY:
case TCP_SINGLE:
if ((netreq->fd = socket(
upstream->addr.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1)
if ((netreq->fd = tcp_connect(upstream, transport)) == -1)
return GETDNS_RETURN_GENERIC_ERROR;
getdns_sock_nonblock(netreq->fd);
#ifdef USE_TCP_FASTOPEN
/* Leave the connect to the later call to sendto() */
#else
if (connect(netreq->fd, (struct sockaddr *)&upstream->addr,
upstream->addr_len) == -1 && errno != EINPROGRESS) {
close(netreq->fd);
return GETDNS_RETURN_GENERIC_ERROR;
}
#endif
netreq->upstream = upstream;
GETDNS_SCHEDULE_EVENT(
@ -955,37 +1311,58 @@ priv_getdns_submit_stub_request(getdns_network_req *netreq)
return GETDNS_RETURN_GOOD;
case GETDNS_TRANSPORT_TCP_ONLY_KEEP_CONNECTIONS_OPEN:
case TCP:
case TLS:
/* In coming comments, "global" means "context wide" */
/* Are we the first? (Is global socket initialized?) */
if (upstream->fd == -1) {
/* We are the first. Make global socket and connect. */
if ((upstream->fd = socket(upstream->addr.ss_family,
SOCK_STREAM, IPPROTO_TCP)) == -1)
return GETDNS_RETURN_GENERIC_ERROR;
getdns_sock_nonblock(upstream->fd);
#ifdef USE_TCP_FASTOPEN
/* Leave the connect to the later call to sendto() */
#else
if (connect(upstream->fd,
(struct sockaddr *)&upstream->addr,
upstream->addr_len) == -1 && errno != EINPROGRESS){
/* 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;
close(upstream->fd);
upstream->fd = -1;
return GETDNS_RETURN_GENERIC_ERROR;
/* We are the first. Make global socket and connect. */
if ((upstream->fd = tcp_connect(upstream, transport)) == -1) {
//TODO: Hum, a reset doesn't make the connect fail...
if (fb_transport == NONE)
return GETDNS_RETURN_GENERIC_ERROR;
fprintf(stderr, "[TLS] Connect failed on fd... %d\n", upstream->fd);
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 == TLS && (fallback == 0)) {
fprintf(stderr, "[TLS] Doing SSL handshake... %d\n", upstream->fd);
upstream->tls_obj = do_tls_handshake(dnsreq, upstream);
if (!upstream->tls_obj) {
if (fb_transport == NONE)
return GETDNS_RETURN_GENERIC_ERROR;
close(upstream->fd);
if ((upstream->fd = tcp_connect(upstream, fb_transport)) == -1)
return GETDNS_RETURN_GENERIC_ERROR;
}
}
} else {
/* Cater for the case of the user downgrading and existing TLS
connection to TCP for some reason...*/
if (transport == TCP && upstream->tls_obj) {
SSL_shutdown(upstream->tls_obj);
SSL_free(upstream->tls_obj);
upstream->tls_obj = NULL;
}
#endif
/* Attach to the global event loop
* so it can do it's own scheduling
*/
upstream->loop = dnsreq->context->extension;
}
netreq->upstream = upstream;
/* Attach to the global event loop
* so it can do it's own scheduling
*/
upstream->loop = dnsreq->context->extension;
/* We have a context wide socket.
* Now schedule the write request.
*/

View File

@ -29,9 +29,23 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <getdns/getdns.h>
#include <getdns/getdns_extra.h>
static int quiet = 0;
static int batch_mode = 0;
static char *query_file = NULL;
static int json = 0;
static char *the_root = ".";
static char *name;
static getdns_context *context;
static getdns_dict *extensions;
static uint16_t request_type = GETDNS_RRTYPE_NS;
static int timeout, edns0_size;
static int async = 0, interactive = 0;
static enum { GENERAL, ADDRESS, HOSTNAME, SERVICE } calltype = GENERAL;
int get_rrtype(const char *t);
getdns_dict *
@ -90,6 +104,7 @@ print_usage(FILE *out, const char *progname)
fprintf(out, "\t-b <bufsize>\tSet edns0 max_udp_payload size\n");
fprintf(out, "\t-D\tSet edns0 do bit\n");
fprintf(out, "\t-d\tclear edns0 do bit\n");
fprintf(out, "\t-F <filename>\tread the queries from the specified file\n");
fprintf(out, "\t-G\tgeneral lookup\n");
fprintf(out, "\t-H\thostname lookup. (<name> must be an IP address; <type> is ignored)\n");
fprintf(out, "\t-h\tPrint this help\n");
@ -104,29 +119,47 @@ print_usage(FILE *out, const char *progname)
fprintf(out, "\t-t <timeout>\tSet timeout in miliseconds\n");
fprintf(out, "\t-T\tSet transport to TCP only\n");
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-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");
fprintf(out, "\t-q\tQuiet mode - don't print response\n");
fprintf(out, "\t+sit[=cookie]\tSet edns cookie\n");
}
void callback(getdns_context *context, getdns_callback_type_t callback_type,
getdns_dict *response, void *userarg, getdns_transaction_t trans_id)
{
getdns_dict **response_ptr = (getdns_dict **)userarg;
char *response_str;
if (response)
*response_ptr = response;
if (callback_type == GETDNS_CALLBACK_COMPLETE) {
/* This is a callback with data */;
if (!quiet) {
if (json)
response_str = getdns_print_json_dict(
response, json == 1);
else if ((response_str = getdns_pretty_print_dict(response))) {
fprintf(stdout, "ASYNC response:\n%s\n", response_str);
free(response_str);
}
}
fprintf(stderr,
"The callback with ID %llu was successfull.\n",
(unsigned long long)trans_id);
} else if (callback_type == GETDNS_CALLBACK_CANCEL)
fprintf(stderr,
"The callback with ID %llu was cancelled. Exiting.\n",
(unsigned long long)trans_id);
else
fprintf(stderr,
"The callback got a callback_type of %d. Exiting.\n",
callback_type);
getdns_dict_destroy(response);
response = NULL;
}
static char *the_root = ".";
static char *name;
static getdns_context *context;
static getdns_dict *extensions;
static uint16_t request_type = GETDNS_RRTYPE_NS;
static int timeout, edns0_size;
static int async = 0, interactive = 0;
static enum { GENERAL, ADDRESS, HOSTNAME, SERVICE } calltype = GENERAL;
static int json = 0;
#define CONTINUE ((getdns_return_t)-2)
static getdns_return_t set_cookie(getdns_dict *exts, char *cookie)
@ -259,6 +292,15 @@ getdns_return_t parse_args(int argc, char **argv)
case 'd':
(void) getdns_context_set_edns_do_bit(context, 0);
break;
case 'F':
if (c[1] != 0 || ++i >= argc || !*argv[i]) {
fprintf(stderr, "file name expected "
"after -F\n");
return GETDNS_RETURN_GENERIC_ERROR;
}
query_file = argv[i];
interactive = 1;
break;
case 'G':
calltype = GENERAL;
break;
@ -282,6 +324,8 @@ getdns_return_t parse_args(int argc, char **argv)
break;
case 'p':
json = 0;
case 'q':
quiet = 1;
break;
case 'r':
getdns_context_set_resolution_type(
@ -319,6 +363,14 @@ getdns_return_t parse_args(int argc, char **argv)
getdns_context_set_dns_transport(context,
GETDNS_TRANSPORT_TCP_ONLY_KEEP_CONNECTIONS_OPEN);
break;
case 'L':
getdns_context_set_dns_transport(context,
GETDNS_TRANSPORT_TLS_ONLY_KEEP_CONNECTIONS_OPEN);
break;
case 'E':
getdns_context_set_dns_transport(context,
GETDNS_TRANSPORT_TLS_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);
@ -327,6 +379,9 @@ getdns_return_t parse_args(int argc, char **argv)
getdns_context_set_dns_transport(context,
GETDNS_TRANSPORT_UDP_ONLY);
break;
case 'B':
batch_mode = 1;
break;
default:
@ -361,6 +416,7 @@ main(int argc, char **argv)
char *response_str;
getdns_return_t r;
getdns_dict *address = NULL;
FILE *fp = NULL;
name = the_root;
if ((r = getdns_context_create(&context, 1))) {
@ -376,14 +432,28 @@ main(int argc, char **argv)
if ((r = parse_args(argc, argv)))
goto done_destroy_context;
if (query_file) {
fp = fopen(query_file, "rt");
if (fp == NULL) {
fprintf(stderr, "Could not open query file: %s\n", query_file);
goto done_destroy_context;
}
}
/* Make the call */
do {
char line[1024], *token, *linev[256];
int linec;
if (interactive) {
fprintf(stdout, "> ");
if (!fgets(line, 1024, stdin) || !*line)
break;
if (!query_file) {
fprintf(stdout, "> ");
if (!fgets(line, 1024, stdin) || !*line)
break;
} else {
if (!fgets(line, 1024, fp) || !*line)
break;
fprintf(stdout,"Found query: %s", line);
}
linev[0] = argv[0];
linec = 1;
@ -430,8 +500,8 @@ main(int argc, char **argv)
}
if (r)
goto done_destroy_extensions;
getdns_context_run(context);
if (!batch_mode)
getdns_context_run(context);
} else {
switch (calltype) {
case GENERAL:
@ -456,22 +526,27 @@ main(int argc, char **argv)
}
if (r)
goto done_destroy_extensions;
}
if (json)
response_str = getdns_print_json_dict(
response, json == 1);
else
response_str = getdns_pretty_print_dict(response);
if (response_str) {
fprintf(stdout, "%s\n", response_str);
free(response_str);
} else {
r = GETDNS_RETURN_MEMORY_ERROR;
fprintf(stderr, "Could not print response\n");
if (!quiet) {
if (json)
response_str = getdns_print_json_dict(
response, json == 1);
else if ((response_str = getdns_pretty_print_dict(response))) {
fprintf(stdout, "SYNC response:\n%s\n", response_str);
free(response_str);
} else {
r = GETDNS_RETURN_MEMORY_ERROR;
fprintf(stderr, "Could not print response\n");
}
} else if (r == GETDNS_RETURN_GOOD)
fprintf(stdout, "Response code was: GOOD\n");
else if (interactive)
fprintf(stderr, "An error occurred: %d\n", r);
}
} while (interactive);
if (batch_mode)
getdns_context_run(context);
/* Clean up */
done_destroy_extensions:
getdns_dict_destroy(extensions);
@ -479,6 +554,9 @@ done_destroy_context:
if (response) getdns_dict_destroy(response);
getdns_context_destroy(context);
if (fp)
fclose(fp);
if (r == CONTINUE)
return 0;
if (r)

View File

@ -44,6 +44,8 @@
#define TRANSPORT_UDP "udp"
#define TRANSPORT_TCP "tcp"
#define TRANSPORT_PIPELINE "pipeline"
#define TRANSPORT_TLS_KEEPOPEN "tls"
#define TRANSPORT_TLS_TCP_KEEPOPEN "dns-over-tls"
#define RESOLUTION_STUB "stub"
#define RESOLUTION_REC "rec"
@ -98,6 +100,10 @@ main(int argc, char** argv)
getdns_context_set_dns_transport(this_context, GETDNS_TRANSPORT_TCP_ONLY);
else if (strncmp(transport, TRANSPORT_PIPELINE, 8) == 0)
getdns_context_set_dns_transport(this_context, GETDNS_TRANSPORT_TCP_ONLY_KEEP_CONNECTIONS_OPEN);
else if (strncmp(transport, TRANSPORT_TLS_KEEPOPEN, 3) == 0)
getdns_context_set_dns_transport(this_context, GETDNS_TRANSPORT_TLS_ONLY_KEEP_CONNECTIONS_OPEN);
else if (strncmp(transport, TRANSPORT_TLS_TCP_KEEPOPEN, 12) == 0)
getdns_context_set_dns_transport(this_context, GETDNS_TRANSPORT_TLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN);
else if (strncmp(transport, TRANSPORT_UDP, 3) != 0) {
fprintf(stderr, "Invalid transport %s, must be one of udp, tcp or pipeline\n", transport);
exit(EXIT_FAILURE);

View File

@ -41,6 +41,8 @@
#define TRANSPORT_UDP "udp"
#define TRANSPORT_TCP "tcp"
#define TRANSPORT_PIPELINE "pipeline"
#define TRANSPORT_TLS_KEEPOPEN "tls"
#define TRANSPORT_TLS_TCP_KEEPOPEN "dns-over-tls"
#define RESOLUTION_STUB "stub"
#define RESOLUTION_REC "rec"
@ -82,6 +84,10 @@ main(int argc, char** argv)
getdns_context_set_dns_transport(this_context, GETDNS_TRANSPORT_TCP_ONLY);
else if (strncmp(transport, TRANSPORT_PIPELINE, 8) == 0)
getdns_context_set_dns_transport(this_context, GETDNS_TRANSPORT_TCP_ONLY_KEEP_CONNECTIONS_OPEN);
else if (strncmp(transport, TRANSPORT_TLS_KEEPOPEN, 3) == 0)
getdns_context_set_dns_transport(this_context, GETDNS_TRANSPORT_TLS_ONLY_KEEP_CONNECTIONS_OPEN);
else if (strncmp(transport, TRANSPORT_TLS_TCP_KEEPOPEN, 12) == 0)
getdns_context_set_dns_transport(this_context, GETDNS_TRANSPORT_TLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN);
else if (strncmp(transport, TRANSPORT_UDP, 3) != 0) {
fprintf(stderr, "Invalid transport %s, must be one of udp, tcp or pipeline\n", transport);
exit(EXIT_FAILURE);

View File

@ -37,6 +37,7 @@
#define TYPES_INTERNAL_H_
#include <netinet/in.h>
#include <openssl/ssl.h>
#include "getdns/getdns.h"
#include "getdns/getdns_extra.h"
#include "util/rbtree.h"
@ -211,6 +212,7 @@ typedef struct getdns_network_req
uint8_t *opt; /* offset of OPT RR in query */
size_t response_len;
uint8_t *response;
SSL* tls_obj;
size_t wire_data_sz;
uint8_t wire_data[];