clarify per-query options vs. per-upstream options

Sending DNS cookies was overwriting any existing options (DNS OPT) in
the outbound query.

Also, DNS cookies may not be the only option that gets set
per-upstream (instead of per-query).

This changeset establishes a set of per-query options (established at
the time of the query), and a buffer of additional space for adding
options based on the upstream is in use.

The size of this buffer is defined at configure time (defaults to 3000
octets).

Just before a query is sent out, we add the per-upstream options to
the query.

Note: we're also standardizing the query in tls too, even though we're
not sending any upstream options in that case at the moment
(edns_cookies are much weaker than TLS itself)
This commit is contained in:
Daniel Kahn Gillmor 2015-10-31 19:04:56 +09:00
parent 3e90795680
commit 0b388872ea
4 changed files with 130 additions and 50 deletions

View File

@ -408,6 +408,8 @@ esac
AC_DEFINE_UNQUOTED([EDNS_COOKIE_OPCODE], [10], [The edns cookie option code.]) AC_DEFINE_UNQUOTED([EDNS_COOKIE_OPCODE], [10], [The edns cookie option code.])
AC_DEFINE_UNQUOTED([EDNS_COOKIE_ROLLOVER_TIME], [(24 * 60 * 60)], [How often the edns client cookie is refreshed.]) AC_DEFINE_UNQUOTED([EDNS_COOKIE_ROLLOVER_TIME], [(24 * 60 * 60)], [How often the edns client cookie is refreshed.])
AC_DEFINE_UNQUOTED([MAXIMUM_UPSTREAM_OPTION_SPACE], [3000], [limit for dynamically-generated DNS options])
my_with_libunbound=1 my_with_libunbound=1
AC_ARG_ENABLE(stub-only, AC_HELP_STRING([--enable-stub-only], [Restricts resolution modes to STUB (which will be the default mode). Removes the libunbound dependency.])) AC_ARG_ENABLE(stub-only, AC_HELP_STRING([--enable-stub-only], [Restricts resolution modes to STUB (which will be the default mode). Removes the libunbound dependency.]))
case "$enable_stub_only" in case "$enable_stub_only" in

View File

@ -113,6 +113,7 @@ network_req_init(getdns_network_req *net_req, getdns_dns_req *owner,
? edns_maximum_udp_payload_size : 1432; ? edns_maximum_udp_payload_size : 1432;
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->wire_data_sz = wire_data_sz; net_req->wire_data_sz = wire_data_sz;
if (max_query_sz) { if (max_query_sz) {
@ -184,6 +185,75 @@ network_req_init(getdns_network_req *net_req, getdns_dns_req *owner,
return r; return r;
} }
/* req->opt + 9 is the length; req->opt + 11 is the start of the
options.
clear_upstream_options() goes back to the per-query options.
*/
void
_getdns_network_req_clear_upstream_options(getdns_network_req * req)
{
size_t pktlen;
if (req->opt) {
gldns_write_uint16(req->opt + 9, req->base_query_option_sz);
req->response = req->opt + 11 + req->base_query_option_sz;
pktlen = req->response - req->query;
gldns_write_uint16(req->query - 2, pktlen);
}
}
/* add_upstream_option appends an option that is derived at send time.
(you can send data as NULL and it will fill with all zeros) */
getdns_return_t
_getdns_network_req_add_upstream_option(getdns_network_req * req, uint16_t code, uint16_t sz, const void* data)
{
uint16_t oldlen;
uint32_t newlen;
uint32_t pktlen;
size_t cur_upstream_option_sz;
/* if no options are set, we can't add upstream options */
if (!req->opt)
return GETDNS_RETURN_GENERIC_ERROR;
/* if TCP, no overflow allowed for length field
https://tools.ietf.org/html/rfc1035#section-4.2.2 */
pktlen = req->response - req->query;
pktlen += 4 + sz;
if (pktlen > UINT16_MAX)
return GETDNS_RETURN_GENERIC_ERROR;
/* no overflow allowed for OPT size either (maybe this is overkill
given the above check?) */
oldlen = gldns_read_uint16(req->opt + 9);
newlen = oldlen + 4 + sz;
if (newlen > UINT16_MAX)
return GETDNS_RETURN_GENERIC_ERROR;
/* avoid overflowing MAXIMUM_UPSTREAM_OPTION_SPACE */
cur_upstream_option_sz = (size_t)oldlen - req->base_query_option_sz;
if (cur_upstream_option_sz + 4 + sz > MAXIMUM_UPSTREAM_OPTION_SPACE)
return GETDNS_RETURN_GENERIC_ERROR;
/* actually add the option: */
gldns_write_uint16(req->opt + 11 + oldlen, code);
gldns_write_uint16(req->opt + 11 + oldlen + 2, sz);
if (data != NULL)
memcpy(req->opt + 11 + oldlen + 4, data, sz);
else
memset(req->opt + 11 + oldlen + 4, 0, sz);
gldns_write_uint16(req->opt + 9, newlen);
/* the response should start right after the options end: */
req->response = req->opt + 11 + newlen;
/* for TCP, adjust the size of the wire format itself: */
gldns_write_uint16(req->query - 2, pktlen);
return GETDNS_RETURN_GOOD;
}
void void
_getdns_dns_req_free(getdns_dns_req * req) _getdns_dns_req_free(getdns_dns_req * req)
{ {
@ -323,12 +393,7 @@ _getdns_dns_req_new(getdns_context *context, getdns_eventloop *loop,
max_query_sz = ( GLDNS_HEADER_SIZE max_query_sz = ( GLDNS_HEADER_SIZE
+ strlen(name) + 1 + 4 /* dname always smaller then strlen(name) + 1 */ + strlen(name) + 1 + 4 /* dname always smaller then strlen(name) + 1 */
+ 12 + opt_options_size /* space needed for OPT (if needed) */ + 12 + opt_options_size /* space needed for OPT (if needed) */
+ ( !edns_cookies ? 0 + MAXIMUM_UPSTREAM_OPTION_SPACE
: 2 /* EDNS0 Option Code */
+ 2 /* Option length = 8 + 16 = 24 */
+ 8 /* client cookie */
+ 16 /* server cookie */
)
/* TODO: TSIG */ /* TODO: TSIG */
+ 7) / 8 * 8; + 7) / 8 * 8;
} }

View File

@ -46,6 +46,7 @@
#include "util-internal.h" #include "util-internal.h"
#include "general.h" #include "general.h"
#define STUB_OUT_OF_OPTIONS -5 /* upstream options exceeded MAXIMUM_UPSTREAM_OPTION_SPACE */
#define STUB_TLS_SETUP_ERROR -4 #define STUB_TLS_SETUP_ERROR -4
#define STUB_TCP_AGAIN -3 #define STUB_TCP_AGAIN -3
#define STUB_TCP_ERROR -2 #define STUB_TCP_ERROR -2
@ -129,9 +130,13 @@ calc_new_cookie(getdns_upstream *upstream, uint8_t *cookie)
cookie[i % 8] ^= md_value[i]; cookie[i % 8] ^= md_value[i];
} }
static uint8_t * static getdns_return_t
attach_edns_cookie(getdns_upstream *upstream, uint8_t *opt) attach_edns_cookie(getdns_network_req *req)
{ {
getdns_upstream *upstream = req->upstream;
uint16_t sz;
void* val;
uint8_t buf[8 + 32]; /* server cookies can be no larger than 32 bytes */
rollover_secret(); rollover_secret();
if (!upstream->has_client_cookie) { if (!upstream->has_client_cookie) {
@ -139,12 +144,8 @@ attach_edns_cookie(getdns_upstream *upstream, uint8_t *opt)
upstream->secret = secret; upstream->secret = secret;
upstream->has_client_cookie = 1; upstream->has_client_cookie = 1;
gldns_write_uint16(opt + 9, 12); /* rdata len */ sz = 8;
gldns_write_uint16(opt + 11, EDNS_COOKIE_OPCODE); val = upstream->client_cookie;
gldns_write_uint16(opt + 13, 8); /* opt len */
memcpy(opt + 15, upstream->client_cookie, 8);
return opt + 23;
} else if (upstream->secret != secret) { } else if (upstream->secret != secret) {
memcpy( upstream->prev_client_cookie memcpy( upstream->prev_client_cookie
, upstream->client_cookie, 8); , upstream->client_cookie, 8);
@ -152,29 +153,19 @@ attach_edns_cookie(getdns_upstream *upstream, uint8_t *opt)
calc_new_cookie(upstream, upstream->client_cookie); calc_new_cookie(upstream, upstream->client_cookie);
upstream->secret = secret; upstream->secret = secret;
gldns_write_uint16(opt + 9, 12); /* rdata len */ sz = 8;
gldns_write_uint16(opt + 11, EDNS_COOKIE_OPCODE); val = upstream->client_cookie;
gldns_write_uint16(opt + 13, 8); /* opt len */
memcpy(opt + 15, upstream->client_cookie, 8);
return opt + 23;
} else if (!upstream->has_server_cookie) { } else if (!upstream->has_server_cookie) {
gldns_write_uint16(opt + 9, 12); /* rdata len */ sz = 8;
gldns_write_uint16(opt + 11, EDNS_COOKIE_OPCODE); val = upstream->client_cookie;
gldns_write_uint16(opt + 13, 8); /* opt len */
memcpy(opt + 15, upstream->client_cookie, 8);
return opt + 23;
} else { } else {
gldns_write_uint16( opt + 9, 12 /* rdata len */ sz = 8 + upstream->server_cookie_len;
+ upstream->server_cookie_len); memcpy(buf, upstream->client_cookie, 8);
gldns_write_uint16(opt + 11, EDNS_COOKIE_OPCODE); memcpy(buf+8, upstream->server_cookie, upstream->server_cookie_len);
gldns_write_uint16(opt + 13, 8 /* opt len */ val = buf;
+ upstream->server_cookie_len);
memcpy(opt + 15, upstream->client_cookie, 8);
memcpy(opt + 23, upstream->server_cookie
, upstream->server_cookie_len);
return opt + 23+ upstream->server_cookie_len;
} }
return _getdns_network_req_add_upstream_option(req, EDNS_COOKIE_OPCODE, sz, val);
} }
static int static int
@ -681,7 +672,7 @@ static int
stub_tcp_write(int fd, getdns_tcp_state *tcp, getdns_network_req *netreq) stub_tcp_write(int fd, getdns_tcp_state *tcp, getdns_network_req *netreq)
{ {
size_t pkt_len = netreq->response - netreq->query; size_t pkt_len;
ssize_t written; ssize_t written;
uint16_t query_id; uint16_t query_id;
intptr_t query_id_intptr; intptr_t query_id_intptr;
@ -704,16 +695,15 @@ stub_tcp_write(int fd, getdns_tcp_state *tcp, getdns_network_req *netreq)
GLDNS_ID_SET(netreq->query, query_id); GLDNS_ID_SET(netreq->query, query_id);
if (netreq->opt) { if (netreq->opt) {
_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 */
gldns_write_uint16(netreq->opt + 3, 65535); gldns_write_uint16(netreq->opt + 3, 65535);
if (netreq->owner->edns_cookies) { if (netreq->owner->edns_cookies)
netreq->response = attach_edns_cookie( if (attach_edns_cookie(netreq))
netreq->upstream, netreq->opt); return STUB_OUT_OF_OPTIONS;
pkt_len = netreq->response - netreq->query;
gldns_write_uint16(netreq->query - 2, pkt_len);
}
} }
pkt_len = netreq->response - netreq->query;
/* We have an initialized packet buffer. /* We have an initialized packet buffer.
* Lets see how much of it we can write * Lets see how much of it we can write
*/ */
@ -1126,7 +1116,7 @@ static int
stub_tls_write(getdns_upstream *upstream, getdns_tcp_state *tcp, stub_tls_write(getdns_upstream *upstream, getdns_tcp_state *tcp,
getdns_network_req *netreq) getdns_network_req *netreq)
{ {
size_t pkt_len = netreq->response - netreq->query; size_t pkt_len;
ssize_t written; ssize_t written;
uint16_t query_id; uint16_t query_id;
intptr_t query_id_intptr; intptr_t query_id_intptr;
@ -1156,10 +1146,16 @@ 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);
if (netreq->opt) if (netreq->opt) {
_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 */
gldns_write_uint16(netreq->opt + 3, 65535); gldns_write_uint16(netreq->opt + 3, 65535);
/* we do not edns_cookie over TLS, since TLS
* provides stronger guarantees than cookies
* already */
}
pkt_len = netreq->response - netreq->query;
/* We have an initialized packet buffer. /* We have an initialized packet buffer.
* Lets see how much of it we can write */ * Lets see how much of it we can write */
@ -1248,25 +1244,24 @@ stub_udp_write_cb(void *userarg)
DEBUG_STUB("%s\n", __FUNCTION__); DEBUG_STUB("%s\n", __FUNCTION__);
getdns_network_req *netreq = (getdns_network_req *)userarg; getdns_network_req *netreq = (getdns_network_req *)userarg;
getdns_dns_req *dnsreq = netreq->owner; getdns_dns_req *dnsreq = netreq->owner;
size_t pkt_len = netreq->response - netreq->query; size_t pkt_len;
GETDNS_CLEAR_EVENT(dnsreq->loop, &netreq->event); GETDNS_CLEAR_EVENT(dnsreq->loop, &netreq->event);
netreq->query_id = arc4random(); netreq->query_id = arc4random();
GLDNS_ID_SET(netreq->query, netreq->query_id); GLDNS_ID_SET(netreq->query, netreq->query_id);
if (netreq->opt) { if (netreq->opt) {
_getdns_network_req_clear_upstream_options(netreq);
if (netreq->edns_maximum_udp_payload_size == -1) if (netreq->edns_maximum_udp_payload_size == -1)
gldns_write_uint16(netreq->opt + 3, gldns_write_uint16(netreq->opt + 3,
( netreq->max_udp_payload_size = ( netreq->max_udp_payload_size =
netreq->upstream->addr.ss_family == AF_INET6 netreq->upstream->addr.ss_family == AF_INET6
? 1232 : 1432)); ? 1232 : 1432));
if (netreq->owner->edns_cookies) { if (netreq->owner->edns_cookies)
netreq->response = attach_edns_cookie( if (attach_edns_cookie(netreq))
netreq->upstream, netreq->opt); return; /* too many upstream options */
pkt_len = netreq->response - netreq->query;
}
} }
pkt_len = netreq->response - netreq->query;
if ((ssize_t)pkt_len != sendto(netreq->fd, netreq->query, pkt_len, 0, if ((ssize_t)pkt_len != sendto(netreq->fd, netreq->query, pkt_len, 0,
(struct sockaddr *)&netreq->upstream->addr, (struct sockaddr *)&netreq->upstream->addr,
netreq->upstream->addr_len)) { netreq->upstream->addr_len)) {

View File

@ -232,6 +232,19 @@ typedef struct getdns_network_req
*/ */
uint8_t *query; uint8_t *query;
uint8_t *opt; /* offset of OPT RR in query */ uint8_t *opt; /* offset of OPT RR in query */
/* each network_req has a set of base options that are
* specific to the query, which are static and included when
* the network_req is created. When the query is sent out to
* a given upstream, some additional options are added that
* are specific to the upstream. There can be at most
* GETDNS_MAXIMUM_UPSTREAM_OPTION_SPACE bytes of
* upstream-specific options.
* use _getdns_network_req_clear_upstream_options() and
* _getdns_network_req_add_upstream_option() to fiddle with the
*/
size_t base_query_option_sz;
size_t response_len; size_t response_len;
uint8_t *response; uint8_t *response;
size_t wire_data_sz; size_t wire_data_sz;
@ -344,5 +357,10 @@ getdns_dns_req *_getdns_dns_req_new(getdns_context *context, getdns_eventloop *l
void _getdns_dns_req_free(getdns_dns_req * req); void _getdns_dns_req_free(getdns_dns_req * req);
/* network request utils */
getdns_return_t _getdns_network_req_add_upstream_option(getdns_network_req * req,
uint16_t code, uint16_t sz, const void* data);
void _getdns_network_req_clear_upstream_options(getdns_network_req * req);
#endif #endif
/* types-internal.h */ /* types-internal.h */