Implement client side edns-tcp-keepalive

This commit is contained in:
Sara Dickinson 2015-11-24 17:59:01 +00:00
parent eb6c6e3f67
commit 746a827baa
8 changed files with 168 additions and 94 deletions

View File

@ -558,6 +558,7 @@ _getdns_upstream_shutdown(getdns_upstream *upstream)
upstream->tcp.write_error = 0; upstream->tcp.write_error = 0;
upstream->writes_done = 0; upstream->writes_done = 0;
upstream->responses_received = 0; upstream->responses_received = 0;
upstream->keepalive_timeout = 0;
if (upstream->tls_hs_state != GETDNS_HS_FAILED) { if (upstream->tls_hs_state != GETDNS_HS_FAILED) {
upstream->tls_hs_state = GETDNS_HS_NONE; upstream->tls_hs_state = GETDNS_HS_NONE;
upstream->tls_auth_failed = 0; upstream->tls_auth_failed = 0;
@ -609,6 +610,7 @@ upstream_init(getdns_upstream *upstream,
/* How is this upstream doing? */ /* How is this upstream doing? */
upstream->writes_done = 0; upstream->writes_done = 0;
upstream->responses_received = 0; upstream->responses_received = 0;
upstream->keepalive_timeout = 0;
upstream->to_retry = 2; upstream->to_retry = 2;
upstream->back_off = 1; upstream->back_off = 1;
@ -1482,9 +1484,8 @@ getdns_context_set_idle_timeout(struct getdns_context *context, uint64_t timeout
{ {
RETURN_IF_NULL(context, GETDNS_RETURN_INVALID_PARAMETER); RETURN_IF_NULL(context, GETDNS_RETURN_INVALID_PARAMETER);
if (timeout == 0) { /* Shuold we enforce maximum based on edns-tcp-keepalive spec? */
return GETDNS_RETURN_INVALID_PARAMETER; /* 0 should be allowed as that is the default.*/
}
context->idle_timeout = timeout; context->idle_timeout = timeout;

View File

@ -89,6 +89,7 @@ typedef struct getdns_upstream {
/* How is this upstream doing? */ /* How is this upstream doing? */
size_t writes_done; size_t writes_done;
size_t responses_received; size_t responses_received;
uint64_t keepalive_timeout;
int to_retry; int to_retry;
int back_off; int back_off;

View File

@ -417,7 +417,8 @@ enum gldns_enum_edns_option
GLDNS_EDNS_DAU = 5, /* RFC6975 */ GLDNS_EDNS_DAU = 5, /* RFC6975 */
GLDNS_EDNS_DHU = 6, /* RFC6975 */ GLDNS_EDNS_DHU = 6, /* RFC6975 */
GLDNS_EDNS_N3U = 7, /* RFC6975 */ GLDNS_EDNS_N3U = 7, /* RFC6975 */
GLDNS_EDNS_CLIENT_SUBNET = 8 /* draft-vandergaast-edns-client-subnet */ GLDNS_EDNS_CLIENT_SUBNET = 8, /* draft-vandergaast-edns-client-subnet */
GLDNS_EDNS_KEEPALIVE = 11 /* draft-ietf-dnsop-edns-tcp-keepalive*/
}; };
typedef enum gldns_enum_edns_option gldns_edns_option; typedef enum gldns_enum_edns_option gldns_edns_option;

View File

@ -165,6 +165,7 @@ static gldns_lookup_table gldns_edns_options_data[] = {
{ 6, "DHU" }, { 6, "DHU" },
{ 7, "N3U" }, { 7, "N3U" },
{ 8, "edns-client-subnet" }, { 8, "edns-client-subnet" },
{ 11, "edns-tcp-keepalive"},
{ 0, NULL} { 0, NULL}
}; };
gldns_lookup_table* gldns_edns_options = gldns_edns_options_data; gldns_lookup_table* gldns_edns_options = gldns_edns_options_data;
@ -1833,6 +1834,25 @@ int gldns_wire2str_edns_subnet_print(char** s, size_t* sl, uint8_t* data,
return w; return w;
} }
int gldns_wire2str_edns_keepalive_print(char** s, size_t* sl, uint8_t* data,
size_t len)
{
int w = 0;
uint16_t timeout;
if(!(len == 0 || len == 2)) {
w += gldns_str_print(s, sl, "malformed keepalive ");
w += print_hex_buf(s, sl, data, len);
return w;
}
if(len == 0 ) {
w += gldns_str_print(s, sl, "no timeout value (only valid for client option) ");
} else {
timeout = gldns_read_uint16(data);
w += gldns_str_print(s, sl, "timeout value in units of 100ms %u", (int)timeout);
}
return w;
}
int gldns_wire2str_edns_option_print(char** s, size_t* sl, int gldns_wire2str_edns_option_print(char** s, size_t* sl,
uint16_t option_code, uint8_t* optdata, size_t optlen) uint16_t option_code, uint8_t* optdata, size_t optlen)
{ {
@ -1861,6 +1881,9 @@ int gldns_wire2str_edns_option_print(char** s, size_t* sl,
case GLDNS_EDNS_CLIENT_SUBNET: case GLDNS_EDNS_CLIENT_SUBNET:
w += gldns_wire2str_edns_subnet_print(s, sl, optdata, optlen); w += gldns_wire2str_edns_subnet_print(s, sl, optdata, optlen);
break; break;
case GLDNS_EDNS_KEEPALIVE:
w += gldns_wire2str_edns_keepalive_print(s, sl, optdata, optlen);
break;
default: default:
/* unknown option code */ /* unknown option code */
w += print_hex_buf(s, sl, optdata, optlen); w += print_hex_buf(s, sl, optdata, optlen);

View File

@ -128,6 +128,7 @@ network_req_init(getdns_network_req *net_req, getdns_dns_req *owner,
net_req->edns_maximum_udp_payload_size = edns_maximum_udp_payload_size; net_req->edns_maximum_udp_payload_size = edns_maximum_udp_payload_size;
net_req->max_udp_payload_size = edns_maximum_udp_payload_size != -1 net_req->max_udp_payload_size = edns_maximum_udp_payload_size != -1
? edns_maximum_udp_payload_size : 1432; ? edns_maximum_udp_payload_size : 1432;
net_req->keepalive_sent = 0;
net_req->write_queue_tail = NULL; net_req->write_queue_tail = NULL;
net_req->response_len = 0; net_req->response_len = 0;
net_req->base_query_option_sz = opt_options_size; net_req->base_query_option_sz = opt_options_size;

View File

@ -42,6 +42,7 @@
#include "gldns/pkthdr.h" #include "gldns/pkthdr.h"
#include "gldns/rrdef.h" #include "gldns/rrdef.h"
#include "gldns/str2wire.h" #include "gldns/str2wire.h"
#include "gldns/wire2str.h"
#include "rr-iter.h" #include "rr-iter.h"
#include "context.h" #include "context.h"
#include "util-internal.h" #include "util-internal.h"
@ -54,6 +55,8 @@
/* Don't currently have access to the context whilst doing handshake */ /* Don't currently have access to the context whilst doing handshake */
#define TIMEOUT_TLS 2500 #define TIMEOUT_TLS 2500
/* Arbritray number of message for EDNS keepalive resend*/
#define EDNS_KEEPALIVE_RESEND 5
static time_t secret_rollover_time = 0; static time_t secret_rollover_time = 0;
static uint32_t secret = 0; static uint32_t secret = 0;
@ -75,7 +78,6 @@ static void netreq_upstream_read_cb(void *userarg);
static void netreq_upstream_write_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);
static void stub_timeout_cb(void *userarg); static void stub_timeout_cb(void *userarg);
/*****************************/ /*****************************/
/* General utility functions */ /* General utility functions */
@ -145,6 +147,15 @@ attach_edns_client_subnet_private(getdns_network_req *req)
4, NULL); 4, NULL);
} }
static getdns_return_t
attach_edns_keepalive(getdns_network_req *req)
{
/* Client always sends length 0, omits the timeout */
return _getdns_network_req_add_upstream_option(req,
GLDNS_EDNS_KEEPALIVE,
0, NULL);
}
static getdns_return_t static getdns_return_t
attach_edns_cookie(getdns_network_req *req) attach_edns_cookie(getdns_network_req *req)
{ {
@ -183,9 +194,10 @@ attach_edns_cookie(getdns_network_req *req)
} }
/* Will find a matching OPT RR, but leaves the caller to validate it*/
static int static int
match_and_process_server_cookie( match_edns_opt_rr(uint16_t code, uint8_t *response, size_t response_len,
getdns_upstream *upstream, uint8_t *response, size_t response_len) uint8_t **position, uint16_t *option_len)
{ {
_getdns_rr_iter rr_iter_storage, *rr_iter; _getdns_rr_iter rr_iter_storage, *rr_iter;
uint8_t *pos; uint8_t *pos;
@ -211,7 +223,17 @@ match_and_process_server_cookie(
pos = rr_iter->rr_type + 8; pos = rr_iter->rr_type + 8;
/* OPT found, now search for the cookie option */ #if defined(STUB_DEBUG) && STUB_DEBUG
char str_spc[8192], *str = str_spc;
size_t str_len = sizeof(str_spc);
uint8_t *data = rr_iter->pos;
size_t data_len = rr_iter->nxt - rr_iter->pos;
(void) gldns_wire2str_rr_scan(
&data, &data_len, &str, &str_len, rr_iter->pkt, rr_iter->pkt_end - rr_iter->pkt);
DEBUG_STUB("OPT RR: %s", str_spc);
#endif
/* OPT found, now search for the specified option */
if (pos + 2 > rr_iter->nxt) if (pos + 2 > rr_iter->nxt)
return 1; /* FORMERR */ return 1; /* FORMERR */
@ -224,23 +246,39 @@ match_and_process_server_cookie(
opt_len = gldns_read_uint16(pos); pos += 2; opt_len = gldns_read_uint16(pos); pos += 2;
if (pos + opt_len > rr_iter->nxt) if (pos + opt_len > rr_iter->nxt)
return 1; /* FORMERR */ return 1; /* FORMERR */
if (opt_code == EDNS_COOKIE_OPCODE) if (opt_code == code)
break; break;
pos += opt_len; /* Skip unknown options */ pos += opt_len; /* Skip unknown options */
} }
if (pos >= rr_iter->nxt || opt_code != EDNS_COOKIE_OPCODE) if (pos >= rr_iter->nxt || opt_code != code)
return 0; /* Everything OK, just no cookie found. */ return 0; /* Everything OK, just no cookie found. */
*position = pos;
*option_len = opt_len;
return 2;
}
if (opt_len < 16 || opt_len > 40) /* TODO: Test combinations of EDNS0 options*/
static int
match_and_process_server_cookie(
getdns_upstream *upstream, uint8_t *response, size_t response_len)
{
uint8_t *position = NULL;
uint16_t option_len = 0;
int found = match_edns_opt_rr(EDNS_COOKIE_OPCODE, response,
response_len, &position, &option_len);
if (found != 2)
return found;
if (option_len < 16 || option_len > 40)
return 1; /* FORMERR */ return 1; /* FORMERR */
if (!upstream->has_client_cookie) if (!upstream->has_client_cookie)
return 1; /* Cookie reply, but we didn't sent one */ return 1; /* Cookie reply, but we didn't sent one */
if (memcmp(upstream->client_cookie, pos, 8) != 0) { if (memcmp(upstream->client_cookie, position, 8) != 0) {
if (!upstream->has_prev_client_cookie) if (!upstream->has_prev_client_cookie)
return 1; /* Cookie didn't match */ return 1; /* Cookie didn't match */
if (memcmp(upstream->prev_client_cookie, pos, 8) != 0) if (memcmp(upstream->prev_client_cookie, position, 8) != 0)
return 1; /* Previous cookie didn't match either */ return 1; /* Previous cookie didn't match either */
upstream->has_server_cookie = 0; upstream->has_server_cookie = 0;
@ -248,11 +286,42 @@ match_and_process_server_cookie(
* is for our previous client cookie * is for our previous client cookie
*/ */
} }
pos += 8; position += 8;
opt_len -= 8; option_len -= 8;
upstream->has_server_cookie = 1; upstream->has_server_cookie = 1;
upstream->server_cookie_len = opt_len; upstream->server_cookie_len = option_len;
(void) memcpy(upstream->server_cookie, pos, opt_len); (void) memcpy(upstream->server_cookie, position, option_len);
return 0;
}
static int
process_keepalive(
getdns_upstream *upstream, getdns_network_req *netreq,
uint8_t *response, size_t response_len)
{
uint8_t *position = NULL;
uint16_t option_len = 0;
int found = match_edns_opt_rr(GLDNS_EDNS_KEEPALIVE, response,
response_len, &position, &option_len);
if (found != 2) {
if (netreq->keepalive_sent == 1)
/* If no keepalive sent back, then we must use 0 idle timeout
as server does not support it.*/
upstream->keepalive_timeout = 0;
return found;
}
if (option_len != 2)
return 1; /* FORMERR */
/* Use server sent value unless the client specified a shorter one.
Convert to ms first (wire value has units of 100ms) */
uint64_t server_keepalive = ((uint64_t)gldns_read_uint16(position))*100;
if (netreq->owner->context->idle_timeout < server_keepalive)
upstream->keepalive_timeout = netreq->owner->context->idle_timeout;
else {
upstream->keepalive_timeout = server_keepalive;
DEBUG_STUB("*** %s: SERVER KEEPALIVE USED : %d ms\n",
__FUNCTION__, (int)server_keepalive);
}
return 0; return 0;
} }
@ -390,7 +459,7 @@ stub_cleanup(getdns_network_req *netreq)
netreq->write_queue_tail = NULL; netreq->write_queue_tail = NULL;
break; break;
} }
upstream_reschedule_events(upstream, netreq->owner->context->idle_timeout); upstream_reschedule_events(upstream, upstream->keepalive_timeout);
} }
static int static int
@ -446,18 +515,17 @@ _getdns_cancel_stub_request(getdns_network_req *netreq)
if (netreq->fd >= 0) close(netreq->fd); if (netreq->fd >= 0) close(netreq->fd);
} }
static void /* May be needed in future for better UDP error handling?*/
/*static void
stub_erred(getdns_network_req *netreq) stub_erred(getdns_network_req *netreq)
{ {
DEBUG_STUB("*** %s\n", __FUNCTION__); DEBUG_STUB("*** %s\n", __FUNCTION__);
stub_next_upstream(netreq); stub_next_upstream(netreq);
stub_cleanup(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); if (netreq->fd >= 0) close(netreq->fd);
netreq->state = NET_REQ_FINISHED; netreq->state = NET_REQ_FINISHED;
_getdns_check_dns_req_complete(netreq->owner); _getdns_check_dns_req_complete(netreq->owner);
} }*/
static void static void
stub_timeout_cb(void *userarg) stub_timeout_cb(void *userarg)
@ -651,6 +719,14 @@ stub_tcp_write(int fd, getdns_tcp_state *tcp, getdns_network_req *netreq)
if (netreq->owner->edns_client_subnet_private) if (netreq->owner->edns_client_subnet_private)
if (attach_edns_client_subnet_private(netreq)) if (attach_edns_client_subnet_private(netreq))
return STUB_OUT_OF_OPTIONS; return STUB_OUT_OF_OPTIONS;
if (netreq->upstream->writes_done == 0 &&
netreq->owner->context->idle_timeout != 0) {
/* Add the keepalive option to the first query on this connection*/
DEBUG_STUB("# %s: Requesting keepalive\n", __FUNCTION__);
if (attach_edns_keepalive(netreq))
return STUB_OUT_OF_OPTIONS;
netreq->keepalive_sent = 1;
}
} }
pkt_len = netreq->response - netreq->query; pkt_len = netreq->response - netreq->query;
/* We have an initialized packet buffer. /* We have an initialized packet buffer.
@ -1098,6 +1174,7 @@ stub_tls_write(getdns_upstream *upstream, getdns_tcp_state *tcp,
&netreq->upstream->netreq_by_query_id, &netreq->node)); &netreq->upstream->netreq_by_query_id, &netreq->node));
GLDNS_ID_SET(netreq->query, query_id); GLDNS_ID_SET(netreq->query, query_id);
/* TODO: Review if more EDNS0 handling can be centralised.*/
if (netreq->opt) { if (netreq->opt) {
_getdns_network_req_clear_upstream_options(netreq); _getdns_network_req_clear_upstream_options(netreq);
/* no limits on the max udp payload size with tcp */ /* no limits on the max udp payload size with tcp */
@ -1108,6 +1185,15 @@ stub_tls_write(getdns_upstream *upstream, getdns_tcp_state *tcp,
if (netreq->owner->edns_client_subnet_private) if (netreq->owner->edns_client_subnet_private)
if (attach_edns_client_subnet_private(netreq)) if (attach_edns_client_subnet_private(netreq))
return STUB_OUT_OF_OPTIONS; return STUB_OUT_OF_OPTIONS;
if (netreq->upstream->writes_done % EDNS_KEEPALIVE_RESEND == 0 &&
netreq->owner->context->idle_timeout != 0) {
/* Add the keepalive option to every nth query on this
connection */
DEBUG_STUB("# %s: Requesting keepalive\n", __FUNCTION__);
if (attach_edns_keepalive(netreq))
return STUB_OUT_OF_OPTIONS;
netreq->keepalive_sent = 1;
}
if (netreq->owner->tls_query_padding_blocksize > 1) { if (netreq->owner->tls_query_padding_blocksize > 1) {
pkt_len = netreq->response - netreq->query; pkt_len = netreq->response - netreq->query;
pkt_len += 4; /* this accounts for the OPTION-CODE and OPTION-LENGTH of the padding */ pkt_len += 4; /* this accounts for the OPTION-CODE and OPTION-LENGTH of the padding */
@ -1259,76 +1345,6 @@ stub_udp_write_cb(void *userarg)
stub_udp_read_cb, NULL, stub_timeout_cb)); stub_udp_read_cb, NULL, stub_timeout_cb));
} }
/**************************/
/* TCP callback functions*/
/**************************/
static void
stub_tcp_read_cb(void *userarg)
{
getdns_network_req *netreq = (getdns_network_req *)userarg;
getdns_dns_req *dnsreq = netreq->owner;
int q;
switch ((q = stub_tcp_read(netreq->fd, &netreq->tcp,
&dnsreq->context->mf))) {
case STUB_TCP_AGAIN:
return;
case STUB_TCP_ERROR:
stub_erred(netreq);
return;
default:
GETDNS_CLEAR_EVENT(dnsreq->loop, &netreq->event);
if (q != netreq->query_id)
return;
if (netreq->owner->edns_cookies &&
match_and_process_server_cookie(
netreq->upstream, netreq->tcp.read_buf,
netreq->tcp.read_pos - netreq->tcp.read_buf))
return; /* Client cookie didn't match? */
netreq->state = NET_REQ_FINISHED;
netreq->response = netreq->tcp.read_buf;
netreq->response_len =
netreq->tcp.read_pos - netreq->tcp.read_buf;
netreq->tcp.read_buf = NULL;
dnsreq->upstreams->current = 0;
netreq->debug_end_time = _getdns_get_time_as_uintt64();
stub_cleanup(netreq);
close(netreq->fd);
_getdns_check_dns_req_complete(dnsreq);
}
}
static void
stub_tcp_write_cb(void *userarg)
{
getdns_network_req *netreq = (getdns_network_req *)userarg;
getdns_dns_req *dnsreq = netreq->owner;
int q;
netreq->debug_start_time = _getdns_get_time_as_uintt64();
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->debug_udp = 0;
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;
}
}
/**************************/ /**************************/
/* Upstream callback functions*/ /* Upstream callback functions*/
/**************************/ /**************************/
@ -1381,6 +1397,18 @@ upstream_read_cb(void *userarg)
/* TODO[TLS]: I don't think we should do this for TCP. We should stay /* TODO[TLS]: I don't think we should do this for TCP. We should stay
* on a working connection until we hit a problem.*/ * on a working connection until we hit a problem.*/
upstream->upstreams->current = 0; upstream->upstreams->current = 0;
/* !THIS CODE NEEDS TESTING!*/
if (netreq->owner->edns_cookies &&
match_and_process_server_cookie(
netreq->upstream, netreq->tcp.read_buf,
netreq->tcp.read_pos - netreq->tcp.read_buf))
return; /* Client cookie didn't match? */
if ((netreq->owner->context->idle_timeout != 0) &&
process_keepalive(netreq->upstream, netreq, netreq->response,
netreq->response_len))
return;
netreq->debug_end_time = _getdns_get_time_as_uintt64(); netreq->debug_end_time = _getdns_get_time_as_uintt64();
/* This also reschedules events for the upstream*/ /* This also reschedules events for the upstream*/
@ -1644,6 +1672,7 @@ find_upstream_for_netreq(getdns_network_req *netreq)
continue; continue;
netreq->transport_current = i; netreq->transport_current = i;
netreq->upstream = upstream; netreq->upstream = upstream;
netreq->keepalive_sent = 0;
return fd; return fd;
} }
return -1; return -1;
@ -1795,8 +1824,7 @@ _getdns_submit_stub_request(getdns_network_req *netreq)
GETDNS_SCHEDULE_EVENT( GETDNS_SCHEDULE_EVENT(
dnsreq->loop, netreq->fd, dnsreq->context->timeout, dnsreq->loop, netreq->fd, dnsreq->context->timeout,
getdns_eventloop_event_init(&netreq->event, netreq, getdns_eventloop_event_init(&netreq->event, netreq,
NULL, (transport == GETDNS_TRANSPORT_UDP ? NULL, stub_udp_write_cb, stub_timeout_cb));
stub_udp_write_cb: stub_tcp_write_cb), stub_timeout_cb));
return GETDNS_RETURN_GOOD; return GETDNS_RETURN_GOOD;
case GETDNS_TRANSPORT_TLS: case GETDNS_TRANSPORT_TLS:

View File

@ -223,6 +223,8 @@ typedef struct getdns_network_req
int edns_maximum_udp_payload_size; int edns_maximum_udp_payload_size;
uint16_t max_udp_payload_size; uint16_t max_udp_payload_size;
size_t keepalive_sent;
/* Network requests scheduled to write after me */ /* Network requests scheduled to write after me */
struct getdns_network_req *write_queue_tail; struct getdns_network_req *write_queue_tail;

View File

@ -730,6 +730,23 @@ _getdns_create_call_reporting_dict(
} }
getdns_dict_destroy(address_debug); getdns_dict_destroy(address_debug);
if (transport != GETDNS_TRANSPORT_UDP) {
/* Report the idle timeout actually used on the connection. Must trim,
maximum used in practice is 6553500ms, but this is stored in a uint64_t.*/
if (netreq->upstream->keepalive_timeout > UINT32_MAX) {
if (getdns_dict_set_int( netreq_debug, "idle timeout in ms (overflow)", UINT32_MAX)) {
getdns_dict_destroy(netreq_debug);
return NULL;
}
} else{
uint32_t idle_timeout = netreq->upstream->keepalive_timeout;
if (getdns_dict_set_int( netreq_debug, "idle timeout in ms", idle_timeout)) {
getdns_dict_destroy(netreq_debug);
return NULL;
}
}
}
if (netreq->upstream->transport != GETDNS_TRANSPORT_TLS) if (netreq->upstream->transport != GETDNS_TRANSPORT_TLS)
return netreq_debug; return netreq_debug;