Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DNS Coookies support #7

Closed
wants to merge 10 commits into from
6 changes: 3 additions & 3 deletions Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ EDIT = $(SED) \
TARGETS=nsd nsd-checkconf nsd-checkzone nsd-control nsd.conf.sample nsd-control-setup.sh
MANUALS=nsd.8 nsd-checkconf.8 nsd-checkzone.8 nsd-control.8 nsd.conf.5

COMMON_OBJ=answer.o axfr.o buffer.o configlexer.o configparser.o dname.o dns.o edns.o iterated_hash.o lookup3.o namedb.o nsec3.o options.o packet.o query.o rbtree.o radtree.o rdata.o region-allocator.o rrl.o tsig.o tsig-openssl.o udb.o udbradtree.o udbzone.o util.o bitset.o popen3.o
COMMON_OBJ=answer.o axfr.o buffer.o configlexer.o configparser.o dname.o dns.o edns.o iterated_hash.o lookup3.o namedb.o nsec3.o options.o packet.o query.o rbtree.o radtree.o rdata.o region-allocator.o rrl.o siphash.o tsig.o tsig-openssl.o udb.o udbradtree.o udbzone.o util.o bitset.o popen3.o
XFRD_OBJ=xfrd-disk.o xfrd-notify.o xfrd-tcp.o xfrd.o remote.o $(DNSTAP_OBJ)
NSD_OBJ=$(COMMON_OBJ) $(XFRD_OBJ) difffile.o ipc.o mini_event.o netio.o nsd.o server.o dbaccess.o dbcreate.o zlexer.o zonec.o zparser.o
ALL_OBJ=$(NSD_OBJ) nsd-checkconf.o nsd-checkzone.o nsd-control.o nsd-mem.o xfr-inspect.o
Expand Down Expand Up @@ -488,8 +488,8 @@ rrl.o: $(srcdir)/rrl.c config.h $(srcdir)/rrl.h $(srcdir)/query.h $(srcdir)/name
server.o: $(srcdir)/server.c config.h $(srcdir)/axfr.h $(srcdir)/nsd.h $(srcdir)/dns.h $(srcdir)/edns.h $(srcdir)/buffer.h \
$(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/query.h $(srcdir)/namedb.h $(srcdir)/dname.h $(srcdir)/radtree.h $(srcdir)/rbtree.h \
$(srcdir)/packet.h $(srcdir)/tsig.h $(srcdir)/netio.h $(srcdir)/xfrd.h $(srcdir)/options.h $(srcdir)/xfrd-tcp.h $(srcdir)/xfrd-disk.h \
$(srcdir)/difffile.h $(srcdir)/udb.h $(srcdir)/nsec3.h $(srcdir)/ipc.h $(srcdir)/remote.h $(srcdir)/lookup3.h $(srcdir)/rrl.h \
$(srcdir)/dnstap/dnstap_collector.h
$(srcdir)/difffile.h $(srcdir)/udb.h $(srcdir)/nsec3.h $(srcdir)/ipc.h $(srcdir)/remote.h $(srcdir)/lookup3.h $(srcdir)/dnstap/dnstap_collector.h $(srcdir)/rrl.h
siphash.o: $(srcdir)/siphash.c
tsig.o: $(srcdir)/tsig.c config.h $(srcdir)/tsig.h $(srcdir)/buffer.h $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/dname.h \
$(srcdir)/tsig-openssl.h $(srcdir)/dns.h $(srcdir)/packet.h $(srcdir)/namedb.h $(srcdir)/radtree.h $(srcdir)/rbtree.h $(srcdir)/query.h $(srcdir)/nsd.h \
$(srcdir)/edns.h
Expand Down
2 changes: 2 additions & 0 deletions configlexer.lex
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,8 @@ tls-service-key{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_TLS_SERVICE_KEY;
tls-service-ocsp{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_TLS_SERVICE_OCSP;}
tls-service-pem{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_TLS_SERVICE_PEM;}
tls-port{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_TLS_PORT;}
answer-cookie{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_ANSWER_COOKIE;}
cookie-secret{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_COOKIE_SECRET;}
{NEWLINE} { LEXOUT(("NL\n")); cfg_parser->line++;}

servers={UNQUOTEDLETTER}* {
Expand Down
2 changes: 2 additions & 0 deletions configparser.y
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ static int parse_range(const char *str, long long *low, long long *high);
%token VAR_REQUEST_XFR
%token VAR_ALLOW_AXFR_FALLBACK
%token VAR_OUTGOING_INTERFACE
%token VAR_ANSWER_COOKIE
%token VAR_COOKIE_SECRET
%token VAR_MAX_REFRESH_TIME
%token VAR_MIN_REFRESH_TIME
%token VAR_MAX_RETRY_TIME
Expand Down
152 changes: 148 additions & 4 deletions edns.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
#include "config.h"

#include <string.h>
#ifdef HAVE_SSL
#include <openssl/opensslv.h>
#include <openssl/evp.h>
#endif

#include "dns.h"
#include "edns.h"
Expand All @@ -33,15 +37,17 @@ edns_init_data(edns_data_type *data, uint16_t max_length)
data->error[3] = (max_length & 0xff00) >> 8; /* size_hi */
data->error[4] = max_length & 0x00ff; /* size_lo */
data->error[5] = 1; /* XXX Extended RCODE=BAD VERS */

/* COOKIE OPT HDR */
data->cookie[0] = (COOKIE_CODE & 0xff00) >> 8;
data->cookie[1] = (COOKIE_CODE & 0x00ff);
data->cookie[2] = (24 & 0xff00) >> 8;
data->cookie[3] = (24 & 0x00ff);
}

void
edns_init_nsid(edns_data_type *data, uint16_t nsid_len)
{
/* add nsid length bytes */
data->rdata_nsid[0] = ((OPT_HDR + nsid_len) & 0xff00) >> 8; /* length_hi */
data->rdata_nsid[1] = ((OPT_HDR + nsid_len) & 0x00ff); /* length_lo */

/* NSID OPT HDR */
data->nsid[0] = (NSID_CODE & 0xff00) >> 8;
data->nsid[1] = (NSID_CODE & 0x00ff);
Expand All @@ -58,6 +64,8 @@ edns_init_record(edns_record_type *edns)
edns->opt_reserved_space = 0;
edns->dnssec_ok = 0;
edns->nsid = 0;
edns->cookie_status = COOKIE_NOT_PRESENT;
edns->cookie_len = 0;
}

/** handle a single edns option in the query */
Expand All @@ -81,6 +89,24 @@ edns_handle_option(uint16_t optcode, uint16_t optlen, buffer_type* packet,
buffer_skip(packet, optlen);
}
break;
case COOKIE_CODE:
/* Cookies enabled? */
if(nsd->do_answer_cookie) {
if (optlen == 8)
edns->cookie_status = COOKIE_INVALID;
else if (optlen < 16 || optlen > 40)
return 0; /* FORMERR */
else
edns->cookie_status = COOKIE_UNVERIFIED;

edns->cookie_len = optlen;
memcpy(edns->cookie, buffer_current(packet), optlen);
buffer_skip(packet, optlen);
edns->opt_reserved_space += OPT_HDR + 24;
} else {
buffer_skip(packet, optlen);
}
break;
default:
buffer_skip(packet, optlen);
break;
Expand Down Expand Up @@ -158,3 +184,121 @@ edns_reserved_space(edns_record_type *edns)
/* MIEK; when a pkt is too large?? */
return edns->status == EDNS_NOT_PRESENT ? 0 : (OPT_LEN + OPT_RDATA + edns->opt_reserved_space);
}

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;
}

void cookie_verify(query_type *q, struct nsd* nsd, uint32_t *now_p)
{
uint8_t hash[8], hash2verify[8];
uint32_t cookie_time, now_uint32;

/* We support only draft-sury-toorop-dnsop-server-cookies sizes */
if(q->edns.cookie_len != 24)
return;

if(q->edns.cookie[8] != 1)
return;

cookie_time = (q->edns.cookie[12] << 24)
| (q->edns.cookie[13] << 16)
| (q->edns.cookie[14] << 8)
| q->edns.cookie[15];

now_uint32 = *now_p ? *now_p : (*now_p = (uint32_t)time(NULL));

if(compare_1982(now_uint32, cookie_time) > 0) {
/* ignore cookies > 1 hour in past */
if (subtract_1982(cookie_time, now_uint32) > 3600)
return;
} else if (subtract_1982(now_uint32, cookie_time) > 300)
/* ignore cookies > 5 minutes in future */
return;
memcpy(hash2verify, q->edns.cookie + 16, 8);
#ifdef INET6
if (q->addr.ss_family == AF_INET6) {
memcpy( q->edns.cookie + 16
, &((struct sockaddr_in6 *)&q->addr)->sin6_addr, 16);
siphash(q->edns.cookie, 32, nsd->cookie_secret, hash, 8);
} else {
memcpy( q->edns.cookie + 16
, &((struct sockaddr_in *)&q->addr)->sin_addr, 4);
siphash(q->edns.cookie, 20, nsd->cookie_secret, hash, 8);
}
#else
memcpy( q->edns.cookie + 16, &q->addr->sin_addr, 4);
siphash(q->edns.cookie, 20, nsd->cookie_secret, hash, 8);
#endif
q->edns.cookie_status = memcmp(hash2verify, hash, 8)
? COOKIE_INVALID : COOKIE_VALID;
}

void cookie_create(query_type *q, struct nsd* nsd, uint32_t *now_p)
{
uint8_t hash[8];
uint32_t now_uint32 = *now_p
? *now_p : (*now_p = (uint32_t)time(NULL));

q->edns.cookie[ 8] = 1;
q->edns.cookie[ 9] = 0;
q->edns.cookie[10] = 0;
q->edns.cookie[11] = 0;
q->edns.cookie[12] = (now_uint32 & 0xFF000000) >> 24;
q->edns.cookie[13] = (now_uint32 & 0x00FF0000) >> 16;
q->edns.cookie[14] = (now_uint32 & 0x0000FF00) >> 8;
q->edns.cookie[15] = now_uint32 & 0x000000FF;
#ifdef INET6
if (q->addr.ss_family == AF_INET6) {
memcpy( q->edns.cookie + 16
, &((struct sockaddr_in6 *)&q->addr)->sin6_addr, 16);
siphash(q->edns.cookie, 32, nsd->cookie_secret, hash, 8);
} else {
memcpy( q->edns.cookie + 16
, &((struct sockaddr_in *)&q->addr)->sin_addr, 4);
siphash(q->edns.cookie, 20, nsd->cookie_secret, hash, 8);
}
#else
memcpy( q->edns.cookie + 16, &q->addr->sin_addr, 4);
siphash(q->edns.cookie, 20, nsd->cookie_secret, hash, 8);
#endif
memcpy(q->edns.cookie + 16, hash, 8);
}

30 changes: 23 additions & 7 deletions edns.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@ struct query;
#define OPT_RDATA 2 /* holds the rdata length comes after OPT_LEN */
#define OPT_HDR 4U /* NSID opt header length */
#define NSID_CODE 3 /* nsid option code */
#define COOKIE_CODE 10 /* COOKIE option code */
#define DNSSEC_OK_MASK 0x8000U /* do bit mask */

struct edns_data
{
char ok[OPT_LEN];
char error[OPT_LEN];
char rdata_none[OPT_RDATA];
char rdata_nsid[OPT_RDATA];
char nsid[OPT_HDR];
char cookie[OPT_HDR];
};
typedef struct edns_data edns_data_type;

Expand All @@ -39,14 +40,26 @@ enum edns_status
};
typedef enum edns_status edns_status_type;

enum cookie_status
{
COOKIE_NOT_PRESENT,
COOKIE_UNVERIFIED,
COOKIE_VALID,
COOKIE_INVALID
};
typedef enum cookie_status cookie_status_type;

struct edns_record
{
edns_status_type status;
size_t position;
size_t maxlen;
size_t opt_reserved_space;
int dnssec_ok;
int nsid;
edns_status_type status;
size_t position;
size_t maxlen;
size_t opt_reserved_space;
int dnssec_ok;
int nsid;
cookie_status_type cookie_status;
size_t cookie_len;
uint8_t cookie[40];
};
typedef struct edns_record edns_record_type;

Expand All @@ -63,4 +76,7 @@ size_t edns_reserved_space(edns_record_type *data);

void edns_init_nsid(edns_data_type *data, uint16_t nsid_len);

void cookie_verify(struct query *q, struct nsd* nsd, uint32_t *now_p);
void cookie_create(struct query *q, struct nsd* nsd, uint32_t *now_p);

#endif /* _EDNS_H_ */
Loading