]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
Downstream DNS Cookies a la RFC7873 and RFC9018
authorWillem Toorop <willem@nlnetlabs.nl>
Wed, 28 Sep 2022 08:28:19 +0000 (10:28 +0200)
committerWillem Toorop <willem@nlnetlabs.nl>
Wed, 28 Sep 2022 08:28:19 +0000 (10:28 +0200)
Create server cookies for clients that send client cookies.
Needs to be turned on in the config file with:

answer-cookie: yes

A cookie-secret can be configured for anycast setups.
Also adds an access control list that will allow queries with
either a valid cookie or over a stateful transport.

17 files changed:
Makefile.in
daemon/acl_list.c
daemon/acl_list.h
daemon/worker.c
doc/unbound.conf.5.in
libunbound/libworker.c
services/authzone.c
sldns/rrdef.h
testcode/fake_event.c
util/config_file.c
util/config_file.h
util/configlexer.lex
util/configparser.y
util/data/msgparse.c
util/data/msgparse.h
util/siphash.c [new file with mode: 0644]
validator/autotrust.c

index 3189731ad52fae6eb5ac6fe4b959ff3b1f9b4b70..718f47f5c8bf125fbb5300722532dcbfc688b4f7 100644 (file)
@@ -128,7 +128,7 @@ util/config_file.c util/configlexer.c util/configparser.c \
 util/shm_side/shm_main.c services/authzone.c \
 util/fptr_wlist.c util/locks.c util/log.c util/mini_event.c util/module.c \
 util/netevent.c util/net_help.c util/random.c util/rbtree.c util/regional.c \
-util/rtt.c util/edns.c util/storage/dnstree.c util/storage/lookup3.c \
+util/rtt.c util/siphash.c util/edns.c util/storage/dnstree.c util/storage/lookup3.c \
 util/storage/lruhash.c util/storage/slabhash.c util/tcp_conn_limit.c \
 util/timehist.c util/tube.c \
 util/ub_event.c util/ub_event_pluggable.c util/winsock_event.c \
@@ -145,7 +145,7 @@ as112.lo msgparse.lo msgreply.lo packed_rrset.lo iterator.lo iter_delegpt.lo \
 iter_donotq.lo iter_fwd.lo iter_hints.lo iter_priv.lo iter_resptype.lo \
 iter_scrub.lo iter_utils.lo localzone.lo mesh.lo modstack.lo view.lo \
 outbound_list.lo alloc.lo config_file.lo configlexer.lo configparser.lo \
-fptr_wlist.lo edns.lo locks.lo log.lo mini_event.lo module.lo net_help.lo \
+fptr_wlist.lo siphash.lo edns.lo locks.lo log.lo mini_event.lo module.lo net_help.lo \
 random.lo rbtree.lo regional.lo rtt.lo dnstree.lo lookup3.lo lruhash.lo \
 slabhash.lo tcp_conn_limit.lo timehist.lo tube.lo winsock_event.lo \
 autotrust.lo val_anchor.lo rpz.lo \
@@ -915,7 +915,8 @@ config_file.lo config_file.o: $(srcdir)/util/config_file.c config.h $(srcdir)/ut
 configlexer.lo configlexer.o: util/configlexer.c config.h $(srcdir)/util/configyyrename.h \
  $(srcdir)/util/config_file.h util/configparser.h
 configparser.lo configparser.o: util/configparser.c config.h $(srcdir)/util/configyyrename.h \
- $(srcdir)/util/config_file.h $(srcdir)/util/net_help.h $(srcdir)/util/log.h
+ $(srcdir)/util/config_file.h $(srcdir)/util/net_help.h $(srcdir)/util/log.h $(srcdir)/sldns/str2wire.h \
+ $(srcdir)/sldns/rrdef.h
 shm_main.lo shm_main.o: $(srcdir)/util/shm_side/shm_main.c config.h $(srcdir)/util/shm_side/shm_main.h \
  $(srcdir)/libunbound/unbound.h $(srcdir)/daemon/daemon.h $(srcdir)/util/locks.h $(srcdir)/util/log.h \
  $(srcdir)/util/alloc.h $(srcdir)/services/modstack.h  \
@@ -1004,6 +1005,7 @@ rtt.lo rtt.o: $(srcdir)/util/rtt.c config.h $(srcdir)/util/rtt.h $(srcdir)/itera
  $(srcdir)/services/outbound_list.h $(srcdir)/util/data/msgreply.h $(srcdir)/util/storage/lruhash.h \
  $(srcdir)/util/locks.h $(srcdir)/util/log.h $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/module.h \
  $(srcdir)/util/data/msgparse.h $(srcdir)/sldns/pkthdr.h $(srcdir)/sldns/rrdef.h
+siphash.lo siphash.o: $(srcdir)/util/siphash.c
 edns.lo edns.o: $(srcdir)/util/edns.c config.h $(srcdir)/util/edns.h $(srcdir)/util/storage/dnstree.h \
  $(srcdir)/util/rbtree.h $(srcdir)/util/config_file.h $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h \
   $(srcdir)/util/net_help.h $(srcdir)/util/log.h $(srcdir)/util/regional.h \
index 8e8e1fc9b4bb8efd7d1019bf1d13cea2698cdb87..e6d0470a1940a3cd22a770953170a7d2e8001431 100644 (file)
@@ -109,6 +109,8 @@ parse_acl_access(const char* str, enum acl_access* control)
                *control = acl_allow_snoop;
        else if(strcmp(str, "allow_setrd") == 0)
                *control = acl_allow_setrd;
+       else if (strcmp(str, "allow_cookie") == 0)
+               *control = acl_allow_cookie;
        else {
                log_err("access control type %s unknown", str);
                return 0;
index c717179baf5ea5a0f9cc2433c80f9f5a2b97465c..8aece11bfd0e972a5de1d0e81393558e9fd1b485 100644 (file)
@@ -65,7 +65,9 @@ enum acl_access {
        /** allow full access for all queries, recursion and cache snooping */
        acl_allow_snoop,
        /** allow full access for recursion queries and set RD flag regardless of request */
-       acl_allow_setrd
+       acl_allow_setrd,
+       /** allow full access if valid cookie present or stateful transport */
+       acl_allow_cookie
 };
 
 /**
index c2a94be79216b767829ba6166912b489f2fb41b5..1abf20a7b9325ae9189631a092cb7960763c1cb5 100644 (file)
@@ -1432,8 +1432,10 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
                }
                goto send_reply;
        }
-       if((ret=parse_edns_from_query_pkt(c->buffer, &edns, worker->env.cfg, c,
-                                       worker->scratchpad)) != 0) {
+       if((ret=parse_edns_from_query_pkt(
+                       c->buffer, &edns, worker->env.cfg, c, repinfo,
+                       (worker->env.now ? *worker->env.now : time(NULL)),
+                       worker->scratchpad)) != 0) {
                struct edns_data reply_edns;
                verbose(VERB_ALGO, "worker parse edns: formerror.");
                log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen);
@@ -1466,6 +1468,44 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
                        edns.udp_size = NORMAL_UDP_SIZE;
                }
        }
+       /* "if, else if" sequence below deals with downstream DNS Cookies */
+       if (acl != acl_allow_cookie)
+               ; /* pass; No cookie downstream processing whatsoever */
+
+       else if (edns.cookie_valid)
+               ; /* pass; Valid cookie is good! */
+
+       else if (c->type != comm_udp)
+               ; /* pass; Stateful transport */
+
+       else if (edns.cookie_present) {
+               /* Cookie present, but not valid: Cookie was bad! */
+               extended_error_encode(c->buffer,
+                       LDNS_EXT_RCODE_BADCOOKIE, &qinfo,
+                       *(uint16_t*)(void *)
+                       sldns_buffer_begin(c->buffer),
+                       sldns_buffer_read_u16_at(c->buffer, 2),
+                       0, &edns);
+               regional_free_all(worker->scratchpad);
+               goto send_reply;
+       } else {
+               /* Cookie requered, but no cookie present on UDP */
+               verbose(VERB_ALGO, "worker request: "
+                       "need cookie or stateful transport");
+               log_addr(VERB_ALGO, "from",
+                       &repinfo->addr, repinfo->addrlen);
+               EDNS_OPT_LIST_APPEND_EDE(&edns.opt_list_out,
+                       worker->scratchpad, LDNS_EDE_OTHER,
+                       "DNS Cookie needed for UDP replies");
+               error_encode(c->buffer,
+                       (LDNS_RCODE_REFUSED|BIT_TC), &qinfo,
+                       *(uint16_t*)(void *)
+                       sldns_buffer_begin(c->buffer),
+                       sldns_buffer_read_u16_at(c->buffer, 2),
+                       &edns);
+               regional_free_all(worker->scratchpad);
+               goto send_reply;
+       }
        if(edns.udp_size > worker->daemon->cfg->max_udp_size &&
                c->type == comm_udp) {
                verbose(VERB_QUERY,
index 73575d93a2b7fb617ba0d5af86f4c6deeb6fb076..e187452de12c543cd74e083bc2f0f7d24934990e 100644 (file)
@@ -673,9 +673,9 @@ This option is experimental at this time.
 .B access\-control: \fI<IP netblock> <action>
 The netblock is given as an IP4 or IP6 address with /size appended for a
 classless network block. The action can be \fIdeny\fR, \fIrefuse\fR,
-\fIallow\fR, \fIallow_setrd\fR, \fIallow_snoop\fR, \fIdeny_non_local\fR or
-\fIrefuse_non_local\fR.
-The most specific netblock match is used, if none match \fIrefuse\fR is used.
+\fIallow\fR, \fIallow_setrd\fR, \fIallow_snoop\fR, \fIallow_cookie\fR,
+\fIdeny_non_local\fR or \fIrefuse_non_local\fR.
+The most specific netblock match is used, if none match \fIdeny\fR is used.
 The order of the access\-control statements therefore does not matter.
 .IP
 The action \fIdeny\fR stops queries from hosts from that netblock.
@@ -710,6 +710,14 @@ the cache contents (for malicious acts).  However, nonrecursive queries can
 also be a valuable debugging tool (when you want to examine the cache
 contents). In that case use \fIallow_snoop\fR for your administration host.
 .IP
+When the \fBanswer\-cookie\fR option is enabled, the \fIallow_cookie\fR action
+will allow access to UDP queries that contain a valid Server Cookie as
+specified in RFC 7873 and RFC9018. UDP queries containing only a Client Cookie
+and no Server Cookie, will receive a BADCOOKIE response including a Server
+Cookie, allow clients to retry with that Server Cookie. The \fIallow_cookie\fR
+will also accept requests over statefull transports, regardless of the precence
+of a Cookie and regardless the \fBanswer\-cookie\fR setting.
+.IP
 By default only localhost is \fIallow\fRed, the rest is \fIrefuse\fRd.
 The default is \fIrefuse\fRd, because that is protocol\-friendly. The DNS
 protocol is not designed to handle dropped packets due to policy, and
@@ -1824,6 +1832,15 @@ Set the number of servers that should be used for fast server selection. Only
 use the fastest specified number of servers with the fast\-server\-permil
 option, that turns this on or off. The default is to use the fastest 3 servers.
 .TP 5
+.B answer\-cookie: \fI<yes or no>
+Enable to answer to requests containig DNS Cookies as specified in RFC7873 and
+RFC9018. Default is no.
+.TP 5
+.B cookie\-secret: \fI<128 bit hex string>
+Server's in an Anycast deployment need to be able to verify each other's
+Server Cookies. For this they need to share the secret used to construct
+and verify the Server Cookies.
+Default is a 128 bits random secret generated at startup time.
 .B edns\-client\-string: \fI<IP netblock> <string>
 Include an EDNS0 option containing configured ascii string in queries with
 destination address matching the configured IP netblock.  This configuration
index 11bf5f9db555d0f9e69c4547fdd6287fcff24394..d5fabe6fb6ee0c1cce47ffb43d7efb28d891533d 100644 (file)
@@ -604,6 +604,8 @@ setup_qinfo_edns(struct libworker* w, struct ctx_query* q,
        edns->opt_list_out = NULL;
        edns->opt_list_inplace_cb_out = NULL;
        edns->padding_block_size = 0;
+       edns->cookie_present = 0;
+       edns->cookie_valid = 0;
        if(sldns_buffer_capacity(w->back->udp_buff) < 65535)
                edns->udp_size = (uint16_t)sldns_buffer_capacity(
                        w->back->udp_buff);
index 6de1e43190955b798c1591d7682889227823fced..079a7eaf14634e3a245cba40a59e63c4ba5d1e8e 100644 (file)
@@ -5419,6 +5419,8 @@ xfr_transfer_lookup_host(struct auth_xfer* xfr, struct module_env* env)
        edns.opt_list_out = NULL;
        edns.opt_list_inplace_cb_out = NULL;
        edns.padding_block_size = 0;
+       edns.cookie_present = 0;
+       edns.cookie_valid = 0;
        if(sldns_buffer_capacity(buf) < 65535)
                edns.udp_size = (uint16_t)sldns_buffer_capacity(buf);
        else    edns.udp_size = 65535;
@@ -6612,6 +6614,8 @@ xfr_probe_lookup_host(struct auth_xfer* xfr, struct module_env* env)
        edns.opt_list_out = NULL;
        edns.opt_list_inplace_cb_out = NULL;
        edns.padding_block_size = 0;
+       edns.cookie_present = 0;
+       edns.cookie_valid = 0;
        if(sldns_buffer_capacity(buf) < 65535)
                edns.udp_size = (uint16_t)sldns_buffer_capacity(buf);
        else    edns.udp_size = 65535;
index 999c223074e1ec19bd4725a12e049e1ed1a6fb32..d5b0585fdce84a67cfb06c5a5cc7b50f231060f0 100644 (file)
@@ -433,6 +433,7 @@ enum sldns_enum_edns_option
        LDNS_EDNS_DHU = 6, /* RFC6975 */
        LDNS_EDNS_N3U = 7, /* RFC6975 */
        LDNS_EDNS_CLIENT_SUBNET = 8, /* RFC7871 */
+       LDNS_EDNS_COOKIE = 10, /* RFC7873 */
        LDNS_EDNS_KEEPALIVE = 11, /* draft-ietf-dnsop-edns-tcp-keepalive*/
        LDNS_EDNS_PADDING = 12, /* RFC7830 */
        LDNS_EDNS_EDE = 15, /* RFC8914 */
@@ -482,6 +483,9 @@ typedef enum sldns_enum_ede_code sldns_ede_code;
 #define LDNS_TSIG_ERROR_BADNAME  20
 #define LDNS_TSIG_ERROR_BADALG   21
 
+/** DNS Cookie extended rcode */
+#define LDNS_EXT_RCODE_BADCOOKIE 23
+
 /**
  * Contains all information about resource record types.
  *
index 03e1c04f3dc8a4dd60e82655518c2aaf6a1f7ad4..c93ceaa4c0d4145d78728b3a449523d0a0ccfd03 100644 (file)
@@ -1263,6 +1263,8 @@ struct serviced_query* outnet_serviced_query(struct outside_network* outnet,
                if(dnssec)
                        edns.bits = EDNS_DO;
                edns.padding_block_size = 0;
+               edns.cookie_present = 0;
+               edns.cookie_valid = 0;
                edns.opt_list_in = NULL;
                edns.opt_list_out = per_upstream_opt_list;
                edns.opt_list_inplace_cb_out = NULL;
index 158169c427f1c74978280b4c5f79f646ffad8d2d..a9234276112d7bf956338b2f933c35936a3993da 100644 (file)
@@ -55,6 +55,7 @@
 #include "util/regional.h"
 #include "util/fptr_wlist.h"
 #include "util/data/dname.h"
+#include "util/random.h"
 #include "util/rtt.h"
 #include "services/cache/infra.h"
 #include "sldns/wire2str.h"
@@ -87,6 +88,9 @@ struct config_parser_state* cfg_parser = 0;
 /** init ports possible for use */
 static void init_outgoing_availports(int* array, int num);
 
+/** init cookie with random data */
+static void init_cookie_secret(uint8_t* cookie_secret,size_t cookie_secret_len);
+
 struct config_file* 
 config_create(void)
 {
@@ -364,6 +368,10 @@ config_create(void)
        cfg->ipsecmod_whitelist = NULL;
        cfg->ipsecmod_strict = 0;
 #endif
+       cfg->do_answer_cookie = 0;
+       memset(cfg->cookie_secret, 0, sizeof(cfg->cookie_secret));
+       cfg->cookie_secret_len = 16;
+       init_cookie_secret(cfg->cookie_secret, cfg->cookie_secret_len);
 #ifdef USE_CACHEDB
        if(!(cfg->cachedb_backend = strdup("testframe"))) goto error_exit;
        if(!(cfg->cachedb_secret = strdup("default"))) goto error_exit;
@@ -1660,6 +1668,21 @@ config_delete(struct config_file* cfg)
        free(cfg);
 }
 
+static void
+init_cookie_secret(uint8_t* cookie_secret, size_t cookie_secret_len)
+{
+       struct ub_randstate *rand = ub_initstate(NULL);
+
+       if (!rand)
+               fatal_exit("could not init random generator");
+       while (cookie_secret_len) {
+               *cookie_secret++ = (uint8_t)ub_random(rand);
+               cookie_secret_len--;
+       }
+       ub_randfree(rand);
+}
+
+
 static void 
 init_outgoing_availports(int* a, int num)
 {
index bbc6d4ac19efdee89cd25f2dce6d9a40f78e161f..3db4676b94d5e3ec6b34d959867f75c95ba1cefa 100644 (file)
@@ -688,6 +688,13 @@ struct config_file {
        int redis_expire_records;
 #endif
 #endif
+       /** Downstream DNS Cookies */
+       /** do answer with server cookie when request contained cookie option */
+       int do_answer_cookie;
+       /** cookie secret */
+       uint8_t cookie_secret[40];
+       /** cookie secret length */
+       size_t  cookie_secret_len;
 
        /* ipset module */
 #ifdef USE_IPSET
index fc9aa72660933cbf6531b892d24193a655883c0f..4fdb2cde031e8ea5f651d301fb8443cc169db740 100644 (file)
@@ -558,6 +558,8 @@ name-v4{COLON}                      { YDVAR(1, VAR_IPSET_NAME_V4) }
 name-v6{COLON}                 { YDVAR(1, VAR_IPSET_NAME_V6) }
 udp-upstream-without-downstream{COLON} { YDVAR(1, VAR_UDP_UPSTREAM_WITHOUT_DOWNSTREAM) }
 tcp-connection-limit{COLON}    { YDVAR(2, VAR_TCP_CONNECTION_LIMIT) }
+answer-cookie{COLON}           { YDVAR(1, VAR_ANSWER_COOKIE ) }
+cookie-secret{COLON}           { YDVAR(1, VAR_COOKIE_SECRET) }
 edns-client-string{COLON}      { YDVAR(2, VAR_EDNS_CLIENT_STRING) }
 edns-client-string-opcode{COLON} { YDVAR(1, VAR_EDNS_CLIENT_STRING_OPCODE) }
 nsid{COLON}                    { YDVAR(1, VAR_NSID ) }
index 8f3672f5da28a795f36099c96167585bf5ceb202..980201460357d9c41ef8792de4fa0eb1a7451e10 100644 (file)
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
+#include <time.h>
 #include <assert.h>
 
 #include "util/configyyrename.h"
 #include "util/config_file.h"
 #include "util/net_help.h"
+#include "sldns/str2wire.h"
 
 int ub_c_lex(void);
 void ub_c_error(const char *message);
@@ -181,6 +183,7 @@ extern struct config_parser_state* cfg_parser;
 %token VAR_FALLBACK_ENABLED VAR_TLS_ADDITIONAL_PORT VAR_LOW_RTT VAR_LOW_RTT_PERMIL
 %token VAR_FAST_SERVER_PERMIL VAR_FAST_SERVER_NUM
 %token VAR_ALLOW_NOTIFY VAR_TLS_WIN_CERT VAR_TCP_CONNECTION_LIMIT
+%token VAR_ANSWER_COOKIE VAR_COOKIE_SECRET
 %token VAR_FORWARD_NO_CACHE VAR_STUB_NO_CACHE VAR_LOG_SERVFAIL VAR_DENY_ANY
 %token VAR_UNKNOWN_SERVER_TIME_LIMIT VAR_LOG_TAG_QUERYREPLY
 %token VAR_STREAM_WAIT_SIZE VAR_TLS_CIPHERS VAR_TLS_CIPHERSUITES VAR_TLS_USE_SNI
@@ -316,6 +319,7 @@ content_server: server_num_threads | server_verbosity | server_port |
        server_unknown_server_time_limit | server_log_tag_queryreply |
        server_stream_wait_size | server_tls_ciphers |
        server_tls_ciphersuites | server_tls_session_ticket_keys |
+       server_answer_cookie | server_cookie_secret |
        server_tls_use_sni | server_edns_client_string |
        server_edns_client_string_opcode | server_nsid |
        server_zonemd_permissive_mode | server_max_reuse_tcp_queries |
@@ -3695,6 +3699,30 @@ server_tcp_connection_limit: VAR_TCP_CONNECTION_LIMIT STRING_ARG STRING_ARG
                }
        }
        ;
+server_answer_cookie: VAR_ANSWER_COOKIE STRING_ARG
+       {
+               OUTYY(("P(server_answer_cookie:%s)\n", $2));
+               if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
+                       yyerror("expected yes or no.");
+               else cfg_parser->cfg->do_answer_cookie = (strcmp($2, "yes")==0);
+               free($2);
+       }
+       ;
+server_cookie_secret: VAR_COOKIE_SECRET STRING_ARG
+       {
+               uint8_t secret[32];
+               size_t secret_len = sizeof(secret);
+
+               OUTYY(("P(server_cookie_secret:%s)\n", $2));
+               if (sldns_str2wire_hex_buf($2, secret, &secret_len)
+               || (  secret_len != 16))
+                       yyerror("expected 128 bit hex string");
+               else {
+                       cfg_parser->cfg->cookie_secret_len = secret_len;
+                       memcpy(cfg_parser->cfg->cookie_secret, secret, sizeof(secret));
+               }
+               free($2);
+       }
        ipsetstart: VAR_IPSET
                {
                        OUTYY(("\nP(ipset:)\n"));
@@ -3764,10 +3792,11 @@ validate_acl_action(const char* action)
                strcmp(action, "refuse_non_local")!=0 &&
                strcmp(action, "allow_setrd")!=0 &&
                strcmp(action, "allow")!=0 &&
-               strcmp(action, "allow_snoop")!=0)
+               strcmp(action, "allow_snoop")!=0 &&
+               strcmp(action, "allow_cookie")!=0)
        {
                yyerror("expected deny, refuse, deny_non_local, "
-                       "refuse_non_local, allow, allow_setrd or "
-                       "allow_snoop as access control action");
+                       "refuse_non_local, allow, allow_setrd, "
+                       "allow_snoop or allow_cookie as access control action");
        }
 }
index 5bb69d6ed06fc2fff257bddb591def269c94236c..3059d555c70589aa36f545845f006e177a78034d 100644 (file)
@@ -951,11 +951,67 @@ edns_opt_list_append_keepalive(struct edns_option** list, int msec,
                        data, region);
 }
 
+int siphash(const uint8_t *in, const size_t inlen,
+               const uint8_t *k, uint8_t *out, const size_t outlen);
+
+/** RFC 1982 comparison, uses unsigned integers, and tries to avoid
+ * compiler optimization (eg. by avoiding a-b<0 comparisons),
+ * this routine matches compare_serial(), for SOA serial number checks */
+static int
+compare_1982(uint32_t a, uint32_t b)
+{
+       /* for 32 bit values */
+       const uint32_t cutoff = ((uint32_t) 1 << (32 - 1));
+
+       if (a == b) {
+               return 0;
+       } else if ((a < b && b - a < cutoff) || (a > b && a - b > cutoff)) {
+               return -1;
+       } else {
+               return 1;
+       }
+}
+
+/** if we know that b is larger than a, return the difference between them,
+ * that is the distance between them. in RFC1982 arith */
+static uint32_t
+subtract_1982(uint32_t a, uint32_t b)
+{
+       /* for 32 bit values */
+       const uint32_t cutoff = ((uint32_t) 1 << (32 - 1));
+
+       if(a == b)
+               return 0;
+       if(a < b && b - a < cutoff) {
+               return b-a;
+       }
+       if(a > b && a - b > cutoff) {
+               return ((uint32_t)0xffffffff) - (a-b-1);
+       }
+       /* wrong case, b smaller than a */
+       return 0;
+}
+
+
+static uint8_t *
+cookie_hash(uint8_t *hash, uint8_t *buf,
+               struct sockaddr_storage *addr, uint8_t *secret)
+{
+       if (addr->ss_family == AF_INET6) {
+               memcpy(buf+16, &((struct sockaddr_in6 *)addr)->sin6_addr, 16);
+               siphash(buf, 32, secret, hash, 8);
+       } else {
+               memcpy(buf+16, &((struct sockaddr_in *)addr)->sin_addr, 4);
+               siphash(buf, 20, secret, hash, 8);
+       }
+       return hash;
+}
+
 /** parse EDNS options from EDNS wireformat rdata */
 static int
 parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len,
        struct edns_data* edns, struct config_file* cfg, struct comm_point* c,
-       struct regional* region)
+       struct comm_reply* repinfo, uint32_t now, struct regional* region)
 {
        /* To respond with a Keepalive option, the client connection must have
         * received one message with a TCP Keepalive EDNS option, and that
@@ -979,6 +1035,10 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len,
        while(rdata_len >= 4) {
                uint16_t opt_code = sldns_read_uint16(rdata_ptr);
                uint16_t opt_len = sldns_read_uint16(rdata_ptr+2);
+               uint8_t server_cookie[40], hash[8];
+               uint32_t cookie_time, subt_1982;
+               int comp_1982;
+
                rdata_ptr += 4;
                rdata_len -= 4;
                if(opt_len > rdata_len)
@@ -1041,6 +1101,86 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len,
                        edns->padding_block_size = cfg->pad_responses_block_size;
                        break;
 
+               case LDNS_EDNS_COOKIE:
+                       if(!cfg || !cfg->do_answer_cookie)
+                               break;
+                       if(opt_len != 8 && (opt_len < 16 || opt_len > 40)) {
+                               verbose(VERB_ALGO, "worker request: "
+                                               "badly formatted cookie");
+                               return LDNS_RCODE_FORMERR;
+                       }
+                       edns->cookie_present = 1;
+
+                       /* Copy client cookie, version and timestamp for
+                        * validation and creation purposes.
+                        */
+                       memcpy(server_cookie, rdata_ptr, 16);
+
+                       /* In the "if, if else" block below, we validate a
+                        * RFC9018 cookie. If it doesn't match the recipe, or
+                        * if it doesn't validate, or if the cookie is too old
+                        * (< 30 min), a new cookie is generated.
+                        */
+                       if (opt_len != 24)
+                               ; /* RFC9018 cookies are 24 bytes long */
+
+                       else if (cfg->cookie_secret_len != 16)
+                               ; /* RFC9018 cookies have 16 byte secrets */
+
+                       else if (rdata_ptr[8] != 1)
+                               ; /* RFC9018 cookies are cookie version 1 */
+
+                       else if ((comp_1982 = compare_1982(now,
+                                       (cookie_time = sldns_read_uint32(rdata_ptr + 12)))) > 0
+                            &&  (subt_1982 = subtract_1982(cookie_time, now)) > 3600)
+                               ; /* Cookie is older than 1 hour
+                                  * (see RFC9018 Section 4.3.)
+                                  */
+
+                       else if (comp_1982 <= 0
+                            &&  subtract_1982(now, cookie_time) > 300)
+                               ; /* Cookie time is more than 5 minutes in the
+                                  * future. (see RFC9018 Section 4.3.)
+                                  */
+
+                       else if (memcmp( cookie_hash( hash, server_cookie
+                                                   , &repinfo->addr
+                                                   , cfg->cookie_secret)
+                                      , rdata_ptr + 16 , 8 ) == 0) {
+
+                               /* Cookie is valid! */
+                               edns->cookie_valid = 1;
+                               if (comp_1982 > 0 && subt_1982 > 1800)
+                                       ; /* But older than 30 minutes,
+                                          * so create a new one anyway */
+
+                               else if (!edns_opt_list_append( /* Reuse cookie */
+                                   &edns->opt_list_out, LDNS_EDNS_COOKIE, opt_len,
+                                   rdata_ptr, region)) {
+                                       log_err("out of memory");
+                                       return LDNS_RCODE_SERVFAIL;
+                               } else
+                                       /* Cookie to be reused added to
+                                        * outgoing options. Done!
+                                        */
+                                       break;
+                       }
+                       /* Add a new server cookie to outgoing cookies */
+                       server_cookie[ 8] = 1;   /* Version */
+                       server_cookie[ 9] = 0;   /* Reserved */
+                       server_cookie[10] = 0;   /* Reserved */
+                       server_cookie[11] = 0;   /* Reserved */
+                       sldns_write_uint32(server_cookie + 12, now);
+                       cookie_hash( hash, server_cookie
+                                  , &repinfo->addr, cfg->cookie_secret);
+                       memcpy(server_cookie + 16, hash, 8);
+                       if (!edns_opt_list_append( &edns->opt_list_out
+                                                , LDNS_EDNS_COOKIE
+                                                , 24, server_cookie, region)) {
+                               log_err("out of memory");
+                               return LDNS_RCODE_SERVFAIL;
+                       }
+                       break;
                default:
                        break;
                }
@@ -1115,6 +1255,8 @@ parse_extract_edns_from_response_msg(struct msg_parse* msg,
        edns->opt_list_out = NULL;
        edns->opt_list_inplace_cb_out = NULL;
        edns->padding_block_size = 0;
+       edns->cookie_present = 0;
+       edns->cookie_valid = 0;
 
        /* take the options */
        rdata_len = found->rr_first->size-2;
@@ -1170,7 +1312,8 @@ skip_pkt_rrs(sldns_buffer* pkt, int num)
 
 int 
 parse_edns_from_query_pkt(sldns_buffer* pkt, struct edns_data* edns,
-       struct config_file* cfg, struct comm_point* c, struct regional* region)
+       struct config_file* cfg, struct comm_point* c,
+       struct comm_reply* repinfo, time_t now, struct regional* region)
 {
        size_t rdata_len;
        uint8_t* rdata_ptr;
@@ -1206,6 +1349,8 @@ parse_edns_from_query_pkt(sldns_buffer* pkt, struct edns_data* edns,
        edns->opt_list_out = NULL;
        edns->opt_list_inplace_cb_out = NULL;
        edns->padding_block_size = 0;
+       edns->cookie_present = 0;
+       edns->cookie_valid = 0;
 
        /* take the options */
        rdata_len = sldns_buffer_read_u16(pkt);
@@ -1214,7 +1359,7 @@ parse_edns_from_query_pkt(sldns_buffer* pkt, struct edns_data* edns,
        rdata_ptr = sldns_buffer_current(pkt);
        /* ignore rrsigs */
        return parse_edns_options_from_query(rdata_ptr, rdata_len, edns, cfg,
-                       c, region);
+                       c, repinfo, now, region);
 }
 
 void
index 0c458e6e8e251e2a9200af44bfaf6fac3166dfff..aebeb2a88b48335f9f1d6114c2b688ddc77b768a 100644 (file)
@@ -72,6 +72,7 @@ struct regional;
 struct edns_option;
 struct config_file;
 struct comm_point;
+struct comm_reply;
 
 /** number of buckets in parse rrset hash table. Must be power of 2. */
 #define PARSE_TABLE_SIZE 32
@@ -217,8 +218,6 @@ struct rr_parse {
  * region.
  */
 struct edns_data {
-       /** if EDNS OPT record was present */
-       int edns_present;
        /** Extended RCODE */
        uint8_t ext_rcode;
        /** The EDNS version number */
@@ -238,7 +237,13 @@ struct edns_data {
        struct edns_option* opt_list_inplace_cb_out;
        /** block size to pad */
        uint16_t padding_block_size;
-};
+       /** if EDNS OPT record was present */
+       unsigned int edns_present   : 1;
+       /** if a cookie was present */
+       unsigned int cookie_present : 1;
+       /** if the cookie validated */
+       unsigned int cookie_valid   : 1;
+};     
 
 /**
  * EDNS option
@@ -315,7 +320,8 @@ int skip_pkt_rrs(struct sldns_buffer* pkt, int num);
  *     RCODE formerr if OPT is badly formatted and so on.
  */
 int parse_edns_from_query_pkt(struct sldns_buffer* pkt, struct edns_data* edns,
-       struct config_file* cfg, struct comm_point* c, struct regional* region);
+       struct config_file* cfg, struct comm_point* c,
+       struct comm_reply* repinfo, time_t now, struct regional* region);
 
 /**
  * Calculate hash value for rrset in packet.
diff --git a/util/siphash.c b/util/siphash.c
new file mode 100644 (file)
index 0000000..d69f4b5
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+   SipHash reference C implementation
+
+   Copyright (c) 2012-2016 Jean-Philippe Aumasson
+   <jeanphilippe.aumasson@gmail.com>
+   Copyright (c) 2012-2014 Daniel J. Bernstein <djb@cr.yp.to>
+
+   To the extent possible under law, the author(s) have dedicated all copyright
+   and related and neighboring rights to this software to the public domain
+   worldwide. This software is distributed without any warranty.
+
+   You should have received a copy of the CC0 Public Domain Dedication along
+   with
+   this software. If not, see
+   <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+/* default: SipHash-2-4 */
+#define cROUNDS 2
+#define dROUNDS 4
+
+#define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b))))
+
+#define U32TO8_LE(p, v)                                                        \
+    (p)[0] = (uint8_t)((v));                                                   \
+    (p)[1] = (uint8_t)((v) >> 8);                                              \
+    (p)[2] = (uint8_t)((v) >> 16);                                             \
+    (p)[3] = (uint8_t)((v) >> 24);
+
+#define U64TO8_LE(p, v)                                                        \
+    U32TO8_LE((p), (uint32_t)((v)));                                           \
+    U32TO8_LE((p) + 4, (uint32_t)((v) >> 32));
+
+#define U8TO64_LE(p)                                                           \
+    (((uint64_t)((p)[0])) | ((uint64_t)((p)[1]) << 8) |                        \
+     ((uint64_t)((p)[2]) << 16) | ((uint64_t)((p)[3]) << 24) |                 \
+     ((uint64_t)((p)[4]) << 32) | ((uint64_t)((p)[5]) << 40) |                 \
+     ((uint64_t)((p)[6]) << 48) | ((uint64_t)((p)[7]) << 56))
+
+#define SIPROUND                                                               \
+    do {                                                                       \
+        v0 += v1;                                                              \
+        v1 = ROTL(v1, 13);                                                     \
+        v1 ^= v0;                                                              \
+        v0 = ROTL(v0, 32);                                                     \
+        v2 += v3;                                                              \
+        v3 = ROTL(v3, 16);                                                     \
+        v3 ^= v2;                                                              \
+        v0 += v3;                                                              \
+        v3 = ROTL(v3, 21);                                                     \
+        v3 ^= v0;                                                              \
+        v2 += v1;                                                              \
+        v1 = ROTL(v1, 17);                                                     \
+        v1 ^= v2;                                                              \
+        v2 = ROTL(v2, 32);                                                     \
+    } while (0)
+
+#ifdef DEBUG
+#define TRACE                                                                  \
+    do {                                                                       \
+        printf("(%3d) v0 %08x %08x\n", (int)inlen, (uint32_t)(v0 >> 32),       \
+               (uint32_t)v0);                                                  \
+        printf("(%3d) v1 %08x %08x\n", (int)inlen, (uint32_t)(v1 >> 32),       \
+               (uint32_t)v1);                                                  \
+        printf("(%3d) v2 %08x %08x\n", (int)inlen, (uint32_t)(v2 >> 32),       \
+               (uint32_t)v2);                                                  \
+        printf("(%3d) v3 %08x %08x\n", (int)inlen, (uint32_t)(v3 >> 32),       \
+               (uint32_t)v3);                                                  \
+    } while (0)
+#else
+#define TRACE
+#endif
+
+int siphash(const uint8_t *in, const size_t inlen, const uint8_t *k,
+            uint8_t *out, const size_t outlen) {
+
+    assert((outlen == 8) || (outlen == 16));
+    uint64_t v0 = 0x736f6d6570736575ULL;
+    uint64_t v1 = 0x646f72616e646f6dULL;
+    uint64_t v2 = 0x6c7967656e657261ULL;
+    uint64_t v3 = 0x7465646279746573ULL;
+    uint64_t k0 = U8TO64_LE(k);
+    uint64_t k1 = U8TO64_LE(k + 8);
+    uint64_t m;
+    int i;
+    const uint8_t *end = in + inlen - (inlen % sizeof(uint64_t));
+    const int left = inlen & 7;
+    uint64_t b = ((uint64_t)inlen) << 56;
+    v3 ^= k1;
+    v2 ^= k0;
+    v1 ^= k1;
+    v0 ^= k0;
+
+    if (outlen == 16)
+        v1 ^= 0xee;
+
+    for (; in != end; in += 8) {
+        m = U8TO64_LE(in);
+        v3 ^= m;
+
+        TRACE;
+        for (i = 0; i < cROUNDS; ++i)
+            SIPROUND;
+
+        v0 ^= m;
+    }
+
+    switch (left) {
+    case 7:
+        b |= ((uint64_t)in[6]) << 48;
+    case 6:
+        b |= ((uint64_t)in[5]) << 40;
+    case 5:
+        b |= ((uint64_t)in[4]) << 32;
+    case 4:
+        b |= ((uint64_t)in[3]) << 24;
+    case 3:
+        b |= ((uint64_t)in[2]) << 16;
+    case 2:
+        b |= ((uint64_t)in[1]) << 8;
+    case 1:
+        b |= ((uint64_t)in[0]);
+        break;
+    case 0:
+        break;
+    }
+
+    v3 ^= b;
+
+    TRACE;
+    for (i = 0; i < cROUNDS; ++i)
+        SIPROUND;
+
+    v0 ^= b;
+
+    if (outlen == 16)
+        v2 ^= 0xee;
+    else
+        v2 ^= 0xff;
+
+    TRACE;
+    for (i = 0; i < dROUNDS; ++i)
+        SIPROUND;
+
+    b = v0 ^ v1 ^ v2 ^ v3;
+    U64TO8_LE(out, b);
+
+    if (outlen == 8)
+        return 0;
+
+    v1 ^= 0xdd;
+
+    TRACE;
+    for (i = 0; i < dROUNDS; ++i)
+        SIPROUND;
+
+    b = v0 ^ v1 ^ v2 ^ v3;
+    U64TO8_LE(out + 8, b);
+
+    return 0;
+}
index 3cdf9ceae851d2199f84ab0bc9e6f0bd9bcf0abf..3011a0ace7a2c2aee77d4efe76eddd83c1b8ac4c 100644 (file)
@@ -2376,6 +2376,8 @@ probe_anchor(struct module_env* env, struct trust_anchor* tp)
        edns.opt_list_out = NULL;
        edns.opt_list_inplace_cb_out = NULL;
        edns.padding_block_size = 0;
+       edns.cookie_present = 0;
+       edns.cookie_valid = 0;
        if(sldns_buffer_capacity(buf) < 65535)
                edns.udp_size = (uint16_t)sldns_buffer_capacity(buf);
        else    edns.udp_size = 65535;