diff --git a/src/const-info.c b/src/const-info.c index 6265c523..0c6cb802 100644 --- a/src/const-info.c +++ b/src/const-info.c @@ -248,6 +248,7 @@ static struct const_name_info consts_name_info[] = { { "GETDNS_OPCODE_STATUS", 2 }, { "GETDNS_OPCODE_UPDATE", 5 }, { "GETDNS_RCODE_BADALG", 21 }, + { "GETDNS_RCODE_BADCOOKIE", 23 }, { "GETDNS_RCODE_BADKEY", 17 }, { "GETDNS_RCODE_BADMODE", 19 }, { "GETDNS_RCODE_BADNAME", 20 }, @@ -255,7 +256,6 @@ static struct const_name_info consts_name_info[] = { { "GETDNS_RCODE_BADTIME", 18 }, { "GETDNS_RCODE_BADTRUNC", 22 }, { "GETDNS_RCODE_BADVERS", 16 }, - { "GETDNS_RCODE_COOKIE", 23 }, { "GETDNS_RCODE_FORMERR", 1 }, { "GETDNS_RCODE_NOERROR", 0 }, { "GETDNS_RCODE_NOTAUTH", 9 }, diff --git a/src/context.c b/src/context.c index e833c03f..dcb1aacf 100644 --- a/src/context.c +++ b/src/context.c @@ -953,9 +953,12 @@ upstream_init(getdns_upstream *upstream, (void) getdns_eventloop_event_init( &upstream->finished_event, upstream, NULL, NULL, NULL); - upstream->has_client_cookie = 0; - upstream->has_prev_client_cookie = 0; - upstream->has_server_cookie = 0; + upstream->server_cookie_len = 0; + (void) memset(&upstream->server_cookie, 0, + sizeof(upstream->server_cookie)); + upstream->src_addr_checked = 0; + (void) memset(&upstream->src_addr, 0, sizeof(upstream->src_addr)); + upstream->src_addr_len = 0; upstream->tsig_alg = GETDNS_NO_TSIG; upstream->tsig_dname_len = 0; diff --git a/src/context.h b/src/context.h index f97884d3..2d9cd879 100644 --- a/src/context.h +++ b/src/context.h @@ -243,15 +243,13 @@ typedef struct getdns_upstream { unsigned is_sync_loop : 1; /* EDNS cookies */ - uint32_t secret; - uint8_t client_cookie[8]; - uint8_t prev_client_cookie[8]; - uint8_t server_cookie[32]; - - unsigned has_client_cookie : 1; - unsigned has_prev_client_cookie : 1; - unsigned has_server_cookie : 1; - unsigned server_cookie_len : 5; + uint8_t server_cookie[40]; + size_t server_cookie_len; + + uint64_t src_addr_checked; + struct sockaddr_storage src_addr; + socklen_t src_addr_len; + char src_addr_str[INET6_ADDRSTRLEN]; /* TSIG */ uint8_t tsig_dname[256]; diff --git a/src/dict.c b/src/dict.c index 64de8da2..9caee781 100644 --- a/src/dict.c +++ b/src/dict.c @@ -1036,21 +1036,21 @@ static int _getdns_print_rcode(gldns_buffer *buf, uint32_t rcode) { static const char *rcodes[] = { - " GETDNS_RCODE_NOERROR" , " GETDNS_RCODE_FORMERR" , - " GETDNS_RCODE_SERVFAIL", " GETDNS_RCODE_NXDOMAIN", - " GETDNS_RCODE_NOTIMP" , " GETDNS_RCODE_REFUSED" , - " GETDNS_RCODE_YXDOMAIN", " GETDNS_RCODE_YXRRSET" , - " GETDNS_RCODE_NXRRSET" , " GETDNS_RCODE_NOTAUTH" , + " GETDNS_RCODE_NOERROR" , " GETDNS_RCODE_FORMERR" , + " GETDNS_RCODE_SERVFAIL", " GETDNS_RCODE_NXDOMAIN" , + " GETDNS_RCODE_NOTIMP" , " GETDNS_RCODE_REFUSED" , + " GETDNS_RCODE_YXDOMAIN", " GETDNS_RCODE_YXRRSET" , + " GETDNS_RCODE_NXRRSET" , " GETDNS_RCODE_NOTAUTH" , " GETDNS_RCODE_NOTZONE" , - " GETDNS_RCODE_BADSIG" , " GETDNS_RCODE_BADKEY" , - " GETDNS_RCODE_BADTIME" , " GETDNS_RCODE_BADMODE" , - " GETDNS_RCODE_BADNAME" , " GETDNS_RCODE_BADALG" , - " GETDNS_RCODE_BADTRUNC" + " GETDNS_RCODE_BADSIG" , " GETDNS_RCODE_BADKEY" , + " GETDNS_RCODE_BADTIME" , " GETDNS_RCODE_BADMODE" , + " GETDNS_RCODE_BADNAME" , " GETDNS_RCODE_BADALG" , + " GETDNS_RCODE_BADTRUNC", " GETDNS_RCODE_BADCOOKIE" }; if (rcode <= 10) (void) gldns_buffer_printf(buf, "%s", rcodes[rcode]); - else if (rcode >= 16 && rcode <= 22) - (void) gldns_buffer_printf(buf, "%s", rcodes[rcode-6]); + else if (rcode >= 16 && rcode <= 23) + (void) gldns_buffer_printf(buf, "%s", rcodes[rcode-5]); else return 0; return 1; @@ -1156,6 +1156,11 @@ getdns_pp_dict(gldns_buffer * buf, size_t indent, if (!json && strcmp(item->node.key, "rcode") == 0 && _getdns_print_rcode(buf, item->i.data.n)) break; + if (!json && + strcmp(item->node.key, "extended_rcode") == 0 && + item->i.data.n >= 16 && + _getdns_print_rcode(buf, item->i.data.n)) + break; if (gldns_buffer_printf( buf,(json < 2 ? " %d" : "%d"), item->i.data.n) < 0) return -1; diff --git a/src/getdns/getdns.h.in b/src/getdns/getdns.h.in index b2702d43..1cc462b7 100644 --- a/src/getdns/getdns.h.in +++ b/src/getdns/getdns.h.in @@ -473,26 +473,26 @@ typedef enum getdns_callback_type_t { * \defgroup rcodes Rcodes * @{ */ -#define GETDNS_RCODE_NOERROR 0 -#define GETDNS_RCODE_FORMERR 1 -#define GETDNS_RCODE_SERVFAIL 2 -#define GETDNS_RCODE_NXDOMAIN 3 -#define GETDNS_RCODE_NOTIMP 4 -#define GETDNS_RCODE_REFUSED 5 -#define GETDNS_RCODE_YXDOMAIN 6 -#define GETDNS_RCODE_YXRRSET 7 -#define GETDNS_RCODE_NXRRSET 8 -#define GETDNS_RCODE_NOTAUTH 9 -#define GETDNS_RCODE_NOTZONE 10 -#define GETDNS_RCODE_BADVERS 16 -#define GETDNS_RCODE_BADSIG 16 -#define GETDNS_RCODE_BADKEY 17 -#define GETDNS_RCODE_BADTIME 18 -#define GETDNS_RCODE_BADMODE 19 -#define GETDNS_RCODE_BADNAME 20 -#define GETDNS_RCODE_BADALG 21 -#define GETDNS_RCODE_BADTRUNC 22 -#define GETDNS_RCODE_COOKIE 23 +#define GETDNS_RCODE_NOERROR 0 +#define GETDNS_RCODE_FORMERR 1 +#define GETDNS_RCODE_SERVFAIL 2 +#define GETDNS_RCODE_NXDOMAIN 3 +#define GETDNS_RCODE_NOTIMP 4 +#define GETDNS_RCODE_REFUSED 5 +#define GETDNS_RCODE_YXDOMAIN 6 +#define GETDNS_RCODE_YXRRSET 7 +#define GETDNS_RCODE_NXRRSET 8 +#define GETDNS_RCODE_NOTAUTH 9 +#define GETDNS_RCODE_NOTZONE 10 +#define GETDNS_RCODE_BADVERS 16 +#define GETDNS_RCODE_BADSIG 16 +#define GETDNS_RCODE_BADKEY 17 +#define GETDNS_RCODE_BADTIME 18 +#define GETDNS_RCODE_BADMODE 19 +#define GETDNS_RCODE_BADNAME 20 +#define GETDNS_RCODE_BADALG 21 +#define GETDNS_RCODE_BADTRUNC 22 +#define GETDNS_RCODE_BADCOOKIE 23 /** @} */ diff --git a/src/request-internal.c b/src/request-internal.c index f933e57f..8ddb1ac6 100644 --- a/src/request-internal.c +++ b/src/request-internal.c @@ -147,6 +147,7 @@ netreq_reset(getdns_network_req *net_req) net_req->dnssec_status = GETDNS_DNSSEC_INDETERMINATE; net_req->tsig_status = GETDNS_DNSSEC_INDETERMINATE; net_req->response_len = 0; + net_req->response_opt = NULL; /* Some fields to record info for return_call_reporting */ net_req->debug_start_time = 0; net_req->debug_end_time = 0; @@ -208,6 +209,9 @@ network_req_init(getdns_network_req *net_req, getdns_dns_req *owner, net_req->transport_current = 0; memset(&net_req->event, 0, sizeof(net_req->event)); net_req->keepalive_sent = 0; + net_req->badcookie_retry = 0; + net_req->cookie_sent = 0; + memset(&net_req->client_cookie, 0, sizeof(net_req->client_cookie)); net_req->write_queue_tail = NULL; /* Some fields to record info for return_call_reporting */ net_req->debug_tls_auth_status = GETDNS_AUTH_NONE; @@ -308,6 +312,7 @@ _getdns_network_req_clear_upstream_options(getdns_network_req * req) req->response = req->opt + 11 + req->base_query_option_sz; pktlen = req->response - req->query; gldns_write_uint16(req->query - 2, (uint16_t) pktlen); + req->response_opt = NULL; } } diff --git a/src/stub.c b/src/stub.c index 24aa0795..d76f30a1 100644 --- a/src/stub.c +++ b/src/stub.c @@ -33,6 +33,10 @@ #include "config.h" +#ifndef USE_WINSOCK +#include +#endif + /* Intercept and do not sent out COM DS queries with TLS * For debugging purposes only. Never commit with this turned on. */ @@ -53,6 +57,7 @@ #include "general.h" #include "pubkey-pinning.h" + /* WSA TODO: * STUB_TCP_RETRY added to deal with edge triggered event loops (versus * level triggered). See also lines containing WSA TODO below... @@ -73,10 +78,6 @@ /* Arbritray number of message for EDNS keepalive resend*/ #define EDNS_KEEPALIVE_RESEND 5 -static time_t secret_rollover_time = 0; -static uint32_t secret = 0; -static uint32_t prev_secret = 0; - static void upstream_read_cb(void *userarg); static void upstream_write_cb(void *userarg); static void upstream_idle_timeout_cb(void *userarg); @@ -97,47 +98,6 @@ uint64_t _getdns_get_time_as_uintt64(); /* General utility functions */ /*****************************/ -static void -rollover_secret() -{ - time_t now = 0; - - /* Create and roll server secrets */ - if (time(&now) <= secret_rollover_time) - return; - - /* Remember previous secret, in to keep answering on rollover - * boundary with old cookie. - */ - prev_secret = secret; - secret = arc4random(); - - /* Next rollover over EDNS_COOKIE_ROLLOVER_TIME with 30% jitter, - * I.e. some offset + or - 15% of the future point in time. - */ - secret_rollover_time = now + (EDNS_COOKIE_ROLLOVER_TIME / 20 * 17) - + arc4random_uniform(EDNS_COOKIE_ROLLOVER_TIME / 10 * 3); -} - -static void -calc_new_cookie(getdns_upstream *upstream, uint8_t *cookie) -{ - unsigned char md_value[GETDNS_TLS_MAX_DIGEST_LENGTH]; - size_t md_len; - size_t i; - sa_family_t af = upstream->addr.ss_family; - void *sa_addr = ((struct sockaddr*)&upstream->addr)->sa_data; - size_t addr_len = ( af == AF_INET6 ? sizeof(struct sockaddr_in6) - : af == AF_INET ? sizeof(struct sockaddr_in) - : 0 ) - sizeof(sa_family_t); - - _getdns_tls_cookie_sha256(secret, sa_addr, addr_len, md_value, &md_len); - - (void) memset(cookie, 0, 8); - for (i = 0; i < md_len; i++) - cookie[i % 8] ^= md_value[i]; -} - static getdns_return_t attach_edns_client_subnet_private(getdns_network_req *req) { @@ -169,154 +129,269 @@ 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) { - calc_new_cookie(upstream, upstream->client_cookie); - upstream->secret = secret; - upstream->has_client_cookie = 1; + if (upstream->server_cookie_len) { + if (!req->badcookie_retry && bind(req->fd, (struct sockaddr *) + &upstream->src_addr, upstream->src_addr_len)) { - sz = 8; - val = upstream->client_cookie; - } else if (upstream->secret != secret) { - memcpy( upstream->prev_client_cookie - , upstream->client_cookie, 8); - upstream->has_prev_client_cookie = 1; - calc_new_cookie(upstream, upstream->client_cookie); - upstream->secret = secret; + _getdns_upstream_log(upstream, + GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_INFO, + "%-40s : Upstream : %s with source address: %s. " + "Renewing Client Cookie.\n", upstream->addr_str, + strerror(errno), upstream->src_addr_str); - sz = 8; - val = upstream->client_cookie; - } else if (!upstream->has_server_cookie) { - sz = 8; - val = upstream->client_cookie; - } else { - sz = 8 + upstream->server_cookie_len; - memcpy(buf, upstream->client_cookie, 8); - memcpy(buf+8, upstream->server_cookie, upstream->server_cookie_len); - val = buf; + upstream->server_cookie_len = 0; + upstream->src_addr_checked = 0; + } else + return _getdns_network_req_add_upstream_option( + req, EDNS_COOKIE_OPCODE, + upstream->server_cookie_len, + upstream->server_cookie); } - return _getdns_network_req_add_upstream_option(req, EDNS_COOKIE_OPCODE, sz, val); + if (_getdns_get_now_ms() - upstream->src_addr_checked < 3600000) { + /* This upstream has been registered to *not* support cookies. + * We will recheck one hour after this was registered. + */ + return 0; + } + /* Try to get a new server cookie for this upstream + * Explicitly connect on UDP only. + */ + if (req->fd >= 0 && connect(req->fd, + (struct sockaddr*)&upstream->addr, upstream->addr_len)) { + _getdns_upstream_log(upstream, + GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_WARNING, + "%-40s : Upstream : Could not connect(): %s. " + "Required for privacy aware Cookies. Cookies disabled.\n", + upstream->addr_str, strerror(errno)); + /* Don't recheck for another hour */ + upstream->src_addr_checked = _getdns_get_now_ms(); + return 0; + } + /* Create new client cookie */ + req->cookie_sent = 1; + gldns_write_uint32(req->client_cookie, arc4random()); + gldns_write_uint32(req->client_cookie + 4, arc4random()); + return _getdns_network_req_add_upstream_option( + req, EDNS_COOKIE_OPCODE, 8, req->client_cookie); } /* Will find a matching OPT RR, but leaves the caller to validate it - * - * Returns 2 when found - * 0 when not found - * and 1 on FORMERR */ +#define MATCH_OPT_FOUND 2 +#define MATCH_OPT_NOT_FOUND 0 +#define MATCH_OPT_ERROR 1 static int -match_edns_opt_rr(uint16_t code, uint8_t *response, size_t response_len, +match_edns_opt_rr(uint16_t code, getdns_network_req *netreq, const uint8_t **position, uint16_t *option_len) { - _getdns_rr_iter rr_iter_storage, *rr_iter; - const uint8_t *pos; + const uint8_t *pos, *rdata_end; uint16_t rdata_len, opt_code = 0, opt_len = 0; + static uint8_t *NO_OPT_RR = (uint8_t *)"\x00\x00"; /* Search for the OPT RR (if any) */ - for ( rr_iter = _getdns_rr_iter_init(&rr_iter_storage - , response, response_len) - ; rr_iter - ; rr_iter = _getdns_rr_iter_next(rr_iter)) { + if (!netreq->response_opt) { + _getdns_rr_iter rr_iter_storage, *rr_iter; + for ( rr_iter = _getdns_rr_iter_init(&rr_iter_storage + , netreq->response, netreq->response_len) + ; rr_iter + ; rr_iter = _getdns_rr_iter_next(rr_iter)) { - if (_getdns_rr_iter_section(rr_iter) != SECTION_ADDITIONAL) - continue; + if (_getdns_rr_iter_section(rr_iter) != SECTION_ADDITIONAL) + continue; - if (gldns_read_uint16(rr_iter->rr_type) != GETDNS_RRTYPE_OPT) - continue; - - break; - } - if (! rr_iter) - return 0; /* No OPT, no cookie */ - - pos = rr_iter->rr_type + 8; + if (gldns_read_uint16(rr_iter->rr_type) != GETDNS_RRTYPE_OPT) + continue; + break; + } + if (! rr_iter) { + netreq->response_opt = NO_OPT_RR; + return MATCH_OPT_NOT_FOUND; + } + pos = netreq->response_opt = rr_iter->rr_type + 8; #if defined(STUB_DEBUG) && STUB_DEBUG - char str_spc[8192], *str = str_spc; - size_t str_len = sizeof(str_spc); - uint8_t *data = (uint8_t *)rr_iter->pos; - size_t data_len = rr_iter->nxt - rr_iter->pos; - (void) gldns_wire2str_rr_scan( - &data, &data_len, &str, &str_len, (uint8_t *)rr_iter->pkt, rr_iter->pkt_end - rr_iter->pkt, NULL); - DEBUG_STUB("%s %-35s: OPT RR: %s", - STUB_DEBUG_READ, __FUNC__, str_spc); + char str_spc[8192], *str = str_spc; + size_t str_len = sizeof(str_spc); + uint8_t *data = (uint8_t *)rr_iter->pos; + size_t data_len = rr_iter->nxt - rr_iter->pos; + (void) gldns_wire2str_rr_scan(&data, &data_len, &str, &str_len, + (uint8_t *)rr_iter->pkt, rr_iter->pkt_end - rr_iter->pkt, + NULL); + DEBUG_STUB("%s %-35s: OPT RR: %s", + STUB_DEBUG_READ, __FUNC__, str_spc); #endif + /* Check limits only the first time*/ + if (pos + 2 > rr_iter->nxt + || pos + 2 + gldns_read_uint16(pos) > rr_iter->nxt) { + netreq->response_opt = NO_OPT_RR; + return MATCH_OPT_ERROR; + } + } else if (netreq->response_opt == NO_OPT_RR) + return MATCH_OPT_NOT_FOUND; + else + /* Reuse earlier found option */ + pos = netreq->response_opt;; - /* OPT found, now search for the specified option */ - if (pos + 2 > rr_iter->nxt) - return 1; /* FORMERR */ + rdata_len = gldns_read_uint16(pos); + pos += 2; + rdata_end = pos + rdata_len; - rdata_len = gldns_read_uint16(pos); pos += 2; - if (pos + rdata_len > rr_iter->nxt) - return 1; /* FORMERR */ - - while (pos < rr_iter->nxt) { + while (pos < rdata_end) { opt_code = gldns_read_uint16(pos); pos += 2; opt_len = gldns_read_uint16(pos); pos += 2; - if (pos + opt_len > rr_iter->nxt) - return 1; /* FORMERR */ + if (pos + opt_len > rdata_end) + return MATCH_OPT_ERROR; if (opt_code == code) break; pos += opt_len; /* Skip unknown options */ } - if (pos >= rr_iter->nxt || opt_code != code) - return 0; /* Everything OK, just no cookie found. */ + if (pos >= rdata_end || opt_code != code) + return MATCH_OPT_NOT_FOUND; + *position = pos; *option_len = opt_len; - return 2; + return MATCH_OPT_FOUND; } /* TODO: Test combinations of EDNS0 options*/ static int match_and_process_server_cookie( - getdns_upstream *upstream, uint8_t *response, size_t response_len) + getdns_upstream *upstream, getdns_network_req *netreq) { const 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; + int gai_r; + int found = match_edns_opt_rr(EDNS_COOKIE_OPCODE, netreq, + &position, &option_len); + if (found == MATCH_OPT_ERROR) { + _getdns_upstream_log(upstream, + GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_WARNING, + "%-40s : Upstream : Error getting Cookie option.\n", + upstream->addr_str); - if (option_len < 16 || option_len > 40) - return 1; /* FORMERR */ - - if (!upstream->has_client_cookie) - return 1; /* Cookie reply, but we didn't sent one */ - - if (memcmp(upstream->client_cookie, position, 8) != 0) { - if (!upstream->has_prev_client_cookie) - return 1; /* Cookie didn't match */ - if (memcmp(upstream->prev_client_cookie, position, 8) != 0) - return 1; /* Previous cookie didn't match either */ - - upstream->has_server_cookie = 0; - return 0; /* Don't store server cookie, because it - * is for our previous client cookie - */ + return 1; /* Discard and wait for better response */ + } + else if (found == MATCH_OPT_NOT_FOUND) { + /* Option not found, server does not support cookies */ + if (upstream->server_cookie_len > 8) { + _getdns_upstream_log(upstream, + GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_ERR, + "%-40s : Upstream : Server did not return " + " DNS Cookie. Response discarded.\n", + upstream->addr_str); + + return 1; /* Discard response */ + + } else if (netreq->cookie_sent) { + _getdns_upstream_log(upstream, + GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_INFO, + "%-40s : Upstream : Does not support DNS Cookies" + ". Retrying in one hour.\n", upstream->addr_str); + + upstream->src_addr_checked = _getdns_get_now_ms(); + } + return 0; /* Use response */ + } + assert(found == MATCH_OPT_FOUND); + + if (option_len < 16 || option_len > 40) { + _getdns_upstream_log(upstream, + GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_WARNING, + "%-40s : Upstream : " + "Unsupported Server Cookie size: %d.\n", + upstream->addr_str, (int)option_len); + + return 1; /* Discard and wait for better response */ + } + + if (upstream->server_cookie_len > 8) { + if (memcmp(upstream->server_cookie, position, 8)) { + _getdns_upstream_log(upstream, + GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_WARNING, + "%-40s : Upstream : DNS Cookie did not match.\n", + upstream->addr_str); + + return 1; /* Discard and wait for better response */ + } + /* Update server cookie */ + upstream->server_cookie_len = option_len; + (void) memcpy(upstream->server_cookie, position, option_len); + return 0; /* Use response */ + + } else if (!netreq->cookie_sent) { + _getdns_upstream_log(upstream, + GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_WARNING, + "%-40s : Upstream : " + "DNS Cookie received but none sent.\n",upstream->addr_str); + + return 1; /* Discard and wait for better response */ + } + if (memcmp(netreq->client_cookie, position, 8)) { + _getdns_upstream_log(upstream, + GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_WARNING, + "%-40s : Upstream : DNS Cookie did not match.\n", + upstream->addr_str); + + return 1; /* Discard and wait for better response */ + } + /* A new client cookie matched, store server cookie but only if we + * can get the source IP address. + */ + upstream->src_addr_checked = _getdns_get_now_ms(); + + upstream->src_addr_len = sizeof(upstream->src_addr); + if (getsockname(netreq->fd,(struct sockaddr*) + &upstream->src_addr, &upstream->src_addr_len)) { + + _getdns_upstream_log(upstream, + GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_WARNING, + "%-40s : Upstream : Could not get source address: %s. " + "Privacy aware DNS Cookies not supported.\n", + upstream->addr_str, strerror(errno)); + + } else if ((gai_r = getnameinfo((struct sockaddr *) + &upstream->src_addr, upstream->src_addr_len, + upstream->src_addr_str, sizeof(upstream->src_addr_str), + NULL, 0, NI_NUMERICHOST))) { + + _getdns_upstream_log(upstream, + GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_WARNING, + "%-40s : Upstream : Could not print source address: %s. " + "Privacy aware DNS Cookies not supported.\n", + upstream->addr_str, gai_strerror(gai_r)); + } else { + switch (upstream->src_addr.ss_family) { + case AF_INET: + ((struct sockaddr_in *)&upstream->src_addr)->sin_port = 0; + break; + case AF_INET6: + ((struct sockaddr_in6*)&upstream->src_addr)->sin6_port = 0; + break; + default: + return 0; + } + _getdns_upstream_log(upstream, + GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_DEBUG, + "%-40s : Upstream : " + "Registering new Server Cookie for source address: %s.\n", + upstream->addr_str, upstream->src_addr_str); + + upstream->server_cookie_len = option_len; + (void) memcpy(upstream->server_cookie, position, option_len); + return 0; } - position += 8; - option_len -= 8; - upstream->has_server_cookie = 1; - upstream->server_cookie_len = option_len; - (void) memcpy(upstream->server_cookie, position, option_len); return 0; } static void -process_keepalive( - getdns_upstream *upstream, getdns_network_req *netreq, - uint8_t *response, size_t response_len) +process_keepalive( getdns_upstream *upstream, getdns_network_req *netreq) { const uint8_t *position = NULL; uint16_t option_len = 0; - int found = match_edns_opt_rr(GLDNS_EDNS_KEEPALIVE, response, - response_len, &position, &option_len); + int found = match_edns_opt_rr(GLDNS_EDNS_KEEPALIVE, netreq, + &position, &option_len); if (found != 2 || option_len != 2) { if (netreq->keepalive_sent == 1) { /* For TCP if no keepalive sent back, then we must use 0 idle timeout @@ -1342,6 +1417,7 @@ _getdns_get_time_as_uintt64() { /**************************/ +static void stub_udp_write_cb(void *userarg); static void stub_udp_read_cb(void *userarg) { @@ -1387,14 +1463,15 @@ stub_udp_read_cb(void *userarg) if (GLDNS_ID_WIRE(netreq->response) != GLDNS_ID_WIRE(netreq->query)) return; /* Cache poisoning attempt ;) */ - if (netreq->owner->edns_cookies && match_and_process_server_cookie( - upstream, netreq->response, read)) - return; /* Client cookie didn't match? */ - + if (netreq->owner->edns_cookies) { + netreq->response_len = read; + if (match_and_process_server_cookie(upstream, netreq)) { + netreq->response_len = 0; /* 0 means error */ + return; /* Client cookie didn't match? */ + } + netreq->response_len = 0; /* 0 means error */ + } GETDNS_CLEAR_EVENT(dnsreq->loop, &netreq->event); - - _getdns_closesocket(netreq->fd); - netreq->fd = -1; while (GLDNS_TC_WIRE(netreq->response)) { DEBUG_STUB("%s %-35s: MSG: %p TC bit set in response \n", STUB_DEBUG_READ, __FUNC__, (void*)netreq); @@ -1405,6 +1482,9 @@ stub_udp_read_cb(void *userarg) if (next_transport != GETDNS_TRANSPORT_TCP && next_transport != GETDNS_TRANSPORT_TLS) break; + + _getdns_closesocket(netreq->fd); + netreq->fd = -1; /* For now, special case where fallback should be on the same upstream*/ if ((netreq->fd = upstream_connect(upstream, next_transport, dnsreq)) == -1) @@ -1414,10 +1494,27 @@ stub_udp_read_cb(void *userarg) _getdns_ms_until_expiry(dnsreq->expires), getdns_eventloop_event_init(&netreq->event, netreq, NULL, NULL, stub_timeout_cb)); - return; } netreq->response_len = read; + if (netreq->owner->edns_cookies + && !netreq->badcookie_retry + && netreq->response_opt /* actually: assert(netreq->response_opt) */ + && (netreq->response_opt[-4] << 4 | GLDNS_RCODE_WIRE(netreq->response)) + == GETDNS_RCODE_BADCOOKIE + && netreq->fd >= 0) { + + /* Retry over UDP with the newly learned Cookie */ + netreq->badcookie_retry = 1; + GETDNS_SCHEDULE_EVENT(dnsreq->loop, netreq->fd, + _getdns_ms_until_expiry(dnsreq->expires), + getdns_eventloop_event_init(&netreq->event, netreq, + NULL, stub_udp_write_cb, stub_timeout_cb)); + return; + } + _getdns_closesocket(netreq->fd); + netreq->fd = -1; + if (!dnsreq->context->round_robin_upstreams) dnsreq->upstreams->current_udp = 0; else { @@ -1428,7 +1525,7 @@ stub_udp_read_cb(void *userarg) netreq->debug_end_time = _getdns_get_time_as_uintt64(); _getdns_netreq_change_state(netreq, NET_REQ_FINISHED); upstream->udp_responses++; - upstream->back_off = 1; + upstream->back_off = 1; if (upstream->udp_responses == 1 || upstream->udp_responses % 100 == 0) _getdns_upstream_log(upstream, GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_INFO, @@ -1584,16 +1681,12 @@ upstream_read_cb(void *userarg) upstream->tcp.read_buf = NULL; upstream->responses_received++; - /* !THIS CODE NEEDS TESTING! */ if (netreq->owner->edns_cookies && - match_and_process_server_cookie( - netreq->upstream, upstream->tcp.read_buf, - upstream->tcp.read_pos - upstream->tcp.read_buf)) + match_and_process_server_cookie(netreq->upstream, netreq)) return; /* Client cookie didn't match (or FORMERR) */ if (netreq->owner->context->idle_timeout != 0) - process_keepalive(netreq->upstream, netreq, netreq->response, - netreq->response_len); + process_keepalive(netreq->upstream, netreq); netreq->debug_end_time = _getdns_get_time_as_uintt64(); /* This also reschedules events for the upstream*/ diff --git a/src/types-internal.h b/src/types-internal.h index 12489e9c..302595ce 100644 --- a/src/types-internal.h +++ b/src/types-internal.h @@ -233,8 +233,6 @@ typedef struct getdns_network_req int edns_maximum_udp_payload_size; uint16_t max_udp_payload_size; - size_t keepalive_sent; - /* Network requests scheduled to write after me */ struct getdns_network_req *write_queue_tail; @@ -244,7 +242,13 @@ typedef struct getdns_network_req getdns_auth_state_t debug_tls_auth_status; getdns_bindata debug_tls_peer_cert; const char *debug_tls_version; - size_t debug_udp; + + /* Some booleans */ + unsigned debug_udp : 1; + unsigned keepalive_sent : 1; + unsigned badcookie_retry: 1; + unsigned cookie_sent : 1; + uint8_t client_cookie[8]; /* When more space is needed for the wire_data response than is * available in wire_data[], it will be allocated separately. @@ -267,6 +271,7 @@ typedef struct getdns_network_req size_t base_query_option_sz; size_t response_len; uint8_t *response; + const uint8_t *response_opt; /* offset of OPT RR in response */ size_t wire_data_sz; uint8_t wire_data[]; diff --git a/src/util-internal.c b/src/util-internal.c index 62aaf226..7432191c 100644 --- a/src/util-internal.c +++ b/src/util-internal.c @@ -642,6 +642,12 @@ _getdns_create_reply_dict(getdns_context *context, getdns_network_req *req, rr_type == GETDNS_RRTYPE_RRSIG && rrsigs_in_answer) *rrsigs_in_answer = 1; + if (section == SECTION_ADDITIONAL && + rr_type == GETDNS_RRTYPE_OPT && + getdns_dict_set_int( result, "/header/extended_rcode" + , (uint32_t)rr_iter->rr_type[4] << 4 + | GLDNS_RCODE_WIRE(req->response))) + goto error; if (section != SECTION_ANSWER) { if (_getdns_list_append_this_dict( sections[section], rr_dict)) diff --git a/src/util-internal.h b/src/util-internal.h index 8a1a6d82..af1e5b71 100644 --- a/src/util-internal.h +++ b/src/util-internal.h @@ -213,6 +213,17 @@ INLINE uint64_t _getdns_ms_until_expiry(uint64_t expires) return now_ms >= expires ? 0 : expires - now_ms; } +INLINE uint64_t _getdns_get_now_ms2(uint64_t *now_ms) +{ + struct timeval tv; + + if (!now_ms) return _getdns_get_now_ms(); + if (*now_ms) return *now_ms; + + (void) gettimeofday(&tv, NULL); + return (*now_ms = (uint64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000); +} + INLINE uint64_t _getdns_ms_until_expiry2(uint64_t expires, uint64_t *now_ms) { if (*now_ms == 0) *now_ms = _getdns_get_now_ms();