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_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
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

View File

@ -113,6 +113,7 @@ network_req_init(getdns_network_req *net_req, getdns_dns_req *owner,
? edns_maximum_udp_payload_size : 1432;
net_req->write_queue_tail = NULL;
net_req->response_len = 0;
net_req->base_query_option_sz = opt_options_size;
net_req->wire_data_sz = wire_data_sz;
if (max_query_sz) {
@ -184,6 +185,75 @@ network_req_init(getdns_network_req *net_req, getdns_dns_req *owner,
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
_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
+ strlen(name) + 1 + 4 /* dname always smaller then strlen(name) + 1 */
+ 12 + opt_options_size /* space needed for OPT (if needed) */
+ ( !edns_cookies ? 0
: 2 /* EDNS0 Option Code */
+ 2 /* Option length = 8 + 16 = 24 */
+ 8 /* client cookie */
+ 16 /* server cookie */
)
+ MAXIMUM_UPSTREAM_OPTION_SPACE
/* TODO: TSIG */
+ 7) / 8 * 8;
}

View File

@ -46,6 +46,7 @@
#include "util-internal.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_TCP_AGAIN -3
#define STUB_TCP_ERROR -2
@ -129,9 +130,13 @@ calc_new_cookie(getdns_upstream *upstream, uint8_t *cookie)
cookie[i % 8] ^= md_value[i];
}
static uint8_t *
attach_edns_cookie(getdns_upstream *upstream, uint8_t *opt)
static getdns_return_t
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();
if (!upstream->has_client_cookie) {
@ -139,12 +144,8 @@ attach_edns_cookie(getdns_upstream *upstream, uint8_t *opt)
upstream->secret = secret;
upstream->has_client_cookie = 1;
gldns_write_uint16(opt + 9, 12); /* rdata len */
gldns_write_uint16(opt + 11, EDNS_COOKIE_OPCODE);
gldns_write_uint16(opt + 13, 8); /* opt len */
memcpy(opt + 15, upstream->client_cookie, 8);
return opt + 23;
sz = 8;
val = upstream->client_cookie;
} else if (upstream->secret != secret) {
memcpy( upstream->prev_client_cookie
, upstream->client_cookie, 8);
@ -152,29 +153,19 @@ attach_edns_cookie(getdns_upstream *upstream, uint8_t *opt)
calc_new_cookie(upstream, upstream->client_cookie);
upstream->secret = secret;
gldns_write_uint16(opt + 9, 12); /* rdata len */
gldns_write_uint16(opt + 11, EDNS_COOKIE_OPCODE);
gldns_write_uint16(opt + 13, 8); /* opt len */
memcpy(opt + 15, upstream->client_cookie, 8);
return opt + 23;
sz = 8;
val = upstream->client_cookie;
} else if (!upstream->has_server_cookie) {
gldns_write_uint16(opt + 9, 12); /* rdata len */
gldns_write_uint16(opt + 11, EDNS_COOKIE_OPCODE);
gldns_write_uint16(opt + 13, 8); /* opt len */
memcpy(opt + 15, upstream->client_cookie, 8);
return opt + 23;
sz = 8;
val = upstream->client_cookie;
} else {
gldns_write_uint16( opt + 9, 12 /* rdata len */
+ upstream->server_cookie_len);
gldns_write_uint16(opt + 11, EDNS_COOKIE_OPCODE);
gldns_write_uint16(opt + 13, 8 /* opt len */
+ 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;
sz = 8 + upstream->server_cookie_len;
memcpy(buf, upstream->client_cookie, 8);
memcpy(buf+8, upstream->server_cookie, upstream->server_cookie_len);
val = buf;
}
return _getdns_network_req_add_upstream_option(req, EDNS_COOKIE_OPCODE, sz, val);
}
static int
@ -681,7 +672,7 @@ static int
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;
uint16_t query_id;
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);
if (netreq->opt) {
_getdns_network_req_clear_upstream_options(netreq);
/* 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);
}
if (netreq->owner->edns_cookies)
if (attach_edns_cookie(netreq))
return STUB_OUT_OF_OPTIONS;
}
pkt_len = netreq->response - netreq->query;
/* We have an initialized packet buffer.
* 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,
getdns_network_req *netreq)
{
size_t pkt_len = netreq->response - netreq->query;
size_t pkt_len;
ssize_t written;
uint16_t query_id;
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));
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 */
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.
* Lets see how much of it we can write */
@ -1248,25 +1244,24 @@ stub_udp_write_cb(void *userarg)
DEBUG_STUB("%s\n", __FUNCTION__);
getdns_network_req *netreq = (getdns_network_req *)userarg;
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);
netreq->query_id = arc4random();
GLDNS_ID_SET(netreq->query, netreq->query_id);
if (netreq->opt) {
_getdns_network_req_clear_upstream_options(netreq);
if (netreq->edns_maximum_udp_payload_size == -1)
gldns_write_uint16(netreq->opt + 3,
( netreq->max_udp_payload_size =
netreq->upstream->addr.ss_family == AF_INET6
? 1232 : 1432));
if (netreq->owner->edns_cookies) {
netreq->response = attach_edns_cookie(
netreq->upstream, netreq->opt);
pkt_len = netreq->response - netreq->query;
}
if (netreq->owner->edns_cookies)
if (attach_edns_cookie(netreq))
return; /* too many upstream options */
}
pkt_len = netreq->response - netreq->query;
if ((ssize_t)pkt_len != sendto(netreq->fd, netreq->query, pkt_len, 0,
(struct sockaddr *)&netreq->upstream->addr,
netreq->upstream->addr_len)) {

View File

@ -232,6 +232,19 @@ typedef struct getdns_network_req
*/
uint8_t *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;
uint8_t *response;
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);
/* 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
/* types-internal.h */