From 00f047816dc93caa91d4cea6cd2b1b3299119a47 Mon Sep 17 00:00:00 2001 From: Willem Toorop Date: Sun, 22 Mar 2015 10:50:48 -0500 Subject: [PATCH] EDNS cookies processing as stub --- configure | 9 ++ configure.ac | 3 +- src/config.h.in | 6 ++ src/context.c | 4 + src/context.h | 12 +++ src/stub.c | 208 +++++++++++++++++++++++++++++++++++++++++-- src/types-internal.h | 2 - 7 files changed, 235 insertions(+), 9 deletions(-) diff --git a/configure b/configure index 56e017c1..40605311 100755 --- a/configure +++ b/configure @@ -11563,6 +11563,15 @@ _ACEOF ;; esac +cat >>confdefs.h <<_ACEOF +#define EDNS_COOKIE_OPCODE 65001 +_ACEOF + + +cat >>confdefs.h <<_ACEOF +#define EDNS_COOKIE_ROLLOVER_TIME (24 * 60 * 60) +_ACEOF + # search to set include and library paths right # find libidn diff --git a/configure.ac b/configure.ac index 164fad31..331c603b 100755 --- a/configure.ac +++ b/configure.ac @@ -156,7 +156,8 @@ case "$enable_draft_edns_cookies" in no|*) ;; esac - +AC_DEFINE_UNQUOTED([EDNS_COOKIE_OPCODE], [65001], [The edns cookie option code.]) +AC_DEFINE_UNQUOTED([EDNS_COOKIE_ROLLOVER_TIME], [(24 * 60 * 60)], [How often the edns client cookie is refreshed.]) # search to set include and library paths right # find libidn diff --git a/src/config.h.in b/src/config.h.in index bb033f07..b8989408 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -3,6 +3,12 @@ /* Define this to enable the experimental draft edns cookies. */ #undef EDNS_COOKIES +/* The edns cookie option code. */ +#undef EDNS_COOKIE_OPCODE + +/* How often the edns client cookie is refreshed. */ +#undef EDNS_COOKIE_ROLLOVER_TIME + /* Define to 1 if you have the header file. */ #undef HAVE_ARPA_INET_H diff --git a/src/context.c b/src/context.c index 5952c0a2..4272caed 100644 --- a/src/context.c +++ b/src/context.c @@ -549,6 +549,10 @@ upstream_init(getdns_upstream *upstream, upstream->write_queue = NULL; upstream->write_queue_last = NULL; + upstream->has_client_cookie = 0; + upstream->has_prev_client_cookie = 0; + upstream->has_server_cookie = 0; + /* Tracking of network requests on this socket */ getdns_rbtree_init(&upstream->netreq_by_query_id, net_req_query_id_cmp); diff --git a/src/context.h b/src/context.h index 71b09488..3a63f794 100644 --- a/src/context.h +++ b/src/context.h @@ -88,6 +88,18 @@ typedef struct getdns_upstream { getdns_network_req *write_queue; getdns_network_req *write_queue_last; getdns_rbtree_t netreq_by_query_id; + + /* 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; + } getdns_upstream; typedef struct getdns_upstreams { diff --git a/src/stub.c b/src/stub.c index 02778cbb..b014d6aa 100644 --- a/src/stub.c +++ b/src/stub.c @@ -41,6 +41,179 @@ #include "util-internal.h" #include "general.h" +static time_t secret_rollover_time = 0; +static uint32_t secret = 0; +static uint32_t prev_secret = 0; + +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 + * boundry with old cookie. + */ + prev_secret = secret; + secret = (ldns_get_random() << 16) | ldns_get_random(); + + /* 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) + + (ldns_get_random()%(EDNS_COOKIE_ROLLOVER_TIME / 10 * 3)); +} + +static void +calc_new_cookie(getdns_upstream *upstream, uint8_t *cookie) +{ + const EVP_MD *md; + EVP_MD_CTX *mdctx; + unsigned char md_value[EVP_MAX_MD_SIZE]; + unsigned int 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); + + md = EVP_sha256(); + mdctx = EVP_MD_CTX_create(); + EVP_DigestInit_ex(mdctx, md, NULL); + EVP_DigestUpdate(mdctx, &secret, sizeof(secret)); + EVP_DigestUpdate(mdctx, sa_addr, addr_len); + EVP_DigestFinal_ex(mdctx, md_value, &md_len); + EVP_MD_CTX_destroy(mdctx); + + (void) memset(cookie, 0, 8); + for (i = 0; i < md_len; i++) + cookie[i % 8] ^= md_value[i]; +} + +static uint8_t * +attach_edns_cookie(getdns_upstream *upstream, uint8_t *opt) +{ + rollover_secret(); + + if (!upstream->has_client_cookie) { + calc_new_cookie(upstream, upstream->client_cookie); + 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; + + } 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; + + 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; + + } 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; + } 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; + } +} + +static int +match_and_process_server_cookie( + getdns_upstream *upstream, uint8_t *response, size_t response_len) +{ + priv_getdns_rr_iter rr_iter_storage, *rr_iter; + uint8_t *pos; + uint16_t rdata_len, opt_code, opt_len; + + /* Search for the OPT RR (if any) */ + for ( rr_iter = priv_getdns_rr_iter_init(&rr_iter_storage + , response, response_len) + ; rr_iter + ; rr_iter = priv_getdns_rr_iter_next(rr_iter)) { + + if (priv_getdns_rr_iter_section(rr_iter) != + GLDNS_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; + + /* OPT found, now search for the cookie option */ + if (pos + 2 > rr_iter->nxt) + return 1; /* FORMERR */ + + rdata_len = gldns_read_uint16(pos); pos += 2; + if (pos + rdata_len > rr_iter->nxt) + return 1; /* FORMERR */ + + while (pos < rr_iter->nxt) { + 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 (opt_code == EDNS_COOKIE_OPCODE) + break; + pos += opt_len; /* Skip unknown options */ + } + if (pos >= rr_iter->nxt || opt_code != EDNS_COOKIE_OPCODE) + return 0; /* Everything OK, just no cookie found. */ + + if (opt_len < 16 || opt_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, pos, 8) != 0) { + if (!upstream->has_prev_client_cookie) + return 1; /* Cookie didn't match */ + if (memcmp(upstream->prev_client_cookie, pos, 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 + */ + } + pos += 8; + opt_len -= 8; + upstream->has_server_cookie = 1; + upstream->server_cookie_len = opt_len; + (void) memcpy(upstream->server_cookie, pos, opt_len); + return 0; +} /** best effort to set nonblocking */ static void @@ -206,6 +379,10 @@ stub_udp_read_cb(void *userarg) if (GLDNS_ID_WIRE(netreq->response) != netreq->query_id) return; /* Cache poisoning attempt ;) */ + if (netreq->owner->edns_cookies && match_and_process_server_cookie( + upstream, netreq->response, read)) + return; /* Client cookie didn't match? */ + close(netreq->fd); if (GLDNS_TC_WIRE(netreq->response) && dnsreq->context->dns_transport == @@ -251,11 +428,18 @@ stub_udp_write_cb(void *userarg) netreq->query_id = ldns_get_random(); GLDNS_ID_SET(netreq->query, netreq->query_id); - if (netreq->edns_maximum_udp_payload_size == -1 && netreq->opt) - gldns_write_uint16(netreq->opt + 3, - ( netreq->max_udp_payload_size = - netreq->upstream->addr.ss_family == AF_INET6 - ? 1232 : 1432)); + if (netreq->opt) { + 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 ((ssize_t)pkt_len != sendto(netreq->fd, netreq->query, pkt_len, 0, (struct sockaddr *)&netreq->upstream->addr, @@ -389,6 +573,11 @@ stub_tcp_read_cb(void *userarg) 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 = @@ -529,10 +718,17 @@ stub_tcp_write(int fd, getdns_tcp_state *tcp, getdns_network_req *netreq) &netreq->upstream->netreq_by_query_id, &netreq->node)); GLDNS_ID_SET(netreq->query, query_id); - if (netreq->opt) + if (netreq->opt) { /* 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); + } + } /* We have an initialized packet buffer. * Lets see how much of it we can write */ diff --git a/src/types-internal.h b/src/types-internal.h index fd7ea972..81141bcc 100644 --- a/src/types-internal.h +++ b/src/types-internal.h @@ -46,8 +46,6 @@ struct getdns_context; struct getdns_upstreams; struct getdns_upstream; -#define EDNSCOOKIE_OPCODE 65001 - /** * \defgroup strings String Constants * @{