From 0b388872ea5701d90c8490aefad8c54103822d70 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Sat, 31 Oct 2015 19:04:56 +0900 Subject: [PATCH] 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) --- configure.ac | 2 + src/request-internal.c | 77 ++++++++++++++++++++++++++++++++++++--- src/stub.c | 83 ++++++++++++++++++++---------------------- src/types-internal.h | 18 +++++++++ 4 files changed, 130 insertions(+), 50 deletions(-) diff --git a/configure.ac b/configure.ac index b4409fa3..613eec79 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/src/request-internal.c b/src/request-internal.c index 30156724..42c296ec 100644 --- a/src/request-internal.c +++ b/src/request-internal.c @@ -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; } diff --git a/src/stub.c b/src/stub.c index 5c9b6b5a..582c02c5 100644 --- a/src/stub.c +++ b/src/stub.c @@ -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)) { diff --git a/src/types-internal.h b/src/types-internal.h index 93b97973..202fbc61 100644 --- a/src/types-internal.h +++ b/src/types-internal.h @@ -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 */